#include "bingo_gross_storage.h"
#include <sstream>

using namespace indigo;
using namespace bingo;

GrossStorage::GrossStorage (size_t gross_block_size) : _gross_formulas(gross_block_size)
{
}

BingoAddr GrossStorage::create(BingoPtr<GrossStorage> &gross_ptr, size_t gross_block_size)
{
   gross_ptr.allocate();
   new (gross_ptr.ptr()) GrossStorage(gross_block_size);
         
   return (BingoAddr)gross_ptr;
}

void GrossStorage::load (BingoPtr<GrossStorage> &gross_ptr, BingoAddr offset)
{
   gross_ptr = BingoPtr<GrossStorage>(offset);
}

void GrossStorage::add (Array<char> &gross_formula, int id)
{
   _gross_formulas.add((byte *)gross_formula.ptr(), gross_formula.size(), id);
   dword hash = _calculateGrossHash(gross_formula.ptr(), gross_formula.size());
   _hashes.add(hash, id);
}

void GrossStorage::find (Array<char> &query_formula, Array<int> &indices, int part_id, int part_count)
{
   Array<int> candidates;

   findCandidates(query_formula, candidates, part_id, part_count);

   int cur_candidate = 0;
   int match_id;
   while ((match_id = findNext(query_formula, candidates, cur_candidate)) != -1)
   {
      indices.push(match_id);
   }
}

void GrossStorage::findCandidates (Array<char> &query_formula, Array<int> &candidates, int part_id, int part_count)
{
   dword query_hash = _calculateGrossHash(query_formula.ptr(), query_formula.size());
   
   dword first_hash = 0;
   dword last_hash = (dword)(-1);

   if (part_id != -1 && part_count != -1)
   {
      first_hash = (part_id - 1) * last_hash / part_count;
      last_hash = part_id * last_hash / part_count;
   }

   if (query_hash < first_hash || query_hash > last_hash)
      return;

   Array<size_t> indices;
   _hashes.getAll(query_hash, indices);

   for (int i = 0; i < indices.size(); i++)
      candidates.push(indices[i]);
}

int GrossStorage::findNext (Array<char> &query_formula, Array<int> &candidates, int &cur_candidate)
{
   Array<int> query_array;
   GrossFormula::fromString(query_formula.ptr(), query_array);

   while(++cur_candidate)
   {
      if (cur_candidate >= candidates.size())
         return -1;

      if (tryCandidate(query_array, candidates[cur_candidate]))
         return candidates[cur_candidate];
   }

   return -1;
}

bool GrossStorage::tryCandidate (Array<int> &query_array, int id)
{
   Array<int> cand_array;
   const char *cand_formula;
   int len;

   cand_formula = (const char *)_gross_formulas.get(id, len);
   Array<char> cand_fstr;
   cand_fstr.copy(cand_formula, len);

   GrossFormula::fromString(cand_fstr.ptr(), cand_array);

   if (GrossFormula::equal(query_array, cand_array))
      return true;

   return false;
}

void GrossStorage::calculateMolFormula (Molecule &mol, Array<char> &gross_formula)
{
   Array<int> gross_array;
   GrossFormula::collect(mol, gross_array);
   GrossFormula::toString(gross_array, gross_formula);
}

void GrossStorage::calculateRxnFormula (Reaction &rxn, Array<char> &gross_formula)
{
   gross_formula.clear();

   for (int i = rxn.begin(); i != rxn.end(); i = rxn.next(i))
   {
      Array<int> mol_array;
      Array<char> mol_formula;

      GrossFormula::collect(rxn.getBaseMolecule(i), mol_array);
      GrossFormula::toString(mol_array, mol_formula);

      gross_formula.concat(mol_formula);
      if (rxn.next(i) != rxn.end())
      {
         if (rxn.getSideType(i) != rxn.getSideType(rxn.next(i)))
            gross_formula.concat(">>", 2);
         else
            gross_formula.concat("+", 1);
      }
   }
}

dword GrossStorage::_calculateGrossHashForMolArray (Array<int> &gross_array)
{
   dword hash = 0;

   for (int i = 0; i < gross_array.size(); i++)
      hash += gross_array[i] * (i + 1);

   return hash;
}

dword GrossStorage::_calculateGrossHashForMol (const char *gross_str, int len)
{
   Array<int> gross_array;
   GrossFormula::fromString(gross_str, gross_array);
 
   return _calculateGrossHashForMolArray(gross_array);
}

dword GrossStorage::_calculateGrossHash (const char *gross_str, int len)
{
   dword hash = 0;

   std::string rxn_str(gross_str, len);
   int delim_pos = rxn_str.find('>');
   if (delim_pos == -1)
      hash = _calculateGrossHashForMol(gross_str, len);
   else
   {
      rxn_str[delim_pos] = '+';
      rxn_str.erase(rxn_str.begin() + delim_pos + 1);
      std::stringstream  rxn_stream;
      rxn_stream << rxn_str;
      std::string mol_str;
      while (rxn_stream.good())
      {
         std::getline(rxn_stream,  mol_str, '+');
         hash += _calculateGrossHashForMol(mol_str.c_str(), len);
      }
   }
   
   return hash;
}