package database

import (
	"testing"

	"github.com/vapor/protocol/bc"
	"github.com/vapor/protocol/bc/types"
	"github.com/vapor/protocol/state"
)

func TestBlockCache(t *testing.T) {
	newBlock := func(h uint64) *types.Block {
		return &types.Block{
			BlockHeader: types.BlockHeader{
				Height: h,
			},
		}
	}

	newConsensusResult := func(seq uint64) *state.ConsensusResult {
		return &state.ConsensusResult{
			Seq: seq,
		}
	}

	blocks := make(map[bc.Hash]*types.Block)
	blockHashes := make(map[uint64]*bc.Hash)
	blockIndexHashes := make(map[uint64][]*bc.Hash)
	for i := 0; i < maxCachedBlockHeaders+10; i++ {
		block := newBlock(uint64(i))
		hash := block.Hash()
		blocks[hash] = block
		blockHashes[block.Height] = &hash
		blockIndexHashes[block.Height] = append(blockIndexHashes[block.Height], &hash)
	}

	consensusResults := make(map[uint64]*state.ConsensusResult)
	for i := 0; i < maxCachedConsensusResults+10; i++ {
		consensusResult := newConsensusResult(uint64(i))
		consensusResults[consensusResult.Seq] = consensusResult
	}

	fillBlockHeaderFn := func(hash *bc.Hash) (*types.BlockHeader, error) {
		return &blocks[*hash].BlockHeader, nil
	}

	fillBlockTxsFn := func(hash *bc.Hash) ([]*types.Tx, error) {
		return blocks[*hash].Transactions, nil
	}

	fillBlockHashesFn := func(height uint64) ([]*bc.Hash, error) {
		return blockIndexHashes[height], nil
	}

	fillMainChainHashFn := func(height uint64) (*bc.Hash, error) {
		return blockHashes[height], nil
	}

	fillConsensusResultFn := func(seq uint64) (*state.ConsensusResult, error) {
		return consensusResults[seq], nil
	}

	cache := newCache(fillBlockHeaderFn, fillBlockTxsFn, fillBlockHashesFn, fillMainChainHashFn, fillConsensusResultFn)
	for i := 0; i < maxCachedBlockHeaders+10; i++ {
		block := newBlock(uint64(i))
		hash := block.Hash()
		cache.lookupBlockHeader(&hash)
	}

	for i := 0; i < 10; i++ {
		block := newBlock(uint64(i))
		hash := block.Hash()
		if _, ok := cache.lruBlockHeaders.Get(hash); ok {
			t.Fatalf("find old block header")
		}
	}

	for i := 10; i < maxCachedBlockHeaders+10; i++ {
		block := newBlock(uint64(i))
		hash := block.Hash()
		if _, ok := cache.lruBlockHeaders.Get(hash); !ok {
			t.Fatalf("can't find new block header")
		}
	}

	for i := 0; i < maxCachedBlockTransactions+10; i++ {
		block := newBlock(uint64(i))
		hash := block.Hash()
		cache.lookupBlockTxs(&hash)
	}

	for i := 0; i < 10; i++ {
		block := newBlock(uint64(i))
		hash := block.Hash()
		if _, ok := cache.lruBlockTxs.Get(hash); ok {
			t.Fatalf("find old block transactions")
		}
	}

	for i := 10; i < maxCachedBlockTransactions+10; i++ {
		block := newBlock(uint64(i))
		hash := block.Hash()
		if _, ok := cache.lruBlockTxs.Get(hash); !ok {
			t.Fatalf("can't find new block transactions")
		}
	}

	for i := 0; i < maxCachedBlockHashes+10; i++ {
		block := newBlock(uint64(i))
		cache.lookupBlockHashesByHeight(block.Height)
	}

	for i := 0; i < 10; i++ {
		block := newBlock(uint64(i))
		if _, ok := cache.lruBlockHashes.Get(block.Height); ok {
			t.Fatalf("find old block Hashes for specified height")
		}
	}

	for i := 10; i < maxCachedBlockHashes+10; i++ {
		block := newBlock(uint64(i))
		if _, ok := cache.lruBlockHashes.Get(block.Height); !ok {
			t.Fatalf("can't find new block Hashes for specified height")
		}
	}

	for i := 0; i < maxCachedMainChainHashes+10; i++ {
		block := newBlock(uint64(i))
		cache.lookupMainChainHash(block.Height)
	}

	for i := 0; i < 10; i++ {
		block := newBlock(uint64(i))
		if _, ok := cache.lruMainChainHashes.Get(block.Height); ok {
			t.Fatalf("find old main chain block Hash for specified height")
		}
	}

	for i := 10; i < maxCachedMainChainHashes+10; i++ {
		block := newBlock(uint64(i))
		if _, ok := cache.lruMainChainHashes.Get(block.Height); !ok {
			t.Fatalf("can't find new main chain block Hash for specified height")
		}
	}

	for i := 0; i < maxCachedConsensusResults+10; i++ {
		consensusResult := newConsensusResult(uint64(i))
		cache.lookupConsensusResult(consensusResult.Seq)
	}

	for i := 0; i < 10; i++ {
		consensusResult := newConsensusResult(uint64(i))
		if _, ok := cache.lruConsensusResults.Get(consensusResult.Seq); ok {
			t.Fatalf("find old vote result")
		}
	}

	for i := 10; i < maxCachedConsensusResults+10; i++ {
		consensusResult := newConsensusResult(uint64(i))
		if _, ok := cache.lruConsensusResults.Get(consensusResult.Seq); !ok {
			t.Fatalf("can't find new vote result")
		}
	}
}
