package jelectrum;

import java.util.HashSet;

import org.bitcoinj.store.BlockStore;
import org.bitcoinj.core.StoredBlock;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Block;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.NetworkParameters;
import java.util.List;
import java.util.LinkedList;
import java.util.TreeMap;
import java.util.HashMap;

import org.junit.Assert;

import jelectrum.db.DBFace;

/** Provides access to StoredBlock objects
 * which are small and have a bunch of important stuff, like the heights
 * of blocks */
public class MapBlockStore implements BlockStore
{
    private Jelectrum jelly;
    private DBFace file_db;

    private Sha256Hash genisis_hash;


    public MapBlockStore(Jelectrum jelly)
        throws org.bitcoinj.store.BlockStoreException
    {
        this.jelly = jelly;

        this.file_db = jelly.getDB();
        NetworkParameters params = jelly.getNetworkParameters();
        


        genisis_hash = params.getGenesisBlock().getHash();

        Assert.assertNotNull(file_db);
        Assert.assertNotNull(file_db.getSpecialBlockStoreMap());


        if (!file_db.getSpecialBlockStoreMap().containsKey("head"))
        {
        
            System.out.println("Adding genesis");
            Block genesisHeader = params.getGenesisBlock().cloneAsHeader();
            StoredBlock storedGenesis = new StoredBlock(genesisHeader, genesisHeader.getWork(), 0);
            put(storedGenesis);
            setChainHead(storedGenesis);
        }

    }


    public void close()
    {
        file_db.commit();
    }

    public NetworkParameters getParams()
    {
      return jelly.getNetworkParameters();
    }

    public StoredBlock get(Sha256Hash hash)
    {
        StoredBlock b = file_db.getBlockStoreMap().get(hash);
        return b;
    }

    public StoredBlock getChainHead()
        throws org.bitcoinj.store.BlockStoreException
    {
        StoredBlock head_blk =  file_db.getSpecialBlockStoreMap().get("head");


        StoredBlock curr = head_blk;
        int stepback=0;
        if (file_db.getBlockSavedMap()==null) throw new RuntimeException("BlockMap is null");

        while((!file_db.getBlockSavedMap().containsKey(curr.getHeader().getHash())) && (curr.getHeight()>=1))
        {   
            int step_size=10;
            if (curr.getHeight() < 1000) step_size=1;
            for(int i=0; i<step_size; i++)
            {
                stepback++;
                curr = curr.getPrev(this);
            }
        }
        setChainHead(curr);
        jelly.getEventLog().alarm("Current Head: " + curr.getHeader().getHash().toString() + " - " + curr.getHeight() + " stepback " + stepback);
        return curr;
    }

    public void put(StoredBlock block)
        throws org.bitcoinj.store.BlockStoreException
    {
        Sha256Hash hash = block.getHeader().getHash();

        file_db.getBlockStoreMap().put(hash, block);
    
    }

    public void put(Block b)
        throws org.bitcoinj.store.BlockStoreException
    {
      Sha256Hash hash = b.getHash();
      Block header = b.cloneAsHeader();

      Sha256Hash prev = b.getPrevBlockHash();
      StoredBlock prev_sb = file_db.getBlockStoreMap().get(prev);

      StoredBlock sb = prev_sb.build(header);

      file_db.getBlockStoreMap().put(hash, sb);

      setChainHead(sb);
    }

    public void putAll(List<Block> blks)
        throws org.bitcoinj.store.BlockStoreException
    {
      HashMap<Sha256Hash, StoredBlock> insert_map = new HashMap<>();

      StoredBlock last = null;

      for(Block b : blks)
      {
        Sha256Hash hash = b.getHash();
        Sha256Hash prev = b.getPrevBlockHash();

        if (!hash.equals(jelly.getNetworkParameters().getGenesisBlock().getHash()))
        {
          StoredBlock prev_sb = insert_map.get(prev);
          if (prev_sb == null)
          {
            prev_sb = file_db.getBlockStoreMap().get(prev);
          }
          Assert.assertNotNull(prev_sb);

          Block header = b.cloneAsHeader();
          StoredBlock sb = prev_sb.build(header);

          last = sb;

          insert_map.put(hash, sb);
        }
      }


      file_db.getBlockStoreMap().putAll(insert_map);

      if (last != null) setChainHead(last);

    }

    public int getHeight(Sha256Hash hash)
    {
        return get(hash).getHeight();
    }


    public void setChainHead(StoredBlock block)
        throws org.bitcoinj.store.BlockStoreException
    {
        Sha256Hash hash = block.getHeader().getHash();


        //if (jelly.isUpToDate() || (block.getHeight() % 100 == 0))
        {
          file_db.getSpecialBlockStoreMap().put("head", block);
        }

        if (jelly.getBlockChainCache() != null)
        {
            jelly.getBlockChainCache().update(jelly, block);
        }

        if (jelly.getHeaderChunkAgent()!=null)
        {
            jelly.getHeaderChunkAgent().poke(block.getHeight());
        }

    }


    /*private void saveChainHead(StoredBlock block)
        throws org.bitcoinj.store.BlockStoreException
    {
      Sha256Hash hash = block.getHeader().getHash();
      file_db.getSpecialBlockStoreMap().put("head", block);

        if (jelly.getBlockChainCache() != null)
        {
            jelly.getBlockChainCache().update(jelly, block);
        }
    }*/

}