package qora.crypto;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import qora.account.Account;
import qora.account.PrivateKeyAccount;
import utils.Pair;

import com.google.common.primitives.Bytes;

public class Crypto {

	public static final byte ADDRESS_VERSION = 58;
	
	private static Crypto instance;
	
	public static Crypto getInstance()
	{
		if(instance == null)
		{
			instance = new Crypto();
		}
		
		return instance;
	}
	
	private Crypto()
	{
		
	}
	
	public byte[] digest(byte[] input)
	{
		try 
		{
			//SHA256
			MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
			return sha256.digest(input);
		}
		catch (NoSuchAlgorithmException e)
		{
			return null;
		}
	}
	
	public byte[] doubleDigest(byte[] input)
	{
		//DOUBLE SHA256
		return this.digest(this.digest(input));
	}
	
	public Pair<byte[], byte[]> createKeyPair(byte[] seed)
	{
		try
		{
			//GENERATE PUBLIC KEY
			return Ed25519.createKeyPair(seed);
		} 
		catch (Exception e) 
		{
			e.printStackTrace();
			return null;
		}
	}
	
	public String getAddress(byte[] publicKey)
	{
		//SHA256 PUBLICKEY FOR PROTECTION
		byte[] publicKeyHash = this.digest(publicKey);
		
		//RIPEMD160 TO CREATE A SHORTER ADDRESS
		RIPEMD160 ripEmd160 = new RIPEMD160();
		publicKeyHash = ripEmd160.digest(publicKeyHash);
		
		//CONVERT TO LIST
		List<Byte> addressList = new ArrayList<Byte>();
		
		//ADD VERSION BYTE
		Byte versionByte = Byte.valueOf(ADDRESS_VERSION);
		addressList.add(versionByte);
		
		addressList.addAll(Bytes.asList(publicKeyHash));
		
		//GENERATE CHECKSUM
		byte[] checkSum = this.doubleDigest(Bytes.toArray(addressList));
		
		//ADD FIRST 4 BYTES OF CHECKSUM TO ADDRESS
		addressList.add(checkSum[0]);
		addressList.add(checkSum[1]);
		addressList.add(checkSum[2]);
		addressList.add(checkSum[3]);
		
		//BASE58 ENCODE ADDRESS
		String address = Base58.encode(Bytes.toArray(addressList));
		
		return address;
	}
	
	public boolean isValidAddress(String address)
	{
		try
		{
			//BASE 58 DECODE
			byte[] addressBytes = Base58.decode(address);
			
			//CHECK BYTES
			if(addressBytes.length != Account.ADDRESS_LENGTH)
			{
				return false;
			}
			
			//CHECK VERSION
			if(addressBytes[0] != ADDRESS_VERSION)
			{
				return false;
			}
			
			//CONVERT TO LIST
			List<Byte> addressList = new ArrayList<Byte>();
			addressList.addAll(Bytes.asList(addressBytes));
			
			//REMOVE CHECKSUM
			byte[] checkSum = new byte[4];
			checkSum[3] = addressList.remove(addressList.size() - 1);
			checkSum[2] = addressList.remove(addressList.size() - 1);
			checkSum[1] = addressList.remove(addressList.size() - 1);
			checkSum[0] = addressList.remove(addressList.size() - 1);
			
			//GENERATE ADDRESS CHECKSUM
			byte[] digest = this.doubleDigest(Bytes.toArray(addressList));
			byte[] checkSumTwo = new byte[4];
			checkSumTwo[0] = digest[0];
			checkSumTwo[1] = digest[1];
			checkSumTwo[2] = digest[2];
			checkSumTwo[3] = digest[3];
			
			//CHECK IF CHECKSUMS ARE THE SAME
			return Arrays.equals(checkSum, checkSumTwo);
		}
		catch(Exception e)
		{
			//ERROR DECODING
			return false;
		}
	}
	
	public byte[] sign(PrivateKeyAccount account, byte[] message)
	{	
		try
		{
			//GET SIGNATURE
			return Ed25519.sign(account.getKeyPair(), message);
		}
		catch (Exception e) 
		{
			e.printStackTrace();
			return new byte[64];
		}
	}
	
	public boolean verify(byte[] publicKey, byte[] signature, byte[] message)
	{
		try 
		{
			//VERIFY SIGNATURE
			return Ed25519.verify(signature, message, publicKey);	
		}
		catch(Exception e)
		{
			e.printStackTrace();
			return false;
		}
	}
	
}
