package api;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;

import controller.Controller;
import qora.account.Account;
import qora.block.Block;
import qora.crypto.Base58;
import qora.crypto.Crypto;
import qora.transaction.Transaction;
import utils.Pair;

@Path("transactions")
@Produces(MediaType.APPLICATION_JSON)
public class TransactionsResource {

	@GET
	public String getTransactions()
	{
		return this.getTransactionsLimited(50);
	}
	
	@GET
	@Path("/{address}")
	public String getTransactions(@PathParam("address") String address)
	{
		return this.getTransactionsLimited(address, 50);
	}
	
	@GET
	@Path("address/{address}")
	public String getTransactionsTwo(@PathParam("address") String address)
	{
		return this.getTransactions(address);
	}
	
	@SuppressWarnings("unchecked")
	@GET
	@Path("limit/{limit}")
	public String getTransactionsLimited(@PathParam("limit") int limit)
	{
		//CHECK IF WALLET EXISTS
		if(!Controller.getInstance().doesWalletExists())
		{
			throw ApiErrorFactory.getInstance().createError(ApiErrorFactory.ERROR_WALLET_NO_EXISTS);
		}
		
		//GET TRANSACTIONS
		List<Pair<Account, Transaction>> transactions = Controller.getInstance().getLastTransactions(limit);
		
		//ORGANIZE TRANSACTIONS
		Map<Account, List<Transaction>> orderedTransactions = new HashMap<Account, List<Transaction>>();
		for(Pair<Account, Transaction> transaction: transactions)
		{
			if(!orderedTransactions.containsKey(transaction.getA()))
			{
				orderedTransactions.put(transaction.getA(), new ArrayList<Transaction>());
			}
			
			orderedTransactions.get(transaction.getA()).add(transaction.getB());
		}
		
		//CREATE JSON OBJECT
		JSONArray orderedTransactionsJSON = new JSONArray();
		
		for(Account account: orderedTransactions.keySet())
		{
			JSONArray transactionsJSON = new JSONArray();
			for(Transaction transaction: orderedTransactions.get(account))
			{
				transactionsJSON.add(transaction.toJson());
			}
			
			JSONObject accountTransactionsJSON = new JSONObject();
			accountTransactionsJSON.put("account", account.getAddress());
			accountTransactionsJSON.put("transactions", transactionsJSON);
			orderedTransactionsJSON.add(accountTransactionsJSON);		
		}
		
		return orderedTransactionsJSON.toJSONString();
	}
	
	@SuppressWarnings("unchecked")
	@GET
	@Path("address/{address}/limit/{limit}")
	public String getTransactionsLimited(@PathParam("address") String address, @PathParam("limit") int limit)
	{
		//CHECK IF WALLET EXISTS
		if(!Controller.getInstance().doesWalletExists())
		{
			throw ApiErrorFactory.getInstance().createError(ApiErrorFactory.ERROR_WALLET_NO_EXISTS);
		}
		
		//CHECK ADDRESS
		if(!Crypto.getInstance().isValidAddress(address))
		{
			throw ApiErrorFactory.getInstance().createError(ApiErrorFactory.ERROR_INVALID_ADDRESS);
		}
		
		//CHECK ACCOUNT IN WALLET
		Account account = Controller.getInstance().getAccountByAddress(address);	
		if(account == null)
		{
			throw ApiErrorFactory.getInstance().createError(ApiErrorFactory.ERROR_WALLET_ADDRESS_NO_EXISTS);
		}
		
		JSONArray array = new JSONArray();
		for(Transaction transaction: Controller.getInstance().getLastTransactions(account, limit))
		{
			array.add(transaction.toJson());
		}
		
		return array.toJSONString();
	}
	
	@GET
	@Path("signature/{signature}")
	public String getTransactionsBySignature(@PathParam("signature") String signature) throws Exception
	{
		//DECODE SIGNATURE
		byte[] signatureBytes;
		try
		{
			signatureBytes = Base58.decode(signature);
		}
		catch(Exception e)
		{
			throw ApiErrorFactory.getInstance().createError(ApiErrorFactory.ERROR_INVALID_SIGNATURE);
		}
		
		//GET TRANSACTION
		Transaction transaction = Controller.getInstance().getTransaction(signatureBytes);
		
		//CHECK IF TRANSACTION EXISTS
		if(transaction == null)
		{
			throw ApiErrorFactory.getInstance().createError(ApiErrorFactory.ERROR_TRANSACTION_NO_EXISTS);
		}
		
		return transaction.toJson().toJSONString();
	}
	
	@SuppressWarnings("unchecked")
	@GET
	@Path("/network")
	public String getNetworkTransactions()
	{
		List<Transaction> transactions = Controller.getInstance().getUnconfirmedTransactions();
		JSONArray array = new JSONArray();
		
		for(Transaction transaction: transactions)
		{
			array.add(transaction.toJson());
		}
		
		return array.toJSONString();
	}
	
	@SuppressWarnings("unchecked")
	@POST
	@Path("/scan")
	public String scanTransactions(String x)
	{
		try
		{
			//READ JSON
			JSONObject jsonObject = (JSONObject) JSONValue.parse(x);
			
			//GET BLOCK
			Block block = null;
			if(jsonObject.containsKey("start"))
			{
				byte[] signatureBytes;
				try
				{
					String signature = (String) jsonObject.get("start");
					signatureBytes = Base58.decode(signature);
				}
				catch(Exception e)
				{
					throw ApiErrorFactory.getInstance().createError(ApiErrorFactory.ERROR_INVALID_SIGNATURE);
				}
						
				block = Controller.getInstance().getBlock(signatureBytes);
				
				//CHECK IF BLOCK EXISTS
				if(block == null)
				{
					throw ApiErrorFactory.getInstance().createError(ApiErrorFactory.ERROR_BLOCK_NO_EXISTS);
				}	
			}
			
			//CHECK FOR BLOCKLIMIT
			int blockLimit = -1;
			try
			{
				 blockLimit = ((Long) jsonObject.get("blocklimit")).intValue();
			}
			catch(NullPointerException e)
			{
				//OPTION DOES NOT EXIST
			}
			catch(ClassCastException e)
			{
				//JSON EXCEPTION
				throw ApiErrorFactory.getInstance().createError(ApiErrorFactory.ERROR_JSON);
			}
			
			//CHECK FOR TRANSACTIONLIMIT
			int transactionLimit = -1;
			try
			{
				transactionLimit = ((Long) jsonObject.get("transactionlimit")).intValue();
			}
			catch(NullPointerException e)
			{
				//OPTION DOES NOT EXIST
			}
			catch(ClassCastException e)
			{
				//JSON EXCEPTION
				throw ApiErrorFactory.getInstance().createError(ApiErrorFactory.ERROR_JSON);
			}
			
			//CHECK FOR TYPE
			int type = -1;
			try
			{
				 type = ((Long) jsonObject.get("type")).intValue();
			}
			catch(NullPointerException e)
			{
				//OPTION DOES NOT EXIST
			}
			catch(ClassCastException e)
			{
				//JSON EXCEPTION
				throw ApiErrorFactory.getInstance().createError(ApiErrorFactory.ERROR_JSON);
			}
			
			//CHECK FOR SERVICE
			int service = -1;
			try
			{
				 service = ((Long) jsonObject.get("service")).intValue();
			}
			catch(NullPointerException e)
			{
				//OPTION DOES NOT EXIST
			}
			catch(ClassCastException e)
			{
				//JSON EXCEPTION
				throw ApiErrorFactory.getInstance().createError(ApiErrorFactory.ERROR_JSON);
			}
			
			//CHECK FOR ACCOUNT
			Account account = null;
			try
			{
				if(jsonObject.containsKey("address"))
				{
					String address = (String) jsonObject.get("address");
					 
					//CHECK ADDRESS
					if(!Crypto.getInstance().isValidAddress(address))
					{
						throw ApiErrorFactory.getInstance().createError(ApiErrorFactory.ERROR_INVALID_ADDRESS);
					}
					
					account = new Account(address);
				}
				 
			}
			catch(NullPointerException e)
			{
				//OPTION DOES NOT EXIST
			}
			catch(ClassCastException e)
			{
				//JSON EXCEPTION
				throw ApiErrorFactory.getInstance().createError(ApiErrorFactory.ERROR_JSON);
			}
			
			//SCAN
			Pair<Block, List<Transaction>> result = Controller.getInstance().scanTransactions(block, blockLimit, transactionLimit, type, service, account);
			
			//CONVERT RESULT TO JSON
			JSONObject json = new JSONObject();
			
			json.put("lastscanned", Base58.encode(result.getA().getSignature()));
			
			
			if(block != null)
			{
				json.put("amount", result.getA().getHeight() - block.getHeight() + 1);
			}
			else
			{
				json.put("amount", result.getA().getHeight());
			}
			
			JSONArray transactions = new JSONArray();
			for(Transaction transaction: result.getB())
			{
				transactions.add(transaction.toJson());
			}
			json.put("transactions", transactions);
			
			//RETURN
			return json.toJSONString();
		}
		catch(NullPointerException e)
		{
			//JSON EXCEPTION
			e.printStackTrace();
			throw ApiErrorFactory.getInstance().createError(ApiErrorFactory.ERROR_JSON);
		}
		catch(ClassCastException e)
		{
			//JSON EXCEPTION
			e.printStackTrace();
			throw ApiErrorFactory.getInstance().createError(ApiErrorFactory.ERROR_JSON);
		}
	}
}