package market

import (
	"unicode/utf8"

	"github.com/filecoin-project/go-state-types/network"
	"golang.org/x/xerrors"

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

	market{{.latestVersion}} "github.com/filecoin-project/go-state-types/builtin{{import .latestVersion}}market"
{{range .versions}}
    {{if (le . 7)}}
	    builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin"
	{{else}}
        builtin{{.}} "github.com/filecoin-project/go-state-types/builtin"
    {{end}}
{{end}}

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

var (
	Address = builtin{{.latestVersion}}.StorageMarketActorAddr
	Methods = builtin{{.latestVersion}}.MethodsMarket
)

func Load(store adt.Store, act *types.Actor) (State, error) {
	if name, av, ok := actors.GetActorMetaByCode(act.Code); ok {
       if name != actors.MarketKey {
          return nil, xerrors.Errorf("actor code is not market: %s", name)
       }

       switch av {
            {{range .versions}}
                {{if (ge . 8)}}
                case actors.Version{{.}}:
                     return load{{.}}(store, act.Head)
                 {{end}}
            {{end}}
       }
	}

	switch act.Code {
{{range .versions}}
    {{if (le . 7)}}
        case builtin{{.}}.StorageMarketActorCodeID:
            return load{{.}}(store, act.Head)
    {{end}}
{{end}}
	}

	return nil, xerrors.Errorf("unknown actor code %s", act.Code)
}

func MakeState(store adt.Store, av actors.Version) (State, error) {
	switch av {
{{range .versions}}
	case actors.Version{{.}}:
		return make{{.}}(store)
{{end}}
}
	return nil, xerrors.Errorf("unknown actor version %d", av)
}

type State interface {
	cbor.Marshaler
	BalancesChanged(State) (bool, error)
	EscrowTable() (BalanceTable, error)
	LockedTable() (BalanceTable, error)
	TotalLocked() (abi.TokenAmount, error)
	StatesChanged(State) (bool, error)
	States() (DealStates, error)
	ProposalsChanged(State) (bool, error)
	Proposals() (DealProposals, error)
	VerifyDealsForActivation(
		minerAddr address.Address, deals []abi.DealID, currEpoch, sectorExpiry abi.ChainEpoch,
	) (weight, verifiedWeight abi.DealWeight, err error)
	NextID() (abi.DealID, error)
	GetState() interface{}
}

type BalanceTable interface {
	ForEach(cb func(address.Address, abi.TokenAmount) error) error
	Get(key address.Address) (abi.TokenAmount, error)
}

type DealStates interface {
	ForEach(cb func(id abi.DealID, ds DealState) error) error
	Get(id abi.DealID) (*DealState, bool, error)

	array() adt.Array
	decode(*cbg.Deferred) (*DealState, error)
}

type DealProposals interface {
	ForEach(cb func(id abi.DealID, dp market{{.latestVersion}}.DealProposal) error) error
	Get(id abi.DealID) (*market{{.latestVersion}}.DealProposal, bool, error)

	array() adt.Array
	decode(*cbg.Deferred) (*market{{.latestVersion}}.DealProposal, error)
}


type PublishStorageDealsReturn interface {
    DealIDs() ([]abi.DealID, error)
	// Note that this index is based on the batch of deals that were published, NOT the DealID
	IsDealValid(index uint64) (bool, int, error)
}

func DecodePublishStorageDealsReturn(b []byte, nv network.Version) (PublishStorageDealsReturn, error) {
	av, err := actors.VersionForNetwork(nv)
	if err != nil {
		return nil, err
	}

	switch av {
{{range .versions}}
	case actors.Version{{.}}:
		return decodePublishStorageDealsReturn{{.}}(b)
{{end}}
}
	return nil, xerrors.Errorf("unknown actor version %d", av)
}

type DealProposal = market{{.latestVersion}}.DealProposal

type DealState = market{{.latestVersion}}.DealState

type DealStateChanges struct {
	Added	 []DealIDState
	Modified []DealStateChange
	Removed	 []DealIDState
}

type DealIDState struct {
	ID	 abi.DealID
	Deal DealState
}

// DealStateChange is a change in deal state from -> to
type DealStateChange struct {
	ID	 abi.DealID
	From *DealState
	To	 *DealState
}

type DealProposalChanges struct {
	Added	[]ProposalIDState
	Removed []ProposalIDState
}

type ProposalIDState struct {
	ID		 abi.DealID
	Proposal market{{.latestVersion}}.DealProposal
}

func EmptyDealState() *DealState {
	return &DealState{
		SectorStartEpoch: -1,
		SlashEpoch:		  -1,
		LastUpdatedEpoch: -1,
	}
}

// returns the earned fees and pending fees for a given deal
func GetDealFees(deal market{{.latestVersion}}.DealProposal, height abi.ChainEpoch) (abi.TokenAmount, abi.TokenAmount) {
	   tf := big.Mul(deal.StoragePricePerEpoch, big.NewInt(int64(deal.EndEpoch-deal.StartEpoch)))

	   ef := big.Mul(deal.StoragePricePerEpoch, big.NewInt(int64(height-deal.StartEpoch)))
	   if ef.LessThan(big.Zero()) {
			   ef = big.Zero()
	   }

	   if ef.GreaterThan(tf) {
			   ef = tf
	   }

	   return ef, big.Sub(tf, ef)
}

func IsDealActive(state market{{.latestVersion}}.DealState) bool {
    return state.SectorStartEpoch > -1 && state.SlashEpoch == -1
}

func labelFromGoString(s string) (market{{.latestVersion}}.DealLabel, error) {
	if utf8.ValidString(s) {
		return market{{.latestVersion}}.NewLabelFromString(s)
	} else {
		return market{{.latestVersion}}.NewLabelFromBytes([]byte(s))
	}
}