package test;

import static org.junit.Assert.*;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import ntp.NTP;

import org.junit.Test;

import qora.BlockGenerator;
import qora.Synchronizer;
import qora.account.PrivateKeyAccount;
import qora.block.Block;
import qora.block.GenesisBlock;
import qora.crypto.Crypto;
import qora.crypto.Ed25519;
import qora.transaction.GenesisTransaction;
import qora.transaction.Transaction;
import database.DBSet;

public class SynchronizerTests {

	@Test
	public void synchronizeNoCommonBlock()
	{
		Ed25519.load();
		
		//GENERATE 5 BLOCKS FROM ACCOUNT 1
		DBSet databaseSet = DBSet.createEmptyDatabaseSet();
		
		//PROCESS GENESISBLOCK
		GenesisBlock genesisBlock = new GenesisBlock();
		genesisBlock.process(databaseSet);
		
		//CREATE KNOWN ACCOUNT
		byte[] seed = Crypto.getInstance().digest("test".getBytes());
		byte[] privateKey = Crypto.getInstance().createKeyPair(seed).getA();
		PrivateKeyAccount generator = new PrivateKeyAccount(privateKey);
				
		//PROCESS GENESIS TRANSACTION TO MAKE SURE GENERATOR HAS FUNDS
		Transaction transaction = new GenesisTransaction(generator, BigDecimal.valueOf(1000).setScale(8), NTP.getTime());
		transaction.process(databaseSet);
		
		//GENERATE 5 NEXT BLOCKS
		Block lastBlock = genesisBlock;
		BlockGenerator blockGenerator = new BlockGenerator();
		List<Block> firstBlocks = new ArrayList<Block>();
		for(int i=0; i<5; i++)
		{	
			//GENERATE NEXT BLOCK
			Block newBlock = blockGenerator.generateNextBlock(databaseSet, generator, lastBlock);
			
			//ADD TRANSACTION SIGNATURE
			byte[] transactionsSignature = Crypto.getInstance().sign(generator, newBlock.getGeneratorSignature());
			newBlock.setTransactionsSignature(transactionsSignature);
			
			//PROCESS NEW BLOCK
			newBlock.process(databaseSet);
			
			//ADD TO LIST
			firstBlocks.add(newBlock);
			
			//LAST BLOCK IS NEW BLOCK
			lastBlock = newBlock;
		}

		//GENERATE NEXT 5 BLOCK FROM ACCOUNT 2 ON FORK
		seed = Crypto.getInstance().digest("test2".getBytes());
		privateKey = Crypto.getInstance().createKeyPair(seed).getA();
		generator = new PrivateKeyAccount(privateKey);
						
		//PROCESS GENESIS TRANSACTION TO MAKE SURE GENERATOR HAS FUNDS
		transaction = new GenesisTransaction(generator, BigDecimal.valueOf(1000).setScale(8), NTP.getTime());
		transaction.process(databaseSet);
		
		//FORK
		DBSet fork = databaseSet.fork();	
		
		//GENERATE NEXT 5 BLOCKS
		List<Block> newBlocks = new ArrayList<Block>();
		for(int i=0; i<5; i++)
		{	
			//GENERATE NEXT BLOCK
			Block newBlock = blockGenerator.generateNextBlock(fork, generator, lastBlock);
			
			//ADD TRANSACTION SIGNATURE
			byte[] transactionsSignature = Crypto.getInstance().sign(generator, newBlock.getGeneratorSignature());
			newBlock.setTransactionsSignature(transactionsSignature);
			
			//PROCESS NEW BLOCK
			newBlock.process(fork);
			
			//ADD TO LIST
			newBlocks.add(newBlock);
			
			//LAST BLOCK IS NEW BLOCK
			lastBlock = newBlock;
		}		
		
		//SYNCHRONIZE DB FROM ACCOUNT 1 WITH NEXT 5 BLOCKS OF ACCOUNT 2
		Synchronizer synchronizer = new Synchronizer();
		
		try
		{
			synchronizer.synchronize(databaseSet, null, newBlocks);
			
			//CHECK LAST 5 BLOCKS
			lastBlock = databaseSet.getBlockMap().getLastBlock();
			for(int i=4; i>=0; i--)
			{
				//CHECK LAST BLOCK
				assertEquals(true, Arrays.equals(newBlocks.get(i).getSignature(), lastBlock.getSignature()));
				lastBlock = lastBlock.getParent(databaseSet);
			}
			
			//CHECK LAST 5 BLOCKS
			for(int i=4; i>=0; i--)
			{
				//CHECK LAST BLOCK
				assertEquals(true, Arrays.equals(firstBlocks.get(i).getSignature(), lastBlock.getSignature()));
				lastBlock = lastBlock.getParent(databaseSet);
			}
			
			//CHECK LAST BLOCK
			assertEquals(true, Arrays.equals(lastBlock.getSignature(), genesisBlock.getSignature()));
			
			//CHECK HEIGHT
			assertEquals(11, databaseSet.getBlockMap().getLastBlock().getHeight(databaseSet));
		}
		catch(Exception e)
		{
			fail("Exception during synchronize");
		}	
	}
	
	@Test
	public void synchronizeCommonBlock()
	{
		Ed25519.load();
		
		//GENERATE 5 BLOCKS FROM ACCOUNT 1
		DBSet databaseSet = DBSet.createEmptyDatabaseSet();
		DBSet databaseSet2 = DBSet.createEmptyDatabaseSet();
		
		//PROCESS GENESISBLOCK
		GenesisBlock genesisBlock = new GenesisBlock();
		genesisBlock.process(databaseSet);
		genesisBlock.process(databaseSet2);
		
		//CREATE KNOWN ACCOUNT
		byte[] seed = Crypto.getInstance().digest("test".getBytes());
		byte[] privateKey = Crypto.getInstance().createKeyPair(seed).getA();
		PrivateKeyAccount generator = new PrivateKeyAccount(privateKey);
				
		//PROCESS GENESIS TRANSACTION TO MAKE SURE GENERATOR HAS FUNDS
		Transaction transaction = new GenesisTransaction(generator, BigDecimal.valueOf(1000).setScale(8), NTP.getTime());
		transaction.process(databaseSet);
		transaction.process(databaseSet2);
		
		//CREATE KNOWN ACCOUNT 2
		byte[] seed2 = Crypto.getInstance().digest("test2".getBytes());
		byte[] privateKey2 = Crypto.getInstance().createKeyPair(seed2).getA();
		PrivateKeyAccount generator2 = new PrivateKeyAccount(privateKey2);
		
		//PROCESS GENESIS TRANSACTION TO MAKE SURE GENERATOR2 HAS FUNDS
		transaction = new GenesisTransaction(generator2, BigDecimal.valueOf(1000).setScale(8), NTP.getTime());
		transaction.process(databaseSet);
		transaction.process(databaseSet2);
		
		//GENERATE 5 NEXT BLOCKS
		Block lastBlock = genesisBlock;
		BlockGenerator blockGenerator = new BlockGenerator();
		for(int i=0; i<5; i++)
		{	
			//GENERATE NEXT BLOCK
			Block newBlock = blockGenerator.generateNextBlock(databaseSet, generator, lastBlock);
			
			//ADD TRANSACTION SIGNATURE
			byte[] transactionsSignature = Crypto.getInstance().sign(generator, newBlock.getGeneratorSignature());
			newBlock.setTransactionsSignature(transactionsSignature);
			
			//PROCESS NEW BLOCK
			newBlock.process(databaseSet);
			
			//LAST BLOCK IS NEW BLOCK
			lastBlock = newBlock;
		}

		//GENERATE NEXT 10 BLOCKS
		lastBlock = genesisBlock;
		List<Block> newBlocks = new ArrayList<Block>();
		for(int i=0; i<10; i++)
		{	
			//GENERATE NEXT BLOCK
			Block newBlock = blockGenerator.generateNextBlock(databaseSet2, generator2, lastBlock);
			
			//ADD TRANSACTION SIGNATURE
			byte[] transactionsSignature = Crypto.getInstance().sign(generator2, newBlock.getGeneratorSignature());
			newBlock.setTransactionsSignature(transactionsSignature);
			
			//PROCESS NEW BLOCK
			newBlock.process(databaseSet2);
			
			//ADD TO LIST
			newBlocks.add(newBlock);
			
			//LAST BLOCK IS NEW BLOCK
			lastBlock = newBlock;
		}		
		
		//SYNCHRONIZE DB FROM ACCOUNT 1 WITH NEXT 5 BLOCKS OF ACCOUNT 2
		Synchronizer synchronizer = new Synchronizer();
		
		try
		{
			synchronizer.synchronize(databaseSet, genesisBlock, newBlocks);
			
			//CHECK BLOCKS
			lastBlock = databaseSet.getBlockMap().getLastBlock();
			for(int i=9; i>=0; i--)
			{
				//CHECK LAST BLOCK
				assertEquals(true, Arrays.equals(newBlocks.get(i).getSignature(), lastBlock.getSignature()));
				lastBlock = lastBlock.getParent(databaseSet);
			}
			
			//CHECK LAST BLOCK
			assertEquals(true, Arrays.equals(lastBlock.getSignature(), genesisBlock.getSignature()));
			
			//CHECK HEIGHT
			assertEquals(11, databaseSet.getBlockMap().getLastBlock().getHeight(databaseSet));
		}
		catch(Exception e)
		{
			e.printStackTrace();
			fail("Exception during synchronize");
		}	
	}	
}
