package params

import (
	"context"
	"math/big"
)

type configKey int

const (
	IsHomesteadEnabled configKey = iota
	IsEIP150Enabled
	IsEIP155Enabled
	IsEIP158Enabled
	IsByzantiumEnabled
	IsConstantinopleEnabled
	IsPetersburgEnabled
	IsEWASM
	BlockNumber
	NoHistory
	WithHistoryHighest
)

func (c *ChainConfig) WithEIPsFlags(ctx context.Context, blockNum *big.Int) context.Context {
	ctx = context.WithValue(ctx, IsHomesteadEnabled, c.IsHomestead(blockNum))
	ctx = context.WithValue(ctx, IsEIP150Enabled, c.IsEIP150(blockNum))
	ctx = context.WithValue(ctx, IsEIP155Enabled, c.IsEIP155(blockNum))
	ctx = context.WithValue(ctx, IsEIP158Enabled, c.IsEIP158(blockNum))
	ctx = context.WithValue(ctx, IsByzantiumEnabled, c.IsByzantium(blockNum))
	ctx = context.WithValue(ctx, IsConstantinopleEnabled, c.IsConstantinople(blockNum))
	ctx = context.WithValue(ctx, IsPetersburgEnabled, c.IsPetersburg(blockNum))
	ctx = context.WithValue(ctx, IsEWASM, c.IsEWASM(blockNum))
	ctx = context.WithValue(ctx, BlockNumber, blockNum)
	return ctx
}

func WithNoHistory(ctx context.Context, defaultValue bool, f noHistFunc) context.Context {
	return context.WithValue(ctx, NoHistory, getIsNoHistory(defaultValue, f))
}

func GetForkFlag(ctx context.Context, name configKey) bool {
	b := ctx.Value(name)
	if b == nil {
		return false
	}
	if valB, ok := b.(bool); ok {
		return valB
	}
	return false
}

func GetBlockNumber(ctx context.Context) *big.Int {
	b := ctx.Value(BlockNumber)
	if b == nil {
		return nil
	}
	if valB, ok := b.(*big.Int); ok {
		return valB
	}
	return nil
}

func GetNoHistoryByBlock(ctx context.Context, currentBlock *big.Int) (context.Context, bool) {
	if currentBlock == nil {
		return ctx, false
	}

	key := getNoHistoryByBlock(currentBlock)
	v, ok := getNoHistoryByKey(ctx, key)
	if ok {
		if !v {
			ctx = updateHighestWithHistory(ctx, currentBlock)
		}
		return ctx, v
	}

	return withNoHistory(ctx, currentBlock, key)
}

func withNoHistory(ctx context.Context, currentBlock *big.Int, key configKey) (context.Context, bool) {
	v := getNoHistory(ctx, currentBlock)
	ctx = context.WithValue(ctx, key, v)
	if !v {
		ctx = updateHighestWithHistory(ctx, currentBlock)
	}

	return ctx, v
}

func updateHighestWithHistory(ctx context.Context, currentBlock *big.Int) context.Context {
	highestWithHistory := getWithHistoryHighest(ctx)
	var currentIsLower bool
	if highestWithHistory != nil {
		currentIsLower = currentBlock.Cmp(highestWithHistory) < 0
	}
	if !currentIsLower {
		ctx = setWithHistoryHighestByBlock(ctx, currentBlock)
	}
	return ctx
}

func getNoHistory(ctx context.Context, currentBlock *big.Int) bool {
	v := ctx.Value(NoHistory)
	if v == nil {
		return false
	}
	if val, ok := v.(noHistFunc); ok {
		return val(currentBlock)
	}
	return false
}

func getWithHistoryHighest(ctx context.Context) *big.Int {
	v := ctx.Value(WithHistoryHighest)
	if v == nil {
		return nil
	}
	if val, ok := v.(*big.Int); ok {
		return val
	}
	return nil
}

func setWithHistoryHighestByBlock(ctx context.Context, block *big.Int) context.Context {
	return context.WithValue(ctx, WithHistoryHighest, block)
}

func getNoHistoryByKey(ctx context.Context, key configKey) (bool, bool) {
	if key < 0 {
		return false, false
	}
	v := ctx.Value(key)
	if v == nil {
		return false, false
	}
	if val, ok := v.(bool); ok {
		return val, true
	}
	return false, false
}

func getNoHistoryByBlock(block *big.Int) configKey {
	if block == nil {
		return configKey(-1)
	}
	return configKey(block.Uint64() + 1000)
}

func GetNoHistory(ctx context.Context) (context.Context, bool) {
	return GetNoHistoryByBlock(ctx, GetBlockNumber(ctx))
}

type noHistFunc func(currentBlock *big.Int) bool

func getIsNoHistory(defaultValue bool, f noHistFunc) noHistFunc {
	return func(currentBlock *big.Int) bool {
		if f == nil {
			return defaultValue
		}

		return f(currentBlock)
	}
}