package state_native

import (
	"github.com/pkg/errors"
	"github.com/prysmaticlabs/prysm/v4/beacon-chain/state/state-native/types"
	"github.com/prysmaticlabs/prysm/v4/beacon-chain/state/stateutil"
	fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
	"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
	"github.com/prysmaticlabs/prysm/v4/crypto/hash"
	"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
	ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
	"github.com/prysmaticlabs/prysm/v4/runtime/version"
	"google.golang.org/protobuf/proto"
)

// For our setters, we have a field reference counter through
// which we can track shared field references. This helps when
// performing state copies, as we simply copy the reference to the
// field. When we do need to modify these fields, we
// perform a full copy of the field. This is true of most of our
// fields except for the following below.
// 1) BlockRoots
// 2) StateRoots
// 3) Eth1DataVotes
// 4) RandaoMixes
// 5) HistoricalRoots
// 6) CurrentEpochAttestations
// 7) PreviousEpochAttestations
// 8) Validators
//
// The fields referred to above are instead copied by reference, where
// we simply copy the reference to the underlying object instead of the
// whole object. This is possible due to how we have structured our state
// as we copy the value on read, so as to ensure the underlying object is
// not mutated while it is being accessed during a state read.

const (
	// This specifies the limit till which we process all dirty indices for a certain field.
	// If we have more dirty indices than the threshold, then we rebuild the whole trie. This
	// comes due to the fact that O(alogn) > O(n) beyond a certain value of a.
	indicesLimit = 8000
)

// SetGenesisTime for the beacon state.
func (b *BeaconState) SetGenesisTime(val uint64) error {
	b.lock.Lock()
	defer b.lock.Unlock()

	b.genesisTime = val
	b.markFieldAsDirty(types.GenesisTime)
	return nil
}

// SetGenesisValidatorsRoot for the beacon state.
func (b *BeaconState) SetGenesisValidatorsRoot(val []byte) error {
	b.lock.Lock()
	defer b.lock.Unlock()

	if len(val) != fieldparams.RootLength {
		return errors.New("incorrect validators root length")
	}
	b.genesisValidatorsRoot = bytesutil.ToBytes32(val)
	b.markFieldAsDirty(types.GenesisValidatorsRoot)
	return nil
}

// SetSlot for the beacon state.
func (b *BeaconState) SetSlot(val primitives.Slot) error {
	b.lock.Lock()
	defer b.lock.Unlock()

	b.slot = val
	b.markFieldAsDirty(types.Slot)
	return nil
}

// SetFork version for the beacon chain.
func (b *BeaconState) SetFork(val *ethpb.Fork) error {
	b.lock.Lock()
	defer b.lock.Unlock()

	fk, ok := proto.Clone(val).(*ethpb.Fork)
	if !ok {
		return errors.New("proto.Clone did not return a fork proto")
	}
	b.fork = fk
	b.markFieldAsDirty(types.Fork)
	return nil
}

// SetHistoricalRoots for the beacon state. Updates the entire
// list to a new value by overwriting the previous one.
func (b *BeaconState) SetHistoricalRoots(val [][]byte) error {
	b.lock.Lock()
	defer b.lock.Unlock()

	b.sharedFieldReferences[types.HistoricalRoots].MinusRef()
	b.sharedFieldReferences[types.HistoricalRoots] = stateutil.NewRef(1)

	roots := make([][32]byte, len(val))
	for i, r := range val {
		copy(roots[i][:], r)
	}
	b.historicalRoots = roots
	b.markFieldAsDirty(types.HistoricalRoots)
	return nil
}

// AppendHistoricalRoots for the beacon state. Appends the new value
// to the end of list.
func (b *BeaconState) AppendHistoricalRoots(root [32]byte) error {
	b.lock.Lock()
	defer b.lock.Unlock()

	if b.version > version.Bellatrix {
		return errNotSupported("AppendHistoricalRoots", b.version)
	}

	roots := b.historicalRoots
	if b.sharedFieldReferences[types.HistoricalRoots].Refs() > 1 {
		roots = make([][32]byte, len(b.historicalRoots))
		copy(roots, b.historicalRoots)
		b.sharedFieldReferences[types.HistoricalRoots].MinusRef()
		b.sharedFieldReferences[types.HistoricalRoots] = stateutil.NewRef(1)
	}

	b.historicalRoots = append(roots, root)
	b.markFieldAsDirty(types.HistoricalRoots)
	return nil
}

// AppendHistoricalSummaries for the beacon state. Appends the new value
// to the end of list.
func (b *BeaconState) AppendHistoricalSummaries(summary *ethpb.HistoricalSummary) error {
	b.lock.Lock()
	defer b.lock.Unlock()

	if b.version < version.Capella {
		return errNotSupported("AppendHistoricalSummaries", b.version)
	}

	summaries := b.historicalSummaries
	if b.sharedFieldReferences[types.HistoricalSummaries].Refs() > 1 {
		summaries = make([]*ethpb.HistoricalSummary, len(b.historicalSummaries))
		copy(summaries, b.historicalSummaries)
		b.sharedFieldReferences[types.HistoricalSummaries].MinusRef()
		b.sharedFieldReferences[types.HistoricalSummaries] = stateutil.NewRef(1)
	}

	b.historicalSummaries = append(summaries, summary)
	b.markFieldAsDirty(types.HistoricalSummaries)
	return nil
}

// Recomputes the branch up the index in the Merkle trie representation
// of the beacon state. This method performs slice reads and the caller MUST
// hold the lock before calling this method.
func (b *BeaconState) recomputeRoot(idx int) {
	hashFunc := hash.CustomSHA256Hasher()
	layers := b.merkleLayers
	// The merkle tree structure looks as follows:
	// [[r1, r2, r3, r4], [parent1, parent2], [root]]
	// Using information about the index which changed, idx, we recompute
	// only its branch up the tree.
	currentIndex := idx
	root := b.merkleLayers[0][idx]
	for i := 0; i < len(layers)-1; i++ {
		isLeft := currentIndex%2 == 0
		neighborIdx := currentIndex ^ 1

		neighbor := make([]byte, 32)
		if layers[i] != nil && len(layers[i]) != 0 && neighborIdx < len(layers[i]) {
			neighbor = layers[i][neighborIdx]
		}
		if isLeft {
			parentHash := hashFunc(append(root, neighbor...))
			root = parentHash[:]
		} else {
			parentHash := hashFunc(append(neighbor, root...))
			root = parentHash[:]
		}
		parentIdx := currentIndex / 2
		// Update the cached layers at the parent index.
		layers[i+1][parentIdx] = root
		currentIndex = parentIdx
	}
	b.merkleLayers = layers
}

func (b *BeaconState) markFieldAsDirty(field types.FieldIndex) {
	b.dirtyFields[field] = true
}

// addDirtyIndices adds the relevant dirty field indices, so that they
// can be recomputed.
func (b *BeaconState) addDirtyIndices(index types.FieldIndex, indices []uint64) {
	if b.rebuildTrie[index] {
		return
	}
	totalIndicesLen := len(b.dirtyIndices[index]) + len(indices)
	if totalIndicesLen > indicesLimit {
		b.rebuildTrie[index] = true
		b.dirtyIndices[index] = []uint64{}
	} else {
		b.dirtyIndices[index] = append(b.dirtyIndices[index], indices...)
	}
}
