package network.message;

import java.io.DataInputStream;
import java.util.Arrays;
import java.util.List;

import qora.block.Block;
import qora.crypto.Crypto;
import qora.transaction.Transaction;

import com.google.common.primitives.Ints;

import network.Peer;

public class MessageFactory {

	private static MessageFactory instance;
	
	public static MessageFactory getInstance()
	{
		if(instance == null)
		{
			instance = new MessageFactory();
		}
		
		return instance;
	}
	
	public Message createPingMessage()
	{
		//CREATE A MESSAGE WITH ping ACTION
		return new Message(Message.PING_TYPE);
	}
	
	public Message createGetPeersMessage()
	{	
		//CREATE A MESSAGE WITH getPeers ACTION
		return new Message(Message.GET_PEERS_TYPE);
	}
	
	public Message createPeersMessage(List<Peer> peers)
	{
		return new PeersMessage(peers);
	}
	
	public Message createVersionMessage(int height)
	{
		return new VersionMessage(height);
	}
	
	public Message createGetHeadersMessage(byte[] parent)
	{
		return new GetSignaturesMessage(parent);
	}
	
	public Message createHeadersMessage(List<byte[]> headers)
	{
		return new SignaturesMessage(headers);
	}
	
	public Message createGetBlockMessage(byte[] header)
	{
		return new GetBlockMessage(header);
	}
	
	public Message createBlockMessage(Block block)
	{
		return new BlockMessage(block);
	}
	
	public Message createTransactionMessage(Transaction transaction)
	{
		return new TransactionMessage(transaction);
	}
	
	public Message parse(Peer sender, DataInputStream inputStream) throws Exception
	{
		//READ MESSAGE TYPE
		byte[] typeBytes = new byte[Message.TYPE_LENGTH];
		inputStream.readFully(typeBytes);
		int type = Ints.fromByteArray(typeBytes);
		
		//READ HAS ID
		int hasId = inputStream.read();
		int id = -1;
		
		if(hasId == 1)
		{
			//READ ID
			byte[] idBytes = new byte[Message.ID_LENGTH];
			inputStream.readFully(idBytes);
			id = Ints.fromByteArray(idBytes);
		}
		
		//READ LENGTH
		//byte[] lengthBytes = new byte[Message.MESSAGE_LENGTH];
		//inputStream.readFully(lengthBytes);
		int length = inputStream.readInt();//Ints.fromByteArray(lengthBytes);		
		
		//IF MESSAGE CONTAINS DATA READ DATA AND VALIDATE CHECKSUM
		byte[] data = new byte[length];
		if(length > 0)
		{
			//READ CHECKSUM
			byte[] checksum = new byte[Message.CHECKSUM_LENGTH];
			inputStream.readFully(checksum);
			
			//READ DATA
			inputStream.readFully(data);
			
			/*int position = 0;
			while(position < length)
			{
				//READ MULTIPLE TIMES BECAUSE OF MAX READ OF 65536 BYTES
				
				//TODO CHECK THIS FIXES PROBLEM
				//position += inputStream.read(data, position, length - position);
				int temp = inputStream.read(data, position, length - position);
				position += temp;
				
				if(temp == -1)
				{
					boolean tempa = true;
					tempa = false;
					if(tempa);
				}
			}*/
			
			//VALIDATE CHECKSUM
			byte[] digest = Crypto.getInstance().digest(data);
			
			//TAKE FOR FIRST BYTES
			digest = Arrays.copyOfRange(digest, 0, Message.CHECKSUM_LENGTH);
			
			//CHECK IF CHECKSUM MATCHES
			if(!Arrays.equals(checksum, digest))
			{
				throw new Exception("Invalid data checksum length="+length);
			}
		}
		
		Message message = null;
			
		switch(type)
		{
		//PING
		case Message.PING_TYPE:
			
			message = new Message(type);
			break;
		
		//GETPEERS
		case Message.GET_PEERS_TYPE:
						
			message = new Message(type);
			break;
			
		//PEERS
		case Message.PEERS_TYPE:
				
			//CREATE MESSAGE FROM DATA
			message = PeersMessage.parse(data);
			break;
				
		//VERSION
		case Message.VERSION_TYPE:
							
			//CREATE MESSAGE FROM DATA
			message = VersionMessage.parse(data);
			break;
			
		//GETSIGNATURES
		case Message.GET_SIGNATURES_TYPE:
				
			//CREATE MESSAGE FROM DATA
			message = GetSignaturesMessage.parse(data);
			break;
				
		//SIGNATURES
		case Message.SIGNATURES_TYPE:
						
			//CREATE MESSAGE FROM DATA
			message = SignaturesMessage.parse(data);
			break;
		
		//GETBLOCK
		case Message.GET_BLOCK_TYPE:
								
			//CREATE MESSAGE FROM DATA
			message = GetBlockMessage.parse(data);
			break;	
				
		//BLOCK
		case Message.BLOCK_TYPE:
												
			//CREATE MESSAGE FROM DATA
			message = BlockMessage.parse(data);
			break;		
			
		//TRANSACTION
		case Message.TRANSACTION_TYPE:
			
			//CREATE MESSAGE FRO MDATA
			message = TransactionMessage.parse(data);
			break;
		}
			
		//SET SENDER
		message.setSender(sender);	
			
		//SET ID
		if(hasId == 1)
		{
			message.setId(id);
		}
			
		//RETURN
		return message;
	}
}
