package database.wallet;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.TreeMap;

import org.mapdb.BTreeKeySerializer;
import org.mapdb.DB;
import org.mapdb.Fun;
import org.mapdb.Fun.Tuple2;
import org.mapdb.BTreeMap;

import qora.account.Account;
import qora.block.Block;
import utils.ObserverMessage;
import utils.Pair;
import utils.ReverseComparator;
import database.DBMap;
import database.serializer.BlockSerializer;

public class BlockMap extends DBMap<Tuple2<String, String>, Block>
{
	public static final int TIMESTAMP_INDEX = 1;
	public static final int GENERATOR_INDEX = 2;
	public static final int BALANCE_INDEX = 3;
	public static final int TRANSACTIONS_INDEX = 4;
	public static final int FEE_INDEX = 5;
	
	private Map<Integer, Integer> observableData = new HashMap<Integer, Integer>();
	
	public BlockMap(WalletDatabase walletDatabase, DB database)
	{
		super(walletDatabase, database);
		
		this.observableData.put(DBMap.NOTIFY_ADD, ObserverMessage.ADD_BLOCK_TYPE);
		this.observableData.put(DBMap.NOTIFY_REMOVE, ObserverMessage.REMOVE_BLOCK_TYPE);
		this.observableData.put(DBMap.NOTIFY_LIST, ObserverMessage.LIST_BLOCK_TYPE);
	}

	public BlockMap(BlockMap parent) 
	{
		super(parent);
	}
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	protected void createIndexes(DB database)
	{
		//TIMESTAMP INDEX
		NavigableSet<Tuple2<Long, Tuple2<String, String>>> timestampIndex = database.createTreeSet("blocks_index_timestamp")
				.comparator(Fun.COMPARATOR)
				.makeOrGet();
		
		NavigableSet<Tuple2<Long, Tuple2<String, String>>> descendingTimestampIndex = database.createTreeSet("blocks_index_timestamp_descending")
				.comparator(new ReverseComparator(Fun.COMPARATOR))
				.makeOrGet();
		
		createIndex(TIMESTAMP_INDEX, timestampIndex, descendingTimestampIndex, new Fun.Function2<Long, Tuple2<String, String>, Block>() {
		   	@Override
		    public Long run(Tuple2<String, String> key, Block value) {
		   		return value.getTimestamp();
		    }
		});
		
		//GENERATOR INDEX
		NavigableSet<Tuple2<String, Tuple2<String, String>>> generatorIndex = database.createTreeSet("blocks_index_generator")
				.comparator(Fun.COMPARATOR)
				.makeOrGet();
		
		NavigableSet<Tuple2<String, Tuple2<String, String>>> descendingGeneratorIndex = database.createTreeSet("blocks_index_generator_descending")
				.comparator(new ReverseComparator(Fun.COMPARATOR))
				.makeOrGet();
		
		createIndex(GENERATOR_INDEX, generatorIndex, descendingGeneratorIndex, new Fun.Function2<String, Tuple2<String, String>, Block>() {
		   	@Override
		    public String run(Tuple2<String, String> key, Block value) {
		   		return key.a;
		    }
		});	
		
		//BALANCE INDEX
		NavigableSet<Tuple2<Long, Tuple2<String, String>>> balanceIndex = database.createTreeSet("blocks_index_balance")
				.comparator(Fun.COMPARATOR)
				.makeOrGet();
		
		NavigableSet<Tuple2<Long, Tuple2<String, String>>> descendingBalanceIndex = database.createTreeSet("blocks_index_balance_descending")
				.comparator(new ReverseComparator(Fun.COMPARATOR))
				.makeOrGet();
		
		createIndex(BALANCE_INDEX, balanceIndex, descendingBalanceIndex, new Fun.Function2<Long, Tuple2<String, String>, Block>() {
		   	@Override
		    public Long run(Tuple2<String, String> key, Block value) {
		   		return value.getGeneratingBalance();
		    }
		});
		
		//TRANSACTIONS INDEX
		NavigableSet<Tuple2<Integer, Tuple2<String, String>>> transactionsIndex = database.createTreeSet("blocks_index_transactions")
				.comparator(Fun.COMPARATOR)
				.makeOrGet();
		
		NavigableSet<Tuple2<Integer, Tuple2<String, String>>> descendingTransactionsIndex = database.createTreeSet("blocks_index_transactions_descending")
				.comparator(new ReverseComparator(Fun.COMPARATOR))
				.makeOrGet();
		
		createIndex(TRANSACTIONS_INDEX, transactionsIndex, descendingTransactionsIndex, new Fun.Function2<Integer, Tuple2<String, String>, Block>() {
		   	@Override
		    public Integer run(Tuple2<String, String> key, Block value) {
		   		return value.getTransactionCount();
		    }
		});
		
		//FEE INDEX
		NavigableSet<Tuple2<BigDecimal, Tuple2<String, String>>> feeIndex = database.createTreeSet("blocks_index_fee")
				.comparator(Fun.COMPARATOR)
				.makeOrGet();
		
		NavigableSet<Tuple2<BigDecimal, Tuple2<String, String>>> descendingFeeIndex = database.createTreeSet("blocks_index_fee_descending")
				.comparator(new ReverseComparator(Fun.COMPARATOR))
				.makeOrGet();
		
		createIndex(FEE_INDEX, feeIndex, descendingFeeIndex, new Fun.Function2<BigDecimal, Tuple2<String, String>, Block>() {
		   	@Override
		    public BigDecimal run(Tuple2<String, String> key, Block value) {
		   		return value.getTotalFee();
		    }
		});
	}

	@Override
	protected Map<Tuple2<String, String>, Block> getMap(DB database) 
	{
		//OPEN MAP
		return database.createTreeMap("blocks")
				.keySerializer(BTreeKeySerializer.TUPLE2)
				.valueSerializer(new BlockSerializer())
				.counterEnable()
				.makeOrGet();
	}

	@Override
	protected Map<Tuple2<String, String>, Block> getMemoryMap() 
	{
		return new TreeMap<Tuple2<String, String>, Block>(Fun.TUPLE2_COMPARATOR);
	}

	@Override
	protected Block getDefaultValue() 
	{
		return null;
	}
	
	@Override
	protected Map<Integer, Integer> getObservableData() 
	{
		return this.observableData;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public List<Block> get(Account account)
	{
		List<Block> blocks = new ArrayList<Block>();
		
		try
		{
			Map<Tuple2<String, String>, Block> accountBlocks = ((BTreeMap) this.map).subMap(
					Fun.t2(account.getAddress(), null),
					Fun.t2(account.getAddress(), Fun.HI()));
			
			//GET ITERATOR
			Iterator<Block> iterator = accountBlocks.values().iterator();
			
			while(iterator.hasNext())
			{
				blocks.add(iterator.next());
			}
		}
		catch(Exception e)
		{
			//ERROR
			e.printStackTrace();
		}
		
		return blocks;
	}
	
	public List<Pair<Account, Block>> get(List<Account> accounts)
	{
		List<Pair<Account, Block>> blocks = new ArrayList<Pair<Account, Block>>();
		
		try
		{
			//FOR EACH ACCOUNTS
			synchronized(accounts)
			{
				for(Account account: accounts)
				{
					List<Block> accountBlocks = get(account);
					for(Block block: accountBlocks)
					{
						blocks.add(new Pair<Account, Block>(account, block));
					}
				}
			}
		}
		catch(Exception e)
		{
			//ERROR
			e.printStackTrace();
		}
		
		return blocks;
	}
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public void delete(Account account)
	{
		//GET ALL TRANSACTIONS THAT BELONG TO THAT ADDRESS
		Map<Tuple2<String, String>, Block> accountBlocks = ((BTreeMap) this.map).subMap(
				Fun.t2(account.getAddress(), null),
				Fun.t2(account.getAddress(), Fun.HI()));
		
		//DELETE TRANSACTIONS
		for(Tuple2<String, String> key: accountBlocks.keySet())
		{
			this.delete(key);
		}
	}
	
	public void delete(Block block)
	{
		this.delete(new Tuple2<String, String>(block.getGenerator().getAddress(), new String(block.getSignature())));
	}
	
	public void deleteAll(List<Account> accounts)
	{
		for(Account account: accounts)
		{
			this.delete(account);
		}
	}
	
	public boolean add(Block block)
	{
		return this.set(new Tuple2<String, String>(block.getGenerator().getAddress(), new String(block.getSignature())), block);
	}
	
	public void addAll(Map<Account, List<Block>> blocks)
	{
		//FOR EACH ACCOUNT
	    for(Account account: blocks.keySet())
	    {
	    	//FOR EACH TRANSACTION
	    	for(Block block: blocks.get(account))
	    	{
	    		this.add(block);
	    	}
	    }
	}
}
