package power

import (
	"github.com/filecoin-project/go-address"
	"github.com/filecoin-project/go-state-types/abi"
	cbg "github.com/whyrusleeping/cbor-gen"

	"github.com/filecoin-project/lotus/chain/actors/adt"
)

type ClaimChanges struct {
	Added    []ClaimInfo
	Modified []ClaimModification
	Removed  []ClaimInfo
}

type ClaimModification struct {
	Miner address.Address
	From  Claim
	To    Claim
}

type ClaimInfo struct {
	Miner address.Address
	Claim Claim
}

func DiffClaims(pre, cur State) (*ClaimChanges, error) {
	results := new(ClaimChanges)

	prec, err := pre.claims()
	if err != nil {
		return nil, err
	}

	curc, err := cur.claims()
	if err != nil {
		return nil, err
	}

	if err := adt.DiffAdtMap(prec, curc, &claimDiffer{results, pre, cur}); err != nil {
		return nil, err
	}

	return results, nil
}

type claimDiffer struct {
	Results    *ClaimChanges
	pre, after State
}

func (c *claimDiffer) AsKey(key string) (abi.Keyer, error) {
	addr, err := address.NewFromBytes([]byte(key))
	if err != nil {
		return nil, err
	}
	return abi.AddrKey(addr), nil
}

func (c *claimDiffer) Add(key string, val *cbg.Deferred) error {
	ci, err := c.after.decodeClaim(val)
	if err != nil {
		return err
	}
	addr, err := address.NewFromBytes([]byte(key))
	if err != nil {
		return err
	}
	c.Results.Added = append(c.Results.Added, ClaimInfo{
		Miner: addr,
		Claim: ci,
	})
	return nil
}

func (c *claimDiffer) Modify(key string, from, to *cbg.Deferred) error {
	ciFrom, err := c.pre.decodeClaim(from)
	if err != nil {
		return err
	}

	ciTo, err := c.after.decodeClaim(to)
	if err != nil {
		return err
	}

	addr, err := address.NewFromBytes([]byte(key))
	if err != nil {
		return err
	}

	if ciFrom != ciTo {
		c.Results.Modified = append(c.Results.Modified, ClaimModification{
			Miner: addr,
			From:  ciFrom,
			To:    ciTo,
		})
	}
	return nil
}

func (c *claimDiffer) Remove(key string, val *cbg.Deferred) error {
	ci, err := c.after.decodeClaim(val)
	if err != nil {
		return err
	}
	addr, err := address.NewFromBytes([]byte(key))
	if err != nil {
		return err
	}
	c.Results.Removed = append(c.Results.Removed, ClaimInfo{
		Miner: addr,
		Claim: ci,
	})
	return nil
}