package multisig

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 PendingTransactionChanges struct {
	Added    []TransactionChange
	Modified []TransactionModification
	Removed  []TransactionChange
}

type TransactionChange struct {
	TxID int64
	Tx   Transaction
}

type TransactionModification struct {
	TxID int64
	From Transaction
	To   Transaction
}

func DiffPendingTransactions(pre, cur State) (*PendingTransactionChanges, error) {
	results := new(PendingTransactionChanges)
	if changed, err := pre.PendingTxnChanged(cur); err != nil {
		return nil, err
	} else if !changed { // if nothing has changed then return an empty result and bail.
		return results, nil
	}

	pret, err := pre.transactions()
	if err != nil {
		return nil, err
	}

	curt, err := cur.transactions()
	if err != nil {
		return nil, err
	}

	if err := adt.DiffAdtMap(pret, curt, &transactionDiffer{results, pre, cur}); err != nil {
		return nil, err
	}
	return results, nil
}

type transactionDiffer struct {
	Results    *PendingTransactionChanges
	pre, after State
}

func (t *transactionDiffer) AsKey(key string) (abi.Keyer, error) {
	txID, err := abi.ParseIntKey(key)
	if err != nil {
		return nil, err
	}
	return abi.IntKey(txID), nil
}

func (t *transactionDiffer) Add(key string, val *cbg.Deferred) error {
	txID, err := abi.ParseIntKey(key)
	if err != nil {
		return err
	}
	tx, err := t.after.decodeTransaction(val)
	if err != nil {
		return err
	}
	t.Results.Added = append(t.Results.Added, TransactionChange{
		TxID: txID,
		Tx:   tx,
	})
	return nil
}

func (t *transactionDiffer) Modify(key string, from, to *cbg.Deferred) error {
	txID, err := abi.ParseIntKey(key)
	if err != nil {
		return err
	}

	txFrom, err := t.pre.decodeTransaction(from)
	if err != nil {
		return err
	}

	txTo, err := t.after.decodeTransaction(to)
	if err != nil {
		return err
	}

	if approvalsChanged(txFrom.Approved, txTo.Approved) {
		t.Results.Modified = append(t.Results.Modified, TransactionModification{
			TxID: txID,
			From: txFrom,
			To:   txTo,
		})
	}

	return nil
}

func approvalsChanged(from, to []address.Address) bool {
	if len(from) != len(to) {
		return true
	}
	for idx := range from {
		if from[idx] != to[idx] {
			return true
		}
	}
	return false
}

func (t *transactionDiffer) Remove(key string, val *cbg.Deferred) error {
	txID, err := abi.ParseIntKey(key)
	if err != nil {
		return err
	}
	tx, err := t.pre.decodeTransaction(val)
	if err != nil {
		return err
	}
	t.Results.Removed = append(t.Results.Removed, TransactionChange{
		TxID: txID,
		Tx:   tx,
	})
	return nil
}