package qora.wallet;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.logging.Logger;

import org.mapdb.Fun.Tuple2;

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

import controller.Controller;
import database.DBSet;
import database.wallet.SecureWalletDatabase;
import database.wallet.WalletDatabase;
import qora.account.Account;
import qora.account.PrivateKeyAccount;
import qora.assets.Asset;
import qora.assets.Order;
import qora.block.Block;
import qora.block.GenesisBlock;
import qora.crypto.Crypto;
import qora.naming.Name;
import qora.naming.NameSale;
import qora.transaction.BuyNameTransaction;
import qora.transaction.CancelOrderTransaction;
import qora.transaction.CancelSellNameTransaction;
import qora.transaction.CreateOrderTransaction;
import qora.transaction.CreatePollTransaction;
import qora.transaction.IssueAssetTransaction;
import qora.transaction.RegisterNameTransaction;
import qora.transaction.SellNameTransaction;
import qora.transaction.Transaction;
import qora.transaction.UpdateNameTransaction;
import qora.transaction.VoteOnPollTransaction;
import qora.voting.Poll;
import utils.ObserverMessage;
import utils.Pair;

public class Wallet extends Observable implements Observer
{
	public static final int STATUS_UNLOCKED = 1;
	public static final int STATUS_LOCKED = 0;
	
	private WalletDatabase database;
	private SecureWalletDatabase secureDatabase;
	
	//CONSTRUCTORS
	
	public Wallet()
	{
		//CHECK IF EXISTS
		if(this.exists())
		{
			//OPEN WALLET
			this.database = new WalletDatabase();
			
			//ADD OBSERVER
		    Controller.getInstance().addObserver(this);
		    DBSet.getInstance().getCompletedOrderMap().addObserver(this);
		}
	}
	
	//GETTERS/SETTERS
	
	public int getVersion()
	{
		return this.database.getVersion();
	}
	
	public boolean isUnlocked()
	{
		return this.secureDatabase != null;
	}
	
	public List<Account> getAccounts()
	{
		return this.database.getAccountMap().getAccounts();
	}
	
	public boolean accountExists(String address)
	{
		return this.database.getAccountMap().exists(address);
	}
	
	public Account getAccount(String address)
	{
		return this.database.getAccountMap().getAccount(address);
	}
	
	public BigDecimal getUnconfirmedBalance(String address)
	{
		return this.database.getAccountMap().getUnconfirmedBalance(address);
	}
	
	public List<PrivateKeyAccount> getprivateKeyAccounts()
	{
		if(this.secureDatabase == null)
		{
			return new ArrayList<PrivateKeyAccount>();
		}
		
		return this.secureDatabase.getAccountSeedMap().getPrivateKeyAccounts();
	}
	
	public PrivateKeyAccount getPrivateKeyAccount(String address)
	{
		if(this.secureDatabase == null)
		{
			return null;
		}
		
		return this.secureDatabase.getAccountSeedMap().getPrivateKeyAccount(address);
	}
	
	public boolean exists()
	{
		return WalletDatabase.exists();
	}
	
	public List<Pair<Account, Transaction>> getLastTransactions(int limit)
	{
		if(!this.exists())
		{
			new ArrayList<Pair<Account, Transaction>>();
		}

		List<Account> accounts = this.getAccounts();
		return this.database.getTransactionMap().get(accounts, limit);
	}
	
	public List<Transaction> getLastTransactions(Account account, int limit)
	{
		if(!this.exists())
		{
			return new ArrayList<Transaction>();
		}

		return this.database.getTransactionMap().get(account, limit);
	}
	
	public List<Pair<Account, Block>> getLastBlocks()
	{
		if(!this.exists())
		{
			return new ArrayList<Pair<Account, Block>>();
		}

		List<Account> accounts = this.getAccounts();
		return this.database.getBlockMap().get(accounts);
	}

	public List<Block> getLastBlocks(Account account)
	{
		if(!this.exists())
		{
			return new ArrayList<Block>();
		}

		return this.database.getBlockMap().get(account);
	}
		
	public List<Pair<Account, Name>> getNames()
	{
		if(!this.exists())
		{
			return new ArrayList<Pair<Account, Name>>();
		}

		List<Account> accounts = this.getAccounts();
		return this.database.getNameMap().get(accounts);
	}
	
	public List<Name> getNames(Account account)
	{
		if(!this.exists())
		{
			return new ArrayList<Name>();
		}

		return this.database.getNameMap().get(account);
	}
	
	public List<Pair<Account, NameSale>> getNameSales()
	{
		if(!this.exists())
		{
			return new ArrayList<Pair<Account, NameSale>>();
		}

		List<Account> accounts = this.getAccounts();
		return this.database.getNameSaleMap().get(accounts);
	}
	
	public List<NameSale> getNameSales(Account account)
	{
		if(!this.exists())
		{
			return new ArrayList<NameSale>();
		}

		return this.database.getNameSaleMap().get(account);
	}
	
	public List<Pair<Account, Poll>> getPolls()
	{
		if(!this.exists())
		{
			return new ArrayList<Pair<Account, Poll>>();
		}

		List<Account> accounts = this.getAccounts();
		return this.database.getPollMap().get(accounts);
	}
	
	public List<Poll> getPolls(Account account)
	{
		if(!this.exists())
		{
			return new ArrayList<Poll>();
		}

		return this.database.getPollMap().get(account);
	}
	
	public void addAssetFavorite(Asset asset)
	{
		if(!this.exists())
		{
			return;
		}
		
		this.database.getAssetFavoritesSet().add(asset.getKey());
	}
	
	public void removeAssetFavorite(Asset asset)
	{
		if(!this.exists())
		{
			return;
		}
		
		this.database.getAssetFavoritesSet().delete(asset.getKey());
	}
	
	public boolean isAssetFavorite(Asset asset)
	{
		if(!this.exists())
		{
			return false;
		}
		
		return this.database.getAssetFavoritesSet().contains(asset.getKey());
	}
	
	//CREATE
	
	public boolean create(byte[] seed, String password, int depth, boolean synchronize)
	{
		//OPEN WALLET
		WalletDatabase database = new WalletDatabase();
		
	    //OPEN SECURE WALLET
		SecureWalletDatabase secureDatabase = new SecureWalletDatabase(password);
	    
	    //CREATE
	    return this.create(database, secureDatabase, seed, depth, synchronize);
	}
	
	public boolean create(WalletDatabase database, SecureWalletDatabase secureDatabase, byte[] seed, int depth, boolean synchronize)
	{
		//CREATE WALLET
		this.database = database;
	    
	    //CREATE SECURE WALLET
	    this.secureDatabase = secureDatabase;
	    
	    //ADD VERSION
	    this.database.setVersion(1);
	    
	    //ADD SEED
	    this.secureDatabase.setSeed(seed);
	    
	    //ADD NONCE
	    this.secureDatabase.setNonce(0);
	    
	    //CREATE ACCOUNTS
	    for(int i=1; i<=depth; i++)
	    {
	    	this.generateNewAccount();
	    }
	    
	    //SCAN TRANSACTIONS
	    if(synchronize)
	    {
	    	this.synchronize();
	    }
	    
	    //COMMIT
	    this.commit();
	    
	    //ADD OBSERVER
	    Controller.getInstance().addObserver(this);
	    DBSet.getInstance().getCompletedOrderMap().addObserver(this);
	    
	    return true;
	}
	
	public String generateNewAccount() 
	{
		//CHECK IF WALLET IS OPEN
		if(!this.isUnlocked())
		{
			return "";
		}
	    
	    //READ SEED
	    byte[] seed = this.secureDatabase.getSeed();
	    
	    //READ NONCE
	    int nonce = this.secureDatabase.getAndIncrementNonce();
	    
	    //GENERATE ACCOUNT SEED
	    byte[] accountSeed = this.generateAccountSeed(seed, nonce);
	    PrivateKeyAccount account = new PrivateKeyAccount(accountSeed);
	    
	    //CHECK IF ACCOUNT ALREADY EXISTS
	    if(!this.accountExists(account.getAddress()))
	    {	    
	    	//ADD TO DATABASE
		    this.secureDatabase.getAccountSeedMap().add(account);
		    this.database.getAccountMap().add(account);
		    Logger.getGlobal().info("Added account #" + nonce);
		    
		    this.secureDatabase.commit();
		    this.database.commit();
		    
		    //NOTIFY
		    this.setChanged();
		    this.notifyObservers(new ObserverMessage(ObserverMessage.ADD_ACCOUNT_TYPE, account));
	    }
	    
	    return account.getAddress();
	}
	
	private byte[] generateAccountSeed(byte[] seed, int nonce) 
	{		
		byte[] nonceBytes = Ints.toByteArray(nonce);
		byte[] accountSeed = Bytes.concat(nonceBytes, seed, nonceBytes);
		return Crypto.getInstance().doubleDigest(accountSeed);		
	}
	
	//DELETE
	
	public boolean deleteAccount(PrivateKeyAccount account)
	{
		//CHECK IF WALLET IS OPEN
		if(!this.isUnlocked())
		{
			return false;
		}
		
		//DELETE FROM DATABASE
		this.database.delete(account);
		this.secureDatabase.delete(account);
		
		//NOTIFY
	    this.setChanged();
	    this.notifyObservers(new ObserverMessage(ObserverMessage.REMOVE_ACCOUNT_TYPE, account));
	    
	    //RETURN
	    return true;
	}
	
	//SYNCRHONIZE
	
	public void synchronize()
	{
		List<Account> accounts = this.getAccounts();
		
		//RESET MAPS
		this.database.getTransactionMap().reset();
		this.database.getBlockMap().reset();
		this.database.getNameMap().reset();
		this.database.getNameSaleMap().reset();
		this.database.getPollMap().reset();
		this.database.getAssetMap().reset();
		this.database.getOrderMap().reset();
		Logger.getGlobal().info("Resetted maps");
		
		//REPROCESS BLOCKS
		Block block = new GenesisBlock();
		this.database.setLastBlockSignature(new byte[]{1,1,1,1,1,1,1,1});
		do
		{
			//UPDATE
			this.update(this, new ObserverMessage(ObserverMessage.ADD_BLOCK_TYPE, block));
			
			if(block.getHeight() % 2000 == 0) 
			{
				Logger.getGlobal().info("Synchronize wallet: " + block.getHeight());
				this.database.commit();
			}
			
			//LOAD NEXT
			block = block.getChild();
		}
		while(block != null);
		
		//RESET UNCONFIRMED BALANCE
		synchronized(accounts)
		{
			for(Account account: accounts)
			{
				this.database.getAccountMap().update(account, account.getConfirmedBalance());
			}
		}
		Logger.getGlobal().info("Resetted balances");
		
		//SET LAST BLOCK
		
		/*//SCAN TRANSACTIONS
		Map<Account, List<Transaction>> transactions;
		synchronized(accounts)
		{
			transactions = Controller.getInstance().scanTransactions(accounts);
		}
		
		//DELETE TRANSACTIONS
		this.database.getTransactionMap().deleteAll(accounts);
		
		//ADD TRANSACTIONS
		this.database.getTransactionMap().addAll(transactions);
	    	
		//TODO SCAN UNCONFIRMED TRANSACTIONS    
	    	    
	    //SCAN BLOCKS
	    Map<Account, List<Block>> blocks;
	    synchronized(accounts)
		{
	    	blocks = Controller.getInstance().scanBlocks(accounts);
		}
	    
	    //DELETE BLOCKS
	  	this.database.getBlockMap().deleteAll(accounts);
	  	
	  	//ADD BLOCKS
	  	this.database.getBlockMap().addAll(blocks);
	    
	    //SCAN NAMES
	    Map<Account, List<Name>> names;
	    synchronized(accounts)
		{
	    	names = Controller.getInstance().scanNames(accounts);
		}
	    
	    //DELETE NAMES
	  	this.database.getNameMap().deleteAll(accounts);
	  	
	  	//ADD NAMES
	  	this.database.getNameMap().addAll(names);
	  	
	  	//TODO SCAN UNCONFIRMED NAMES
	    
	  	//SCAN NAMESALES
	    Map<Account, List<NameSale>> nameSales;
	    synchronized(accounts)
		{
	    	nameSales = Controller.getInstance().scanNameSales(accounts);
		}
	    
	    //DELETE NAMESALES
	  	this.database.getNameSaleMap().deleteAll(accounts);
	  	
	  	//ADD NAMES
	  	this.database.getNameSaleMap().addAll(nameSales);
	  	
	  	//SCAN POLLS
	  	Map<Account, List<Poll>> polls;
	  	synchronized(accounts)
	  	{
	  		polls = Controller.getInstance().scanPolls(accounts);
	  	}
	  	
	  	//DELETE POLLS
	  	this.database.getPollMap().deleteAll(accounts);
	  	
	  	//ADD POLLS
	  	this.database.getPollMap().addAll(polls);
	  	
	  	//SCAN ASSETS
		Map<Account, List<Asset>> assets;
	  	synchronized(accounts)
	  	{
	  		assets = Controller.getInstance().scanAssets(accounts);
	  	}
	  	
	  	//DELETE ASSETS
	  	this.database.getAssetMap().deleteAll(accounts);
	  	
	  	//ADD ASSETS
	  	this.database.getAssetMap().addAll(assets);

	  	//SCAN ORDERS
	  	Map<Account, List<Order>> orders;
	  	synchronized(accounts)
	  	{
	  		orders = Controller.getInstance().scanOrders(accounts);
	  	}
	  	
	  	//DELETE ASSETS
	  	this.database.getOrderMap().deleteAll(accounts);
	  	
	  	//ADD ASSETS
	  	this.database.getOrderMap().addAll(orders);
	  	
	  	//SET LAST BLOCK
	  	this.database.setLastBlockSignature(Controller.getInstance().getLastBlock().getSignature());*/
	}
	
	//UNLOCK
	
	public boolean unlock(String password)
	{
		if(this.isUnlocked())
		{
			return false;
		}
		
		//TRY TO UNLOCK
		try
		{
			SecureWalletDatabase secureDatabase = new SecureWalletDatabase(password);
			return this.unlock(secureDatabase);
		}
		catch(Exception e)
		{
			return false;
		}
	}
	
	public boolean unlock(SecureWalletDatabase secureDatabase)
	{
		this.secureDatabase = secureDatabase;
		
		//NOTIFY
		this.setChanged();
		this.notifyObservers(new ObserverMessage(ObserverMessage.WALLET_STATUS, STATUS_UNLOCKED));
		
		return true;
	}
	
	public boolean lock()
	{
		if(!this.isUnlocked())
		{
			return false;
		}
		
		//CLOSE
		this.secureDatabase.close();
		this.secureDatabase = null;
		
		//NOTIFY
		this.setChanged();
		this.notifyObservers(new ObserverMessage(ObserverMessage.WALLET_STATUS, STATUS_LOCKED));
		
		//LOCK SUCCESSFULL
		return true;
	}

	//IMPORT/EXPORT
	
	public String importAccountSeed(byte[] accountSeed)
	{
		//CHECK IF WALLET IS OPEN
		if(!this.isUnlocked())
		{
			return "";
		}
		
		//CHECK LENGTH
		if(accountSeed.length != 32)
		{
			return "";
		}
		
		//CREATE ACCOUNT
		PrivateKeyAccount account = new PrivateKeyAccount(accountSeed);
		
		//CHECK IF ACCOUNT ALREADY EXISTS
	    if(!this.accountExists(account.getAddress()))
	    {	
	    	//ADD TO DATABASE
		    this.secureDatabase.getAccountSeedMap().add(account);
		    this.database.getAccountMap().add(account);
		    
		    //SYNCHRONIZE
		    this.synchronize();
		    
		    //RETURN
		    return account.getAddress();
	    }
	    
	    return "";
	}

	public byte[] exportAccountSeed(String address)
	{
		//CHECK IF WALLET IS OPEN
		if(!this.isUnlocked())
		{
			return null;
		}
		
		PrivateKeyAccount account = this.getPrivateKeyAccount(address);
		
		if(account == null)
		{
			return null;
		}
		
		return account.getSeed();	
	}
	
	public byte[] exportSeed()
	{
		//CHECK IF WALLET IS OPEN
		if(!this.isUnlocked())
		{
			return null;
		}
		
		return this.secureDatabase.getSeed();
	}
	
	//OBSERVER
	
	@Override
	public void addObserver(Observer o)
	{
		super.addObserver(o);
		
		//REGISTER ON ACCOUNTS
		this.database.getAccountMap().addObserver(o);
		
		//REGISTER ON TRANSACTIONS
		this.database.getTransactionMap().addObserver(o);
		
		//REGISTER ON BLOCKS
		this.database.getBlockMap().addObserver(o);
		
		//REGISTER ON NAMES
		this.database.getNameMap().addObserver(o);
		
		//REGISTER ON NAME SALES
		this.database.getNameSaleMap().addObserver(o);
		
		//REGISTER ON POLLS
		this.database.getPollMap().addObserver(o);
		
		//REGISTER ON ASSETS
		this.database.getAssetMap().addObserver(o);

		//REGISTER ON ORDERS
		this.database.getOrderMap().addObserver(o);
		
		//REGISTER ON ASSET FAVORITES
		this.database.getAssetFavoritesSet().addObserver(o);
		
		//SEND STATUS
		int status = STATUS_LOCKED;
		if(this.isUnlocked())
		{
			status = STATUS_UNLOCKED;
		}
		
		o.update(this, new ObserverMessage(ObserverMessage.WALLET_STATUS, status));
	}

	private void processTransaction(Transaction transaction)
	{
		//CHECK IF WALLET IS OPEN
		if(!this.exists())
		{
			return;
		}
				
		//FOR ALL ACCOUNTS
		List<Account> accounts = this.getAccounts();	
		synchronized(accounts)
		{		
			for(Account account: accounts)
			{
				//CHECK IF INVOLVED
				if(transaction.isInvolved(account))
				{
					//ADD TO ACCOUNT TRANSACTIONS
					if(!this.database.getTransactionMap().add(account, transaction))
					{					
						//UPDATE UNCONFIRMED BALANCE
						BigDecimal unconfirmedBalance = this.getUnconfirmedBalance(account.getAddress()).add(transaction.getAmount(account));
						this.database.getAccountMap().update(account, unconfirmedBalance);
					}
				}
			}
		}
	}
	
	private void orphanTransaction(Transaction transaction)
	{
		//CHECK IF WALLET IS OPEN
		if(!this.exists())
		{
			return;
		}
		
		///FOR ALL ACCOUNTS
		List<Account> accounts = this.getAccounts();
		
		synchronized(accounts)
		{		
			for(Account account: accounts)
			{
				//CHECK IF INVOLVED
				if(transaction.isInvolved(account))
				{
					//DELETE FROM ACCOUNT TRANSACTIONS
					this.database.getTransactionMap().delete(account, transaction);
					
					//UPDATE UNCONFIRMED BALANCE
					BigDecimal unconfirmedBalance = this.getUnconfirmedBalance(account.getAddress()).subtract(transaction.getAmount(account));
					this.database.getAccountMap().update(account, unconfirmedBalance);
				}
			}
		}
	}

	private void processBlock(Block block)
	{
		//CHECK IF WALLET IS OPEN
		if(!this.exists())
		{
			return;
		}
		
		//CHECK IF WE NEED TO RESYNC
		byte[] lastBlockSignature = this.database.getLastBlockSignature();
		if(lastBlockSignature == null || !Arrays.equals(lastBlockSignature, block.getReference()))
		{
			Logger.getGlobal().info("Wallet not synchronized with current blockchain: synchronizing wallet.");
			this.synchronize();
		}
		
		//SET AS LAST BLOCK
		this.database.setLastBlockSignature(block.getSignature());
			
		//CHECK IF WE ARE GENERATOR
		if(this.accountExists(block.getGenerator().getAddress()))
		{
			//ADD BLOCK
			this.database.getBlockMap().add(block);
				
			//KEEP TRACK OF UNCONFIRMED BALANCE
			BigDecimal unconfirmedBalance = this.getUnconfirmedBalance(block.getGenerator().getAddress()).add(block.getTotalFee());
			this.database.getAccountMap().update(block.getGenerator(), unconfirmedBalance);
		}
	}

	private void orphanBlock(Block block)
	{
		//CHECK IF WALLET IS OPEN
		if(!this.exists())
		{
			return;
		}
				
		//CHECK IF WE ARE GENERATOR
		if(this.accountExists(block.getGenerator().getAddress()))
		{
			//DELETE BLOCK
			this.database.getBlockMap().delete(block);
			
			//KEEP TRACK OF UNCONFIRMED BALANCE
			BigDecimal unconfirmedBalance = this.getUnconfirmedBalance(block.getGenerator().getAddress()).subtract(block.getTotalFee());
			this.database.getAccountMap().update(block.getGenerator(), unconfirmedBalance);
		}
	}
	
	private void processNameRegistration(RegisterNameTransaction nameRegistration)
	{
		//CHECK IF WALLET IS OPEN
		if(!this.exists())
		{
			return;
		}
		
		//CHECK IF WE ARE OWNER
		if(this.accountExists(nameRegistration.getName().getOwner().getAddress()))
		{
			//ADD NAME
			this.database.getNameMap().add(nameRegistration.getName());
		}
	}
	
	private void orphanNameRegistration(RegisterNameTransaction nameRegistration)
	{
		//CHECK IF WALLET IS OPEN
		if(!this.exists())
		{
			return;
		}
		
		//CHECK IF WE ARE OWNER
		if(this.accountExists(nameRegistration.getName().getOwner().getAddress()))
		{
			//DELETE NAME
			this.database.getNameMap().delete(nameRegistration.getName());
		}
	}
	
	private void processPollCreation(CreatePollTransaction pollCreation)
	{
		//CHECK IF WALLET IS OPEN
		if(!this.exists())
		{
			return;
		}
		
		//CHECK IF WE ARE OWNER
		if(this.accountExists(pollCreation.getPoll().getCreator().getAddress()))
		{
			//ADD POLL
			this.database.getPollMap().add(pollCreation.getPoll());
		}
	}
	
	private void orphanPollCreation(CreatePollTransaction pollCreation)
	{
		//CHECK IF WALLET IS OPEN
		if(!this.exists())
		{
			return;
		}
		
		//CHECK IF WE ARE OWNER
		if(this.accountExists(pollCreation.getPoll().getCreator().getAddress()))
		{
			//DELETE POLL
			this.database.getPollMap().delete(pollCreation.getPoll());
		}
	}

	private void processPollVote(VoteOnPollTransaction pollVote)
	{
		//CHECK IF WALLET IS OPEN
		if(!this.exists())
		{
			return;
		}
		
		//CHECK IF WE ARE OWNER
		Poll poll = DBSet.getInstance().getPollMap().get(pollVote.getPoll());
		if(this.accountExists(poll.getCreator().getAddress()))
		{
			//UPDATE POLL
			this.database.getPollMap().add(poll);
		}
	}
	
	private void orphanPollVote(VoteOnPollTransaction pollVote)
	{
		//CHECK IF WALLET IS OPEN
		if(!this.exists())
		{
			return;
		}
				
		//CHECK IF WE ARE OWNER
		Poll poll = DBSet.getInstance().getPollMap().get(pollVote.getPoll());
		if(this.accountExists(poll.getCreator().getAddress()))
		{
			//UPDATE POLL
			this.database.getPollMap().add(poll);
		}
	}	
	
	private void processNameUpdate(UpdateNameTransaction nameUpdate)
	{
		//CHECK IF WE ARE OWNER
		if(this.accountExists(nameUpdate.getOwner().getAddress()))
		{
			//CHECK IF OWNER CHANGED
			if(!nameUpdate.getOwner().getAddress().equals(nameUpdate.getName().getOwner().getAddress()))
			{
				//DELETE PREVIOUS NAME
				Name name = DBSet.getInstance().getUpdateNameMap().get(nameUpdate);
				this.database.getNameMap().delete(nameUpdate.getOwner(), name);
			}
		}
		
		//CHECK IF WE ARE NEW OWNER
		if(this.accountExists(nameUpdate.getName().getOwner().getAddress()))
		{
			//ADD NAME
			this.database.getNameMap().add(nameUpdate.getName());
		}
	}
	
	private void orphanNameUpdate(UpdateNameTransaction nameUpdate)
	{
		//CHECK IF WE WERE OWNER
		if(this.accountExists(nameUpdate.getOwner().getAddress()))
		{
			//CHECK IF OWNER WAS CHANGED
			if(!nameUpdate.getOwner().getAddress().equals(nameUpdate.getName().getOwner().getAddress()))
			{
				//ADD PREVIOUS  NAME
				Name name = DBSet.getInstance().getNameMap().get(nameUpdate.getName().getName());
				this.database.getNameMap().add(name);

			}
		}
		
		//CHECK IF WE WERE NEW OWNER
		if(this.accountExists(nameUpdate.getName().getOwner().getAddress()))
		{
			//ADD NAME
			this.database.getNameMap().delete(nameUpdate.getName());
		}
	}
	
	private void processNameSale(SellNameTransaction nameSaleTransaction)
	{
		//CHECK IF WE ARE SELLER
		if(this.accountExists(nameSaleTransaction.getNameSale().getName().getOwner().getAddress()))
		{
			//ADD TO DATABASE
			this.database.getNameSaleMap().add(nameSaleTransaction.getNameSale());
		}
	}
	
	private void orphanNameSale(SellNameTransaction nameSaleTransaction)
	{
		//CHECK IF WE ARE SELLER
		if(this.accountExists(nameSaleTransaction.getNameSale().getName().getOwner().getAddress()))
		{
			//REMOVE FROM DATABASE
			this.database.getNameSaleMap().delete(nameSaleTransaction.getNameSale());
		}
	}

	private void processCancelNameSale(CancelSellNameTransaction cancelNameSaleTransaction)
	{
		//CHECK IF WE ARE SELLER
		if(this.accountExists(cancelNameSaleTransaction.getOwner().getAddress()))
		{
			//REMOVE FROM DATABASE
			BigDecimal amount = DBSet.getInstance().getCancelSellNameMap().get(cancelNameSaleTransaction);
			NameSale nameSale = new NameSale(cancelNameSaleTransaction.getName(), amount);
			this.database.getNameSaleMap().delete(nameSale);
		}
	}
	
	private void orphanCancelNameSale(CancelSellNameTransaction cancelNameSaleTransaction)
	{
		//CHECK IF WE ARE SELLER
		if(this.accountExists(cancelNameSaleTransaction.getOwner().getAddress()))
		{
			//ADD TO DATABASE
			NameSale nameSale = DBSet.getInstance().getNameExchangeMap().getNameSale(cancelNameSaleTransaction.getName());
			this.database.getNameSaleMap().add(nameSale);
		}
	}
	
	private void processNamePurchase(BuyNameTransaction namePurchase)
	{
		//CHECK IF WE ARE BUYER
		if(this.accountExists(namePurchase.getBuyer().getAddress()))
		{
			//ADD NAME
			Name name = DBSet.getInstance().getNameMap().get(namePurchase.getNameSale().getKey());
			this.database.getNameMap().add(name);
		}
		
		//CHECK IF WE ARE SELLER
		Account seller = namePurchase.getSeller();
		if(this.accountExists(seller.getAddress()))
		{
			//DELETE NAMESALE
			this.database.getNameSaleMap().delete(seller, namePurchase.getNameSale());
			
			//DELETE NAME
			Name name = DBSet.getInstance().getNameMap().get(namePurchase.getNameSale().getKey());
			name.setOwner(seller);
			this.database.getNameMap().delete(seller, name);
		}
	}
	
	private void orphanNamePurchase(BuyNameTransaction namePurchase)
	{
		//CHECK IF WE WERE BUYER
		if(this.accountExists(namePurchase.getBuyer().getAddress()))
		{
			//DELETE NAME
			Name name = namePurchase.getNameSale().getName();
			name.setOwner(namePurchase.getBuyer());
			this.database.getNameMap().delete(namePurchase.getBuyer(), name);
		}
		
		//CHECK IF WE WERE SELLER
		Account seller = namePurchase.getSeller();
		if(this.accountExists(seller.getAddress()))
		{
			//ADD NAMESALE
			this.database.getNameSaleMap().add(namePurchase.getNameSale());
			
			//ADD NAME
			Name name = namePurchase.getNameSale().getName();
			this.database.getNameMap().add(name);
		}
	}
	
	private void processAssetIssue(IssueAssetTransaction assetIssue)
	{
		//CHECK IF WALLET IS OPEN
		if(!this.exists())
		{
			return;
		}
		
		//CHECK IF WE ARE OWNER
		if(this.accountExists(assetIssue.getAsset().getOwner().getAddress()))
		{
			//ADD POLL
			this.database.getAssetMap().add(assetIssue.getAsset());
		}
	}
	
	private void orphanAssetIssue(IssueAssetTransaction assetIssue)
	{
		//CHECK IF WALLET IS OPEN
		if(!this.exists())
		{
			return;
		}
		
		//CHECK IF WE ARE OWNER
		if(this.accountExists(assetIssue.getAsset().getOwner().getAddress()))
		{
			//DELETE ASSET
			this.database.getAssetMap().delete(assetIssue.getAsset());
		}
	}
	
	private void processOrderCreation(CreateOrderTransaction orderCreation)
	{
		//CHECK IF WALLET IS OPEN
		if(!this.exists())
		{
			return;
		}
		
		//ADD ORDER
		this.addOrder(orderCreation.getOrder());
	}
	
	private void addOrder(Order order)
	{
		//CHECK IF WE ARE CREATOR
		if(this.accountExists(order.getCreator().getAddress()))
		{
			//ADD ORDER
			this.database.getOrderMap().add(order);
		}
	}
	
	private void orphanOrderCreation(CreateOrderTransaction orderCreation)
	{
		//CHECK IF WALLET IS OPEN
		if(!this.exists())
		{
			return;
		}
		
		//CHECK IF WE ARE CREATOR
		if(this.accountExists(orderCreation.getOrder().getCreator().getAddress()))
		{
			//DELETE ORDER
			//this.database.getOrderMap().delete(orderCreation.getOrder());
		}
	}
	
	private void processOrderCancel(CancelOrderTransaction orderCancel)
	{
		//CHECK IF WALLET IS OPEN
		if(!this.exists())
		{
			return;
		}
				
		//CHECK IF WE ARE CREATOR
		if(this.accountExists(orderCancel.getCreator().getAddress()))
		{
			//DELETE ORDER
			this.database.getOrderMap().delete(new Tuple2<String, BigInteger>(orderCancel.getCreator().getAddress(), orderCancel.getOrder()));
		}
	}
	
	private void orphanOrderCancel(CancelOrderTransaction orderCancel)
	{
		//CHECK IF WALLET IS OPEN
		if(!this.exists())
		{
			return;
		}
				
		//CHECK IF WE ARE CREATOR
		if(this.accountExists(orderCancel.getCreator().getAddress()))
		{
			//DELETE ORDER
			Order order = DBSet.getInstance().getOrderMap().get(orderCancel.getOrder());
			this.database.getOrderMap().add(order);
		}
	}
	
	@Override
	public void update(Observable o, Object arg) 
	{
		ObserverMessage message = (ObserverMessage) arg;
		
		if(message.getType() == ObserverMessage.ADD_BLOCK_TYPE)
		{
			Block block = (Block) message.getValue();
				
			//CHECK BLOCK
			this.processBlock(block);
				
			//CHECK TRANSACTIONS
			for(Transaction transaction: block.getTransactions())
			{
				this.processTransaction(transaction);
				
				//CHECK IF NAME REGISTRATION
				if(transaction instanceof RegisterNameTransaction)
				{
					this.processNameRegistration((RegisterNameTransaction) transaction);
				}
				
				//CHECK IF NAME UPDATE
				if(transaction instanceof UpdateNameTransaction)
				{
					this.processNameUpdate((UpdateNameTransaction) transaction);
				}
				
				//CHECK IF NAME SALE
				if(transaction instanceof SellNameTransaction)
				{
					this.processNameSale((SellNameTransaction) transaction);
				}
				
				//CHECK IF NAME SALE
				if(transaction instanceof CancelSellNameTransaction)
				{
					this.processCancelNameSale((CancelSellNameTransaction) transaction);
				}
				
				//CHECK IF NAME PURCHASE
				if(transaction instanceof BuyNameTransaction)
				{
					this.processNamePurchase((BuyNameTransaction) transaction);
				}
				
				//CHECK IF POLL CREATION
				if(transaction instanceof CreatePollTransaction)
				{
					this.processPollCreation((CreatePollTransaction) transaction);
				}
				
				//CHECK IF POLL VOTE
				if(transaction instanceof VoteOnPollTransaction)
				{
					this.processPollVote((VoteOnPollTransaction) transaction);
				}
				
				//CHECK IF ASSET ISSUE
				if(transaction instanceof IssueAssetTransaction)
				{
					this.processAssetIssue((IssueAssetTransaction) transaction);
				}
				
				//CHECK IF ORDER CREATION
				/*if(transaction instanceof CreateOrderTransaction)
				{
					this.processOrderCreation((CreateOrderTransaction) transaction);
				}*/
				
				//CHECK IF ORDER CANCEL
				if(transaction instanceof CancelOrderTransaction)
				{
					this.processOrderCancel((CancelOrderTransaction) transaction);
				}
			}
		}
		
		if(message.getType() == ObserverMessage.ADD_TRANSACTION_TYPE)
		{	
			Transaction transaction = (Transaction) message.getValue();
				
			this.processTransaction(transaction);
			
			//CHECK IF NAME REGISTRATION
			if(transaction instanceof RegisterNameTransaction)
			{
				this.processNameRegistration((RegisterNameTransaction) transaction);
			}
			
			//CHECK IF POLL CREATION
			if(transaction instanceof CreatePollTransaction)
			{
				this.processPollCreation((CreatePollTransaction) transaction);
			}
			
			//CHECK IF ASSET ISSUE
			if(transaction instanceof IssueAssetTransaction)
			{
				this.processAssetIssue((IssueAssetTransaction) transaction);
			}
			
			//CHECK IF ORDER CREATION
			if(transaction instanceof CreateOrderTransaction)
			{
				this.processOrderCreation((CreateOrderTransaction) transaction);
			}
		}
		
		if(message.getType() == ObserverMessage.REMOVE_BLOCK_TYPE)
		{
			Block block = (Block) message.getValue();
				
			//CHECK BLOCK
			this.orphanBlock(block);
				
			//CHECK TRANSACTIONS
			for(Transaction transaction: block.getTransactions())
			{
				this.orphanTransaction(transaction);
				
				//CHECK IF NAME REGISTRATION
				if(transaction instanceof RegisterNameTransaction)
				{
					this.orphanNameRegistration((RegisterNameTransaction) transaction);
				}
				
				//CHECK IF NAME UPDATE
				if(transaction instanceof UpdateNameTransaction)
				{
					this.orphanNameUpdate((UpdateNameTransaction) transaction);
				}
				
				//CHECK IF NAME SALE
				if(transaction instanceof SellNameTransaction)
				{
					this.orphanNameSale((SellNameTransaction) transaction);
				}
				
				//CHECK IF CANCEL NAME SALE
				if(transaction instanceof CancelSellNameTransaction)
				{
					this.orphanCancelNameSale((CancelSellNameTransaction) transaction);
				}
				
				//CHECK IF CANCEL NAME SALE
				if(transaction instanceof BuyNameTransaction)
				{
					this.orphanNamePurchase((BuyNameTransaction) transaction);
				}
				
				//CHECK IF POLL CREATION
				if(transaction instanceof CreatePollTransaction)
				{
					this.orphanPollCreation((CreatePollTransaction) transaction);
				}
				
				//CHECK IF POLL VOTE
				if(transaction instanceof VoteOnPollTransaction)
				{
					this.orphanPollVote((VoteOnPollTransaction) transaction);
				}
				
				//CHECK IF ASSET ISSUE
				if(transaction instanceof IssueAssetTransaction)
				{
					this.orphanAssetIssue((IssueAssetTransaction) transaction);
				}
				
				//CHECK IF ORDER CREATION
				if(transaction instanceof CreateOrderTransaction)
				{
					this.orphanOrderCreation((CreateOrderTransaction) transaction);
				}
				
				//CHECK IF ORDER CANCEL
				if(transaction instanceof CancelOrderTransaction)
				{
					this.orphanOrderCancel((CancelOrderTransaction) transaction);
				}
			}
		}
		
		if(message.getType() == ObserverMessage.REMOVE_TRANSACTION_TYPE)
		{	
			Transaction transaction = (Transaction) message.getValue();
				
			this.orphanTransaction(transaction);
					
			//CHECK IF NAME REGISTRATION
			if(transaction instanceof RegisterNameTransaction)
			{
				this.orphanNameRegistration((RegisterNameTransaction) transaction);
			}
			
			//CHECK IF POLL CREATION
			if(transaction instanceof CreatePollTransaction)
			{
				this.orphanPollCreation((CreatePollTransaction) transaction);
			}
			
			//CHECK IF ASSET ISSUE
			if(transaction instanceof IssueAssetTransaction)
			{
				this.orphanAssetIssue((IssueAssetTransaction) transaction);
			}
			
			//CHECK IF ORDER CREATION
			if(transaction instanceof CreateOrderTransaction)
			{
				this.orphanOrderCreation((CreateOrderTransaction) transaction);
			}
		}
		
		//ADD ORDER
		if(message.getType() == ObserverMessage.ADD_ORDER_TYPE || message.getType() == ObserverMessage.REMOVE_ORDER_TYPE)
		{
			this.addOrder((Order) message.getValue());
		}
	}

	//CLOSE
	
	public void close()
	{
		if(this.database != null)
		{
			this.database.close();
		}
		
		if(this.secureDatabase != null)
		{
			this.secureDatabase.close();
		}
	}

	public void commit() 
	{
		if(this.database != null)
		{
			this.database.commit();
		}
		
		if(this.secureDatabase != null)
		{
			this.secureDatabase.commit();
		}
		
	}	
}
