package market

import (
	"github.com/ipfs/go-cid"
	actorstypes "github.com/filecoin-project/go-state-types/actors"
    "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"
	"github.com/filecoin-project/go-state-types/manifest"

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

    builtintypes "github.com/filecoin-project/go-state-types/builtin"

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

var (
	Address = builtintypes.StorageMarketActorAddr
	Methods = builtintypes.MethodsMarket
)

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

       switch av {
            {{range .versions}}
                {{if (ge . 8)}}
                case actorstypes.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 actorstypes.Version) (State, error) {
	switch av {
{{range .versions}}
	case actorstypes.Version{{.}}:
		return make{{.}}(store)
{{end}}
}
	return nil, xerrors.Errorf("unknown actor version %d", av)
}

type State interface {
	cbor.Marshaler

    Code() cid.Cid
    ActorKey() string
    ActorVersion() actorstypes.Version

	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{}
	GetAllocationIdForPendingDeal(dealId abi.DealID) (verifregtypes.AllocationId, error)
}

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 markettypes.DealProposal) error) error
	Get(id abi.DealID) (*markettypes.DealProposal, bool, error)

	array() adt.Array
	decode(*cbg.Deferred) (*markettypes.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 := actorstypes.VersionForNetwork(nv)
	if err != nil {
		return nil, err
	}

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

type DealProposal = markettypes.DealProposal
type DealLabel = markettypes.DealLabel

type DealState = markettypes.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 markettypes.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 markettypes.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 markettypes.DealState) bool {
    return state.SectorStartEpoch > -1 && state.SlashEpoch == -1
}

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

func AllCodes() []cid.Cid {
	return []cid.Cid{ {{range .versions}}
        (&state{{.}}{}).Code(),
    {{- end}}
    }
}
