 package qora.payment;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;

import org.json.simple.JSONObject;

import com.google.common.primitives.Bytes;
import com.google.common.primitives.Longs;

import database.DBSet;
import qora.account.Account;
import qora.account.PublicKeyAccount;
import qora.crypto.Base58;

public class Payment {

	private static final int RECIPIENT_LENGTH = 25;
	private static final int ASSET_LENGTH = 8;
	private static final int AMOUNT_LENGTH = 12;
	public static final int BASE_LENGTH = RECIPIENT_LENGTH + ASSET_LENGTH + AMOUNT_LENGTH;
	
	private Account recipient;
	private long asset;
	private BigDecimal amount;
	
	public Payment(Account recipient, long asset, BigDecimal amount)
	{
		this.recipient = recipient;
		this.asset = asset;
		this.amount = amount;
	}
	
	//GETTERS/SETTERS
	
	public Account getRecipient()
	{
		return this.recipient;
	}
	
	public long getAsset()
	{
		return this.asset;
	}
	
	public BigDecimal getAmount()
	{
		return this.amount;
	}
	
	//PARSE
	
	public static Payment parse(byte[] data) throws Exception
	{	
		int position = 0;
		
		//READ RECIPIENT
		byte[] recipientBytes = Arrays.copyOfRange(data, position, position + RECIPIENT_LENGTH);
		Account recipient = new Account(Base58.encode(recipientBytes));
		position += RECIPIENT_LENGTH;
		
		//READ ASSET
		byte[] assetBytes = Arrays.copyOfRange(data, position, position + ASSET_LENGTH);
		long asset = Longs.fromByteArray(assetBytes);	
		position += ASSET_LENGTH;
		
		//READ AMOUNT
		byte[] amountBytes = Arrays.copyOfRange(data, position, position + AMOUNT_LENGTH);
		BigDecimal amount = new BigDecimal(new BigInteger(amountBytes), 8);
		position += AMOUNT_LENGTH;
		
		return new Payment(recipient, asset, amount);
	}
	
	@SuppressWarnings("unchecked")
	public JSONObject toJson() 
	{
		//GET BASE
		JSONObject payment = new JSONObject();
								
		//ADD RECIPIENT/ASSET/AMOUNT
		payment.put("recipient", this.recipient);
		payment.put("asset", this.asset);
		payment.put("amount", this.amount.toPlainString());
								
		return payment;	
	}
	
	public byte[] toBytes()
	{
		byte[] data = new byte[0];
		
		//WRITE RECIPIENT
		data = Bytes.concat(data, Base58.decode(this.recipient.getAddress()));
		
		//WRITE ASSET
		byte[] assetBytes = Longs.toByteArray(this.asset);
		assetBytes = Bytes.ensureCapacity(assetBytes, ASSET_LENGTH, 0);
		data = Bytes.concat(data, assetBytes);
		
		//WRITE AMOUNT
		byte[] amountBytes = this.amount.unscaledValue().toByteArray();
		byte[] fill = new byte[AMOUNT_LENGTH - amountBytes.length];
		amountBytes = Bytes.concat(fill, amountBytes);
		data = Bytes.concat(data, amountBytes);
		
		return data;
	}
	
	public int getDataLength() 
	{
		return BASE_LENGTH;
	}
	
	//PROCESS/ORPHAN

	public void process(PublicKeyAccount sender, DBSet db) 
	{
		//UPDATE SENDER
		sender.setConfirmedBalance(this.asset, sender.getConfirmedBalance(this.asset, db).subtract(this.amount), db);
						
		//UPDATE RECIPIENT
		this.recipient.setConfirmedBalance(this.asset, this.recipient.getConfirmedBalance(this.asset, db).add(this.amount), db);	
	}	
	
	public void orphan(PublicKeyAccount sender, DBSet db) 
	{
		//UPDATE SENDER
		sender.setConfirmedBalance(this.asset, sender.getConfirmedBalance(this.asset, db).add(this.amount), db);
						
		//UPDATE RECIPIENT
		this.recipient.setConfirmedBalance(this.asset, this.recipient.getConfirmedBalance(this.asset, db).subtract(this.amount), db);	
	}
}
