package miner

import (
	"github.com/ipfs/go-cid"
	actorstypes "github.com/filecoin-project/go-state-types/actors"
	"github.com/filecoin-project/go-state-types/big"
	"github.com/filecoin-project/go-state-types/network"
	"github.com/filecoin-project/lotus/chain/actors"
	cbg "github.com/whyrusleeping/cbor-gen"
	"golang.org/x/xerrors"

	"github.com/filecoin-project/go-bitfield"
	"github.com/filecoin-project/go-state-types/abi"
	"github.com/filecoin-project/go-state-types/cbor"
	"github.com/filecoin-project/go-state-types/dline"
	"github.com/filecoin-project/go-state-types/proof"

	"github.com/filecoin-project/lotus/chain/actors/adt"
	"github.com/filecoin-project/lotus/chain/types"
	minertypes "github.com/filecoin-project/go-state-types/builtin/v9/miner"
	"github.com/filecoin-project/go-state-types/manifest"

{{range .versions}}
    {{if (le . 7)}}
	    builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin"
    {{end}}
{{end}}
)

func Load(store adt.Store, act *types.Actor) (State, error) {
	if name, av, ok := actors.GetActorMetaByCode(act.Code); ok {
       if name != manifest.MinerKey {
          return nil, xerrors.Errorf("actor code is not miner: %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{{.}}.StorageMinerActorCodeID:
            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

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

	// Total available balance to spend.
	AvailableBalance(abi.TokenAmount) (abi.TokenAmount, error)
	// Funds that will vest by the given epoch.
	VestedFunds(abi.ChainEpoch) (abi.TokenAmount, error)
	// Funds locked for various reasons.
	LockedFunds() (LockedFunds, error)
	FeeDebt() (abi.TokenAmount, error)

    // Returns nil, nil if sector is not found
	GetSector(abi.SectorNumber) (*SectorOnChainInfo, error)
	FindSector(abi.SectorNumber) (*SectorLocation, error)
	GetSectorExpiration(abi.SectorNumber) (*SectorExpiration, error)
	GetPrecommittedSector(abi.SectorNumber) (*SectorPreCommitOnChainInfo, error)
	ForEachPrecommittedSector(func(SectorPreCommitOnChainInfo) error) error
	LoadSectors(sectorNos *bitfield.BitField) ([]*SectorOnChainInfo, error)
	NumLiveSectors() (uint64, error)
	IsAllocated(abi.SectorNumber) (bool, error)
        // UnallocatedSectorNumbers returns up to count unallocated sector numbers (or less than
        // count if there aren't enough).
	UnallocatedSectorNumbers(count int) ([]abi.SectorNumber, error)
	GetAllocatedSectors() (*bitfield.BitField, error)

	// Note that ProvingPeriodStart is deprecated and will be renamed / removed in a future version of actors
	GetProvingPeriodStart() (abi.ChainEpoch, error)
	// Testing only
	EraseAllUnproven() error

	LoadDeadline(idx uint64) (Deadline, error)
	ForEachDeadline(cb func(idx uint64, dl Deadline) error) error
	NumDeadlines() (uint64, error)
	DeadlinesChanged(State) (bool, error)

	Info() (MinerInfo, error)
	MinerInfoChanged(State) (bool, error)

	DeadlineInfo(epoch abi.ChainEpoch) (*dline.Info, error)
	DeadlineCronActive() (bool, error)

	// Diff helpers. Used by Diff* functions internally.
	sectors() (adt.Array, error)
	decodeSectorOnChainInfo(*cbg.Deferred) (SectorOnChainInfo, error)
	precommits() (adt.Map, error)
	decodeSectorPreCommitOnChainInfo(*cbg.Deferred) (SectorPreCommitOnChainInfo, error)
	GetState() interface{}
}

type Deadline interface {
	LoadPartition(idx uint64) (Partition, error)
	ForEachPartition(cb func(idx uint64, part Partition) error) error
	PartitionsPoSted() (bitfield.BitField, error)

	PartitionsChanged(Deadline) (bool, error)
	DisputableProofCount() (uint64, error)
}

type Partition interface {
	// AllSectors returns all sector numbers in this partition, including faulty, unproven, and terminated sectors
	AllSectors() (bitfield.BitField, error)

	// Subset of sectors detected/declared faulty and not yet recovered (excl. from PoSt).
	// Faults ∩ Terminated = ∅
	FaultySectors() (bitfield.BitField, error)

	// Subset of faulty sectors expected to recover on next PoSt
	// Recoveries ∩ Terminated = ∅
	RecoveringSectors() (bitfield.BitField, error)

	// Live sectors are those that are not terminated (but may be faulty).
	LiveSectors() (bitfield.BitField, error)

	// Active sectors are those that are neither terminated nor faulty nor unproven, i.e. actively contributing power.
	ActiveSectors() (bitfield.BitField, error)

	// Unproven sectors in this partition. This bitfield will be cleared on
	// a successful window post (or at the end of the partition's next
	// deadline). At that time, any still unproven sectors will be added to
	// the faulty sector bitfield.
	UnprovenSectors() (bitfield.BitField, error)
}

type SectorOnChainInfo = minertypes.SectorOnChainInfo

func PreferredSealProofTypeFromWindowPoStType(nver network.Version, proof abi.RegisteredPoStProof) (abi.RegisteredSealProof, error) {
	// We added support for the new proofs in network version 7, and removed support for the old
	// ones in network version 8.
	if nver < network.Version7 {
		switch proof {
		case abi.RegisteredPoStProof_StackedDrgWindow2KiBV1:
			return abi.RegisteredSealProof_StackedDrg2KiBV1, nil
		case abi.RegisteredPoStProof_StackedDrgWindow8MiBV1:
			return abi.RegisteredSealProof_StackedDrg8MiBV1, nil
		case abi.RegisteredPoStProof_StackedDrgWindow512MiBV1:
			return abi.RegisteredSealProof_StackedDrg512MiBV1, nil
		case abi.RegisteredPoStProof_StackedDrgWindow32GiBV1:
			return abi.RegisteredSealProof_StackedDrg32GiBV1, nil
		case abi.RegisteredPoStProof_StackedDrgWindow64GiBV1:
			return abi.RegisteredSealProof_StackedDrg64GiBV1, nil
		default:
			return -1, xerrors.Errorf("unrecognized window post type: %d", proof)
		}
	}

	switch proof {
	case abi.RegisteredPoStProof_StackedDrgWindow2KiBV1, abi.RegisteredPoStProof_StackedDrgWindow2KiBV1_1:
		return abi.RegisteredSealProof_StackedDrg2KiBV1_1, nil
	case abi.RegisteredPoStProof_StackedDrgWindow8MiBV1, abi.RegisteredPoStProof_StackedDrgWindow8MiBV1_1:
		return abi.RegisteredSealProof_StackedDrg8MiBV1_1, nil
	case abi.RegisteredPoStProof_StackedDrgWindow512MiBV1, abi.RegisteredPoStProof_StackedDrgWindow512MiBV1_1:
		return abi.RegisteredSealProof_StackedDrg512MiBV1_1, nil
	case abi.RegisteredPoStProof_StackedDrgWindow32GiBV1, abi.RegisteredPoStProof_StackedDrgWindow32GiBV1_1:
		return abi.RegisteredSealProof_StackedDrg32GiBV1_1, nil
	case abi.RegisteredPoStProof_StackedDrgWindow64GiBV1, abi.RegisteredPoStProof_StackedDrgWindow64GiBV1_1:
		return abi.RegisteredSealProof_StackedDrg64GiBV1_1, nil
	default:
		return -1, xerrors.Errorf("unrecognized window post type: %d", proof)
	}
}

func WinningPoStProofTypeFromWindowPoStProofType(nver network.Version, proof abi.RegisteredPoStProof) (abi.RegisteredPoStProof, error) {
	switch proof {
	case abi.RegisteredPoStProof_StackedDrgWindow2KiBV1, abi.RegisteredPoStProof_StackedDrgWindow2KiBV1_1:
		return abi.RegisteredPoStProof_StackedDrgWinning2KiBV1, nil
	case abi.RegisteredPoStProof_StackedDrgWindow8MiBV1, abi.RegisteredPoStProof_StackedDrgWindow8MiBV1_1:
		return abi.RegisteredPoStProof_StackedDrgWinning8MiBV1, nil
	case abi.RegisteredPoStProof_StackedDrgWindow512MiBV1, abi.RegisteredPoStProof_StackedDrgWindow512MiBV1_1:
		return abi.RegisteredPoStProof_StackedDrgWinning512MiBV1, nil
	case abi.RegisteredPoStProof_StackedDrgWindow32GiBV1, abi.RegisteredPoStProof_StackedDrgWindow32GiBV1_1:
		return abi.RegisteredPoStProof_StackedDrgWinning32GiBV1, nil
	case abi.RegisteredPoStProof_StackedDrgWindow64GiBV1, abi.RegisteredPoStProof_StackedDrgWindow64GiBV1_1:
		return abi.RegisteredPoStProof_StackedDrgWinning64GiBV1, nil
	default:
		return -1, xerrors.Errorf("unknown proof type %d", proof)
	}
}

type MinerInfo = minertypes.MinerInfo
type BeneficiaryTerm = minertypes.BeneficiaryTerm
type PendingBeneficiaryChange = minertypes.PendingBeneficiaryChange
type WorkerKeyChange = minertypes.WorkerKeyChange
type SectorPreCommitOnChainInfo = minertypes.SectorPreCommitOnChainInfo
type SectorPreCommitInfo = minertypes.SectorPreCommitInfo
type WindowPostVerifyInfo = proof.WindowPoStVerifyInfo

type SectorExpiration struct {
	OnTime abi.ChainEpoch

	// non-zero if sector is faulty, epoch at which it will be permanently
	// removed if it doesn't recover
	Early abi.ChainEpoch
}

type SectorLocation struct {
	Deadline  uint64
	Partition uint64
}

type SectorChanges struct {
	Added    []SectorOnChainInfo
	Extended []SectorExtensions
	Removed  []SectorOnChainInfo
}

type SectorExtensions struct {
	From SectorOnChainInfo
	To   SectorOnChainInfo
}

type PreCommitChanges struct {
	Added   []SectorPreCommitOnChainInfo
	Removed []SectorPreCommitOnChainInfo
}

type LockedFunds struct {
	VestingFunds             abi.TokenAmount
	InitialPledgeRequirement abi.TokenAmount
	PreCommitDeposits        abi.TokenAmount
}

func (lf LockedFunds) TotalLockedFunds() abi.TokenAmount {
	return big.Add(lf.VestingFunds, big.Add(lf.InitialPledgeRequirement, lf.PreCommitDeposits))
}

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