/******************************************************************************
* NOMAD -  Nonlinear Optimization for Mixed vAriables and Derivatives.        *
* Copyright (C) 2001-2004  Gilles Couture - Ecole Polytechnique,              *
*                          Charles Audet - Ecole Polytechnique,               *
*		           John Dennis - Rice University.                     *
*                                                                             *
* Author: Gilles Couture                                                      *
* École Polytechnique de Montréal - GERAD                                     *
* C.P. 6079, Succ. Centre-ville, Montréal (Québec) H3C 3A7 Canada             *
* Tel:  (514) 340-6053 x6928                                                  *
* Email: Gilles.Couture@gerad.ca                                              *
* Fax: (514) 340-5665                                                         *
*                                                                             *
* This program is free software; you can redistribute it and/or               *
* modify it under the terms of the GNU General Public License                 *
* as published by the Free Software Foundation. You can find the              *
* GNU General Public License at:                                              *
*             http://www.gnu.org/copyleft/gpl.html                            *
*                                                                             *
* This program is distributed in the hope that it will be useful,             *
* but WITHOUT ANY WARRANTY; without even the implied warranty of              *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               *
* GNU General Public License for more details.                                *
*                                                                             *
* You should have received a copy of the GNU General Public License           *
* along with this program; if not, write to the Free Software                 *
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. *
*                                                                             *
* You can find information on the NOMAD software at:                          *
*             http://www.gerad.ca/NOMAD/                                      *
******************************************************************************/

// stlcache.cpp -- StlCache class methods.

#include <fstream>
#include <iostream>
using namespace std;
#include "stlcache.h"

// Initialization of the static variable. This is the date when the cache
// structure was last modified. The format is year/month/day, like this:
// yyyymmdd
const int StlCache::VERSION = 20050630;

// Private methods.
// ****************************************************************************

// This method deletes the pairs in the cache from memory.
void StlCache::destroyCache()
{
  cout << "Destroying StlCache...\n";
  std::map<Point *, Point *, PointComparison>::iterator iter;
  for (iter = cache.begin(); iter != cache.end(); iter++)
    {
      delete (*iter).first;
      delete (*iter).second;
    }
  cout << "Step one.\n";
  cache.clear();
}

// Public methods.
// ****************************************************************************

// Constructor. The database is created if the user chooses to use a cache,
// during data entry (in the user interface).
StlCache::StlCache(const char * name, const int dim, const int out_dim)
  : Database(name, dim, out_dim), cache_hits(0), nb_pairs_in_cache(0)
{
  // The cache file is read.
  if (file_name != NULL)
    read();
}

// Destructor.
StlCache::~StlCache()
{
  cout << "In 'StlCache::~StlCache()'\n";
  cout << "Elements in cache = " << cache.size() << endl;
  if (!cache.empty())
    destroyCache();
  else
    cout << "StlCache empty!\n";
}

// This method checks if the database contains a point.
bool StlCache::find(AugmentedPoint & ap, const double * key)
{
  bool found = false;
  Point * x = new Point(key_size, key);

  // We check if the point 'x' is in the STL map.
  std::map<Point *, Point *, PointComparison>::iterator iter = cache.find(x);

  // If 'x' was found in the cache, the cache hits counter is incremented.
  if (iter != cache.end())
    {
      ap.complete((*iter).second->getPoint());
      ++cache_hits;
      found = true;
    }

  // The memory is freed.
  delete x;
  // The boolean value is returned.
  return found;
}

// This method inserts a point in the cache.
void StlCache::insert(double * inp, double * utp)
{
  cache.insert(pair<Point *, Point *> (new Point(key_size, inp),
				       new Point(data_size, utp)));
  // The number of pairs is updated (we can't simply increment the variable,
  // because if a point is not inserted if it's already in the cache.
  nb_pairs_in_cache = cache.size();
}

// This method adds the cache 'temp' to this object's cache.
void StlCache::operator+=(StlCache & temp)
{
  if (this == &temp)
    return;

  // The pairs of the temporary cache are inserted in 'this' cache. Since the
  // data structure is a map (with unique keys), a pair won't be inserted if it
  // is already in the cache.
  std::map<Point *, Point *, PointComparison>::iterator iter;
  for (iter = temp.cache.begin(); iter != temp.cache.end(); iter++)
    insert((*iter).first->getPoint(), (*iter).second->getPoint());
}

// This method reads points from a binary file. The points are then inserted in
// the cache. This is the file structure:
// A : Cache version             : 1 integer.
// B : Number of pairs    (p)    : 1 integer.
// C : Problem dimension  (n)    : 1 integer.
// D : Number of outputs  (m)    : 1 integer.
// E : The 'p' pairs of points   : p * (n + m) double
void StlCache::read()
{
  // The file is opened.
  ifstream fin(file_name, ios::binary);

  // A test to see if the file exists.
  if (fin.fail())
    {
      cout << "The permanent cache does not exist.\n";
      return;
    }

  fin.seekg(0, ios::beg);

  // A: The cache version is read.
  int cache_version;
  fin.read((char *) &cache_version, sizeof(cache_version));
  // If the cache version is incorrect the file is closed.
  if (cache_version != StlCache::VERSION)
    {
      fin.close();
      fin.clear();
      cout << "\nError in cache file " << file_name << ": Cache version = ";
      cout << cache_version << " != " << StlCache::VERSION << "\n";
      return;
    }

  // B: The number of pairs is read.
  fin.read((char *) &nb_pairs_in_cache, sizeof(nb_pairs_in_cache));
  // If the number of pairs is negative or null the file is closed.
  if (nb_pairs_in_cache <= 0)
    {
      fin.close();
      fin.clear();
      cout << "\nError in cache file " << file_name;
      cout << ": Number of pairs = " << nb_pairs_in_cache << "\n";
      return;
    }

  // C: The problem dimension is read.
  int dimension;
  fin.read((char *) &dimension, sizeof(dimension));
  // If the problem dimension in the cache isn't the same as the current
  // problem dimension the file is closed.
  if (dimension != key_size)
    {
      fin.close();
      fin.clear();
      cout << "\nError in cache file " << file_name << ": Dimension = ";
      cout << dimension << " != " << key_size << "\n";
      return;
    }

  // D: The number of outputs is read.
  int output_dimension;
  fin.read((char *) &output_dimension, sizeof(output_dimension));
  // If the output dimension in the cache isn't the same as the current
  // output dimension the file is closed.
  if (output_dimension != data_size)
    {
      fin.close();
      fin.clear();
      cout << "\nError in cache file " << file_name << ": Output dimension = ";
      cout << output_dimension << " != " << data_size << "\n";
      return;
    }

  // E : The 'p' pairs of points are read.
  double val;
  int i, j;
  double * key = new double[key_size];
  double * data = new double[data_size];

  for (i = 0; i < nb_pairs_in_cache; i++)
    {
      // The key's coordinates are read.
      for (j = 0; j < key_size; j++)
	{
	  fin.read((char *) &val, sizeof(val));
	  key[j] = val;
	}

      // The data's coordinates are read.
      for (j = 0; j < data_size; j++)
	{
	  fin.read((char *) &val, sizeof(val));
	  data[j] = val;
	}

      // The pair is inserted in the cache.
      cache.insert(pair<Point *, Point *> (new Point(key_size, key),
					   new Point(data_size, data)));
    }

  // The file is closed.
  fin.close();
  fin.clear();

  delete [] key;
  delete [] data;
}

// This method resets this object's attributes.
void StlCache::reset()
{
  // The cache is destroyed.
  destroyCache();
  // The variables are reset.
  cache_hits = nb_pairs_in_cache = 0;
}

// This method outputs the pairs of the STL map.
void StlCache::show() const
{
  // The object's attributes are shown.
  cout << "Cache version: " << StlCache::VERSION << "\n";
  if (file_name != NULL)
    cout << "File name = " << file_name << "\n";
  cout << "Key size = " << key_size << ", data size = " << data_size << "\n";
  cout << "Number of cache hits: " << cache_hits << "\n";
  cout << "Number of items in the cache: " << nb_pairs_in_cache << "\n";

  if (nb_pairs_in_cache > 0)
    {
      // The points are shown.
      cout << "\nPairs in the cache:\n-------------------\n";
      std::map<Point *, Point *, PointComparison>::const_iterator iter;
      for (iter = cache.begin(); iter != cache.end(); iter++)
	{
	  cout << "Key: " << *((*iter).first);
	  cout << ", data: " <<  *((*iter).second) << "\n";
	}
    }
  cout << endl;
}

// This method outputs this object's attributes.
void StlCache::showOutput(ofstream & fout) const
{
  if (!fout.is_open())
    return;
  fout << "Number of items in the cache: " << nb_pairs_in_cache << "\n";
  fout << "Cache hits: " << cache_hits << "\n";
}

// This method saves the cache in a binary file. This is the file structure:
// A : Cache version             : 1 integer.
// B : Number of pairs    (p)    : 1 integer.
// C : Problem dimension  (n)    : 1 integer.
// D : Number of outputs  (m)    : 1 integer.
// E : The 'p' pairs of points   : p * (n + m) double
void StlCache::write()
{
  if (file_name == NULL)
    return;

  // The binary file is created.
  ofstream fout(file_name, ios::binary | ios::out);

  // A: The cache version is written.
  fout.write((char *) &StlCache::VERSION, sizeof(int));

  // B: The number of points in the cache is written.
  fout.write((char *) &nb_pairs_in_cache, sizeof(nb_pairs_in_cache));

  // C: The problem dimension is written.
  fout.write((char *) &key_size, sizeof(key_size));

  // D: The number of outputs is written.
  fout.write((char *) &data_size, sizeof(data_size));

  // E: The pairs <key, data> are written.
  double value;
  double * key, * data;
  int i;
  std::map<Point *, Point *, PointComparison>::iterator iter;
  for (iter = cache.begin(); iter != cache.end(); iter++)
    {
      // The points' coordinates are retrieved.
      key = ((*iter).first)->getPoint();
      data = (*iter).second->getPoint();

      // The key's coordinates are written.
      for (i = 0; i < key_size; i++)
	{
	  value = key[i];
	  fout.write((char *) &value, sizeof(value));
	}

      // The data's coordinates are written.
      for (i = 0; i < data_size; i++)
	{
	  value = data[i];
	  fout.write((char *) &value, sizeof(value));
	}
    }

  // The cache is saved in the binary file.
  fout.close();
  fout.clear();
}
