// Copyright (c) 2015-2023 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package standalone

import (
	"sort"
	"sync"
)

// SubsidyParams defines an interface that is used to provide the parameters
// required when calculating block and vote subsidies.  These values are
// typically well-defined and unique per network.
type SubsidyParams interface {
	// BlockOneSubsidy returns the total subsidy of block height 1 for the
	// network.  This is separate since it encompasses the initial coin
	// distribution.
	BlockOneSubsidy() int64

	// BaseSubsidyValue returns the starting base max potential subsidy amount
	// for mined blocks.  This value is reduced over time and then split
	// proportionally between PoW, PoS, and the Treasury.  The reduction is
	// controlled by the SubsidyReductionInterval, SubsidyReductionMultiplier,
	// and SubsidyReductionDivisor parameters.
	//
	// NOTE: BaseSubsidy must be a max of 140,739,635,871,744 atoms or incorrect
	// results will occur due to int64 overflow.  This value comes from
	// MaxInt64/MaxUint16 = (2^63 - 1)/(2^16 - 1) = 2^47 + 2^31 + 2^15.
	BaseSubsidyValue() int64

	// SubsidyReductionMultiplier returns the multiplier to use when performing
	// the exponential subsidy reduction described by the CalcBlockSubsidy
	// documentation.
	SubsidyReductionMultiplier() int64

	// SubsidyReductionDivisor returns the divisor to use when performing the
	// exponential subsidy reduction described by the CalcBlockSubsidy
	// documentation.
	SubsidyReductionDivisor() int64

	// SubsidyReductionIntervalBlocks returns the reduction interval in number
	// of blocks.
	SubsidyReductionIntervalBlocks() int64

	// These parameters control the proportional split of the max potential
	// block subsidy between PoW, PoS, and the Treasury.

	// WorkSubsidyProportion returns the comparative proportion of the subsidy
	// generated for creating a block (PoW) that was in effect prior to the
	// modified value defined in DCP0010.
	//
	// The proportional split between PoW, PoS, and the Treasury is calculated
	// by treating each of the proportional parameters as a ratio to the sum of
	// the three proportional parameters: WorkSubsidyProportion,
	// StakeSubsidyProportion, and TreasurySubsidyProportion.
	//
	// For example:
	// WorkSubsidyProportion:     6 => 6 / (6+3+1) => 6/10 => 60%
	// StakeSubsidyProportion:    3 => 3 / (6+3+1) => 3/10 => 30%
	// TreasurySubsidyProportion: 1 => 1 / (6+3+1) => 1/10 => 10%
	WorkSubsidyProportion() uint16

	// StakeSubsidyProportion returns the comparative proportion of the subsidy
	// generated for casting stake votes (collectively, per block) that was in
	// effect prior to the modified value defined in DCP0010.  See the
	// documentation for WorkSubsidyProportion for more details on how the
	// parameter is used.
	StakeSubsidyProportion() uint16

	// TreasurySubsidyProportion returns the comparative proportion of the
	// subsidy allocated to the project treasury.  See the documentation for
	// WorkSubsidyProportion for more details on how the parameter is used.
	TreasurySubsidyProportion() uint16

	// StakeValidationBeginHeight returns the height at which votes become
	// required to extend a block.  This height is the first that will be voted
	// on, but will not include any votes itself.
	StakeValidationBeginHeight() int64

	// VotesPerBlock returns the maximum number of votes a block must contain to
	// receive full subsidy once voting begins at StakeValidationBeginHeight.
	VotesPerBlock() uint16
}

// SubsidyCache provides efficient access to consensus-critical subsidy
// calculations for blocks and votes, including the max potential subsidy for
// given block heights, the proportional proof-of-work subsidy, the proportional
// proof of stake per-vote subsidy, and the proportional treasury subsidy.
//
// It makes using of caching to avoid repeated calculations.
type SubsidyCache struct {
	// The following fields are protected by the mtx mutex.
	//
	// cache houses the cached subsidies keyed by reduction interval.
	//
	// cachedIntervals contains an ordered list of all cached intervals.  It is
	// used to efficiently track sparsely cached intervals with O(log N)
	// discovery of a prior cached interval.
	mtx             sync.RWMutex
	cache           map[uint64]int64
	cachedIntervals []uint64

	// params stores the subsidy parameters to use during subsidy calculation.
	params SubsidyParams

	// These fields house values calculated from the parameters in order to
	// avoid repeated calculation.
	//
	// minVotesRequired is the minimum number of votes required for a block to
	// be consider valid by consensus.
	//
	// totalProportions is the sum of the PoW, PoS, and Treasury proportions.
	minVotesRequired uint16
	totalProportions uint16
}

// NewSubsidyCache creates and initializes a new subsidy cache instance.  See
// the SubsidyCache documentation for more details.
func NewSubsidyCache(params SubsidyParams) *SubsidyCache {
	// Initialize the cache with the first interval set to the base subsidy and
	// enough initial space for a few sparse entries for typical usage patterns.
	const prealloc = 5
	baseSubsidy := params.BaseSubsidyValue()
	cache := make(map[uint64]int64, prealloc)
	cache[0] = baseSubsidy

	return &SubsidyCache{
		cache:            cache,
		cachedIntervals:  make([]uint64, 1, prealloc),
		params:           params,
		minVotesRequired: (params.VotesPerBlock() / 2) + 1,
		totalProportions: params.WorkSubsidyProportion() +
			params.StakeSubsidyProportion() +
			params.TreasurySubsidyProportion(),
	}
}

// uint64s implements sort.Interface for *[]uint64.
type uint64s []uint64

func (s *uint64s) Len() int           { return len(*s) }
func (s *uint64s) Less(i, j int) bool { return (*s)[i] < (*s)[j] }
func (s *uint64s) Swap(i, j int)      { (*s)[i], (*s)[j] = (*s)[j], (*s)[i] }

// CalcBlockSubsidy returns the max potential subsidy for a block at the
// provided height.  This value is reduced over time based on the height and
// then split proportionally between PoW, PoS, and the Treasury.
//
// Subsidy calculation for exponential reductions:
//
//	subsidy := BaseSubsidyValue()
//	for i := 0; i < (height / SubsidyReductionIntervalBlocks()); i++ {
//	  subsidy *= SubsidyReductionMultiplier()
//	  subsidy /= SubsidyReductionDivisor()
//	}
//
// This function is safe for concurrent access.
func (c *SubsidyCache) CalcBlockSubsidy(height int64) int64 {
	// Negative block heights are invalid and produce no subsidy.
	// Block 0 is the genesis block and produces no subsidy.
	// Block 1 subsidy is special as it is used for initial token distribution.
	switch {
	case height <= 0:
		return 0
	case height == 1:
		return c.params.BlockOneSubsidy()
	}

	// Calculate the reduction interval associated with the requested height and
	// attempt to look it up in cache.  When it's not in the cache, look up the
	// latest cached interval and subsidy while the mutex is still held for use
	// below.
	reqInterval := uint64(height / c.params.SubsidyReductionIntervalBlocks())
	c.mtx.RLock()
	if cachedSubsidy, ok := c.cache[reqInterval]; ok {
		c.mtx.RUnlock()
		return cachedSubsidy
	}
	lastCachedInterval := c.cachedIntervals[len(c.cachedIntervals)-1]
	lastCachedSubsidy := c.cache[lastCachedInterval]
	c.mtx.RUnlock()

	// When the requested interval is after the latest cached interval, avoid
	// additional work by either determining if the subsidy is already exhausted
	// at that interval or using the interval as a starting point to calculate
	// and store the subsidy for the requested interval.
	//
	// Otherwise, the requested interval is prior to the final cached interval,
	// so use a binary search to find the latest cached interval prior to the
	// requested one and use it as a starting point to calculate and store the
	// subsidy for the requested interval.
	if reqInterval > lastCachedInterval {
		// Return zero for all intervals after the subsidy reaches zero.  This
		// enforces an upper bound on the number of entries in the cache.
		if lastCachedSubsidy == 0 {
			return 0
		}
	} else {
		c.mtx.RLock()
		cachedIdx := sort.Search(len(c.cachedIntervals), func(i int) bool {
			return c.cachedIntervals[i] >= reqInterval
		})
		lastCachedInterval = c.cachedIntervals[cachedIdx-1]
		lastCachedSubsidy = c.cache[lastCachedInterval]
		c.mtx.RUnlock()
	}

	// Finally, calculate the subsidy by applying the appropriate number of
	// reductions per the starting and requested interval.
	reductionMultiplier := c.params.SubsidyReductionMultiplier()
	reductionDivisor := c.params.SubsidyReductionDivisor()
	subsidy := lastCachedSubsidy
	neededIntervals := reqInterval - lastCachedInterval
	for i := uint64(0); i < neededIntervals; i++ {
		subsidy *= reductionMultiplier
		subsidy /= reductionDivisor

		// Stop once no further reduction is possible.  This ensures a bounded
		// computation for large requested intervals and that all future
		// requests for intervals at or after the final reduction interval
		// return 0 without recalculating.
		if subsidy == 0 {
			reqInterval = lastCachedInterval + i + 1
			break
		}
	}

	// Update the cache for the requested interval or the interval in which the
	// subsidy became zero when applicable.  The cached intervals are stored in
	// a map for O(1) lookup and also tracked via a sorted array to support the
	// binary searches for efficient sparse interval query support.
	c.mtx.Lock()
	c.cache[reqInterval] = subsidy
	c.cachedIntervals = append(c.cachedIntervals, reqInterval)
	sort.Sort((*uint64s)(&c.cachedIntervals))
	c.mtx.Unlock()
	return subsidy
}

// calcWorkSubsidy returns the proof of work subsidy for a block for a given
// number of votes using the provided work subsidy proportion and total
// proportions.  This is the primary implementation logic used by
// CalcWorkSubsidy, CalcWorkSubsidyV2, and CalcWorkSubsidyV3.
//
// See the comments of those functions for further details.
//
// This function is safe for concurrent access.
func (c *SubsidyCache) calcWorkSubsidy(height int64, voters uint16, proportion,
	totalProportions uint16) int64 {

	// The first block has special subsidy rules.
	if height == 1 {
		return c.params.BlockOneSubsidy()
	}

	// The subsidy is zero if there are not enough voters once voting begins.  A
	// block without enough voters will fail to validate anyway.
	stakeValidationHeight := c.params.StakeValidationBeginHeight()
	if height >= stakeValidationHeight && voters < c.minVotesRequired {
		return 0
	}

	// Calculate the full block subsidy and reduce it according to the PoW
	// proportion.
	subsidy := c.CalcBlockSubsidy(height)
	subsidy *= int64(proportion)
	subsidy /= int64(totalProportions)

	// Ignore any potential subsidy reductions due to the number of votes prior
	// to the point voting begins.
	if height < stakeValidationHeight {
		return subsidy
	}

	// Adjust for the number of voters.
	return (int64(voters) * subsidy) / int64(c.params.VotesPerBlock())
}

// CalcWorkSubsidy returns the proof of work subsidy for a block for a given
// number of votes using the subsidy split that was in effect prior to the
// modifed value defined in DCP0010.
//
// It is calculated as a proportion of the total subsidy and further reduced
// proportionally depending on the number of votes once the height at which
// voting begins has been reached.
//
// Note that passing a number of voters fewer than the minimum required for a
// block to be valid by consensus along with a height greater than or equal to
// the height at which voting begins will return zero.
//
// This function is safe for concurrent access.
//
// Deprecated: Use CalcWorkSubsidyV3 instead.
func (c *SubsidyCache) CalcWorkSubsidy(height int64, voters uint16) int64 {
	return c.calcWorkSubsidy(height, voters, c.params.WorkSubsidyProportion(),
		c.totalProportions)
}

// CalcWorkSubsidyV2 returns the proof of work subsidy for a block for a given
// number of votes using either the original subsidy split that was in effect at
// Decred launch or the modified subsidy split defined in DCP0010 according to
// the provided flag.
//
// It is calculated as a proportion of the total subsidy and further reduced
// proportionally depending on the number of votes once the height at which
// voting begins has been reached.
//
// Note that passing a number of voters fewer than the minimum required for a
// block to be valid by consensus along with a height greater than or equal to
// the height at which voting begins will return zero.
//
// This function is safe for concurrent access.
//
// Deprecated: Use CalcWorkSubsidyV3 instead.
func (c *SubsidyCache) CalcWorkSubsidyV2(height int64, voters uint16, useDCP0010 bool) int64 {
	if !useDCP0010 {
		return c.CalcWorkSubsidy(height, voters)
	}

	// The work subsidy proportion defined in DCP0010 is 10%.  Thus it is 1
	// since 1/10 = 10%.
	//
	// Note that the value is hard coded here as opposed to using the subsidy
	// params in order to avoid the need for a major module bump that would be
	// required if the subsidy params interface were changed.
	const workSubsidyProportion = 1
	const totalProportions = 10
	return c.calcWorkSubsidy(height, voters, workSubsidyProportion,
		totalProportions)
}

// SubsidySplitVariant defines the available variants for subsidy split
// calculations.
type SubsidySplitVariant uint8

const (
	// SSVOriginal specifies the original subsidy split that was in effect at
	// initial launch.  In particular, 60% PoW, 40% PoS, and 10% Treasury.
	SSVOriginal SubsidySplitVariant = iota

	// SSVDCP0010 specifies the modified subsidy split specified by DCP0010.
	// In particular, 10% PoW, 80% PoS, and 10% Treasury.
	SSVDCP0010

	// SSVDCP0012 specifies the modified subsidy split specified by DCP0012.
	// In particular, 1% PoW, 89% PoS, and 10% Treasury.
	SSVDCP0012
)

// CalcWorkSubsidyV3 returns the proof of work subsidy for a block for a given
// number of votes using the subsidy split determined by the provided subsidy
// split variant parameter.
//
// It is calculated as a proportion of the total subsidy and further reduced
// proportionally depending on the number of votes once the height at which
// voting begins has been reached.
//
// Note that passing a number of voters fewer than the minimum required for a
// block to be valid by consensus along with a height greater than or equal to
// the height at which voting begins will return zero.
//
// Passing an invalid subsidy split variant will be treated the same as the
// SSVOriginal variant.
//
// This function is safe for concurrent access.
func (c *SubsidyCache) CalcWorkSubsidyV3(height int64, voters uint16, splitVariant SubsidySplitVariant) int64 {
	switch splitVariant {
	case SSVDCP0010:
		// The work subsidy proportion defined in DCP0010 is 10%.  Thus it is 10
		// since 10/100 = 10%.
		//
		// Note that the value is hard coded here as opposed to using the
		// subsidy params in order to avoid the need for a major module bump
		// that would be required if the subsidy params interface were changed.
		const workSubsidyProportion = 10
		const totalProportions = 100
		return c.calcWorkSubsidy(height, voters, workSubsidyProportion,
			totalProportions)

	case SSVDCP0012:
		// The work subsidy proportion defined in DCP0012 is 1%.  Thus it is 1
		// since 1/100 = 1%.
		//
		// Note that the value is hard coded here as opposed to using the
		// subsidy params in order to avoid the need for a major module bump
		// that would be required if the subsidy params interface were changed.
		const workSubsidyProportion = 1
		const totalProportions = 100
		return c.calcWorkSubsidy(height, voters, workSubsidyProportion,
			totalProportions)
	}

	// Treat unknown subsidy split variants as the original.
	return c.CalcWorkSubsidy(height, voters)
}

// calcStakeVoteSubsidy returns the subsidy for a single stake vote for a block
// using the provided stake vote subsidy proportion.  It is calculated as the
// provided proportion of the total subsidy and max potential number of votes
// per block.  This is the primary implementation logic used by
// CalcStakeVoteSubsidy and CalcStakeVoteSubsidyV2.
//
// See the comments of those functions for further details.
//
// This function is safe for concurrent access.
func (c *SubsidyCache) calcStakeVoteSubsidy(height int64, proportion, totalProportions uint16) int64 {
	// Votes have no subsidy prior to the point voting begins.  The minus one
	// accounts for the fact that vote subsidy are, unfortunately, based on the
	// height that is being voted on as opposed to the block in which they are
	// included.
	if height < c.params.StakeValidationBeginHeight()-1 {
		return 0
	}

	// Calculate the full block subsidy and reduce it according to the stake
	// proportion.  Then divide it by the number of votes per block to arrive
	// at the amount per vote.
	subsidy := c.CalcBlockSubsidy(height)
	proportions := int64(totalProportions)
	subsidy *= int64(proportion)
	subsidy /= (proportions * int64(c.params.VotesPerBlock()))

	return subsidy
}

// CalcStakeVoteSubsidy returns the subsidy for a single stake vote for a block
// using the subsidy split that was in effect prior to the modifed value defined
// in DCP0010.
//
// It is calculated as a proportion of the total subsidy and max potential
// number of votes per block.
//
// Unlike the Proof-of-Work and Treasury subsidies, the subsidy that votes
// receive is not reduced when a block contains less than the maximum number of
// votes.  Consequently, this does not accept the number of votes.  However, it
// is important to note that blocks that do not receive the minimum required
// number of votes for a block to be valid by consensus won't actually produce
// any vote subsidy either since they are invalid.
//
// This function is safe for concurrent access.
//
// Deprecated: Use CalcStakeVoteSubsidyV3 instead.
func (c *SubsidyCache) CalcStakeVoteSubsidy(height int64) int64 {
	return c.calcStakeVoteSubsidy(height, c.params.StakeSubsidyProportion(),
		c.totalProportions)
}

// CalcStakeVoteSubsidyV2 returns the subsidy for a single stake vote for a
// block using either the original subsidy split that was in effect at Decred
// launch or the modified subsidy split defined in DCP0010 according to the
// provided flag.
//
// It is calculated as a proportion of the total subsidy and max potential
// number of votes per block.
//
// Unlike the Proof-of-Work and Treasury subsidies, the subsidy that votes
// receive is not reduced when a block contains less than the maximum number of
// votes.  Consequently, this does not accept the number of votes.  However, it
// is important to note that blocks that do not receive the minimum required
// number of votes for a block to be valid by consensus won't actually produce
// any vote subsidy either since they are invalid.
//
// This function is safe for concurrent access.
//
// Deprecated: Use CalcStakeVoteSubsidyV3 instead.
func (c *SubsidyCache) CalcStakeVoteSubsidyV2(height int64, useDCP0010 bool) int64 {
	if !useDCP0010 {
		return c.CalcStakeVoteSubsidy(height)
	}

	// The stake vote subsidy proportion defined in DCP0010 is 80%.  Thus it is
	// 8 since 8/10 = 80%.
	//
	// Note that the value is hard coded here as opposed to using the subsidy
	// params in order to avoid the need for a major module bump that would be
	// required if the subsidy params interface were changed.
	const voteSubsidyProportion = 8
	const totalProportions = 10
	return c.calcStakeVoteSubsidy(height, voteSubsidyProportion,
		totalProportions)
}

// CalcStakeVoteSubsidyV3 returns the subsidy for a single stake vote for a
// block using the subsidy split determined by the provided subsidy split
// variant parameter.
//
// It is calculated as a proportion of the total subsidy and max potential
// number of votes per block.
//
// Unlike the Proof-of-Work and Treasury subsidies, the subsidy that votes
// receive is not reduced when a block contains less than the maximum number of
// votes.  Consequently, this does not accept the number of votes.  However, it
// is important to note that blocks that do not receive the minimum required
// number of votes for a block to be valid by consensus won't actually produce
// any vote subsidy either since they are invalid.
//
// Passing an invalid subsidy split variant will be treated the same as the
// SSVOriginal variant.
//
// This function is safe for concurrent access.
func (c *SubsidyCache) CalcStakeVoteSubsidyV3(height int64, splitVariant SubsidySplitVariant) int64 {
	switch splitVariant {
	case SSVDCP0010:
		// The stake vote subsidy proportion defined in DCP0010 is 80%.  Thus it
		// is 80 since 80/100 = 80%.
		//
		// Note that the value is hard coded here as opposed to using the
		// subsidy params in order to avoid the need for a major module bump
		// that would be required if the subsidy params interface were changed.
		const voteSubsidyProportion = 80
		const totalProportions = 100
		return c.calcStakeVoteSubsidy(height, voteSubsidyProportion,
			totalProportions)

	case SSVDCP0012:
		// The stake vote subsidy proportion defined in DCP0012 is 89%.  Thus it
		// is 89 since 89/100 = 89%.
		//
		// Note that the value is hard coded here as opposed to using the
		// subsidy params in order to avoid the need for a major module bump
		// that would be required if the subsidy params interface were changed.
		const voteSubsidyProportion = 89
		const totalProportions = 100
		return c.calcStakeVoteSubsidy(height, voteSubsidyProportion,
			totalProportions)
	}

	return c.CalcStakeVoteSubsidy(height)
}

// CalcTreasurySubsidy returns the subsidy required to go to the treasury for
// a block.  It is calculated as a proportion of the total subsidy and further
// reduced proportionally depending on the number of votes once the height at
// which voting begins has been reached.
//
// Note that passing a number of voters fewer than the minimum required for a
// block to be valid by consensus along with a height greater than or equal to
// the height at which voting begins will return zero.
//
// When the treasury agenda is active the subsidy rule changes from paying out
// a proportion based on the number of votes to always pay the full subsidy.
//
// This function is safe for concurrent access.
func (c *SubsidyCache) CalcTreasurySubsidy(height int64, voters uint16, isTreasuryEnabled bool) int64 {
	// The first two blocks have special subsidy rules.
	if height <= 1 {
		return 0
	}

	// The subsidy is zero if there are not enough voters once voting
	// begins.  A block without enough voters will fail to validate anyway.
	stakeValidationHeight := c.params.StakeValidationBeginHeight()
	if height >= stakeValidationHeight && voters < c.minVotesRequired {
		return 0
	}

	// Calculate the full block subsidy and reduce it according to the
	// treasury proportion.
	subsidy := c.CalcBlockSubsidy(height)
	subsidy *= int64(c.params.TreasurySubsidyProportion())
	subsidy /= int64(c.totalProportions)

	// Ignore any potential subsidy reductions due to the number of votes
	// prior to the point voting begins or if treasury is enabled.
	if height < stakeValidationHeight || isTreasuryEnabled {
		return subsidy
	}

	// Adjust for the number of voters.
	return (int64(voters) * subsidy) / int64(c.params.VotesPerBlock())
}
