// ---------------------------------------------------------------------------
//   Optimizer.h (created on Thu Nov 17 18:25:35 BRT 2005)
// 
//   Genetic Algorithm File Fitter (gaffitter)
//
//   Copyright (C) 2005-2015 Douglas A. Augusto
// 
// This file is part of gaffitter.
// 
// gaffitter 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; either version 3 of the License, or (at
// your option) any later version.
// 
// gaffitter 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 gaffitter; if not, see <http://www.gnu.org/licenses/>.
//
// ---------------------------------------------------------------------------

#ifndef Optimizer_hh
#define Optimizer_hh

#include "Input.h"
#include "Params.h"
#include "util/Exception.h"

#include <vector>
#include <iostream>

#include <cmath>

// ---------------------------------------------------------------------------
/**
 * @def ROUND
 * @brief Round a given float number
 */
#define ROUND( x ) (x < 0 ? std::ceil( (x) - 0.5 ) : std::floor( (x) + 0.5 ))

const Params::Size_t EPSILON = 0.000001;

// ---------------------------------------------------------------------------
class Optimizer;
class BinSet;

/** 
 * @brief Compare two double considering the not exact float point
 * representation. 
 */
static bool AlmostEqual( double u, double v )
{
   return std::fabs( u - v ) <= EPSILON * std::fabs( u ) &&
          std::fabs( u - v ) <= EPSILON * std::fabs( v );
}

// ---------------------------------------------------------------------------
/**
 * @class Bin
 * @brief A Bin is a collection of items/files (a "volume")
 */
class Bin {
public:
   /**
    * @brief Bin's constructor.
    */
   Bin( Params::Size_t capacity ): 
        m_sum_sizes( 0.0 ), m_capacity( capacity ) {}

   /** 
    * @brief Adds items from a given vector of SizeName and sets the
    * capacity as the sum of the sizes. */
   Bin( const std::vector<const SizeName*>& sn ) 
   { 
      for( unsigned i = 0; i < sn.size(); ++i ) AddItem( sn[i] );
      m_capacity = Used();
   }

   /**
    * @brief Returns the number of items.
    */
   unsigned Count() const { return m_items.size(); }

   /**
    * @brief Returns the ith item.
    */
   const SizeName* Item( int i ) const { return m_items[i]; }

   /**
    * @brief Returns all items (vector of items).
    */
   std::vector<const SizeName*>& Items() { return m_items; }
   /**
    * @brief Returns all items (vector of items); const version.
    */
   const std::vector<const SizeName*>& Items() const { return m_items; }

   /**
    * @brief Returns the total space occupied by the items.
    */
   Params::Size_t Used() const { return m_sum_sizes; }

   /**
    * @brief Returns the total space available.
    */
   Params::Size_t Free() const { return Capacity() - Used(); }
   /**
    * @brief Returns the bin's capacity.
    *
    * Likely, the bin's capacity is the same as @ref m_params.m_target.
    * However, gaffitter can be adapted to support variable bin capacity, and
    * so different bins might report different capacity.
    */
   Params::Size_t Capacity() const { return m_capacity; }

   /**
    * @brief Add an item to the current bin.
    */
   void AddItem( const SizeName* i ) 
   { 
      //Assert (i->Size() <= Free());
      m_items.push_back(i); 
      m_sum_sizes += i->Size();
   }

   /**
    * @brief Replace the ith item with a new item.
    */
   void ReplaceItem( int i, const SizeName* item )
   {
      m_sum_sizes += ( item->Size() - m_items[i]->Size() );
      m_items[i] = item;
   }

   /**
    * @brief Remove the ith item.
    */
   void DelItem( int i ) 
   { 
      //Assert(m_items[i]->Size() <= Used());
      m_sum_sizes -= m_items[i]->Size(); 
      m_items.erase( m_items.begin() + i );
   }

   /**
    * @brief Add all items of a given bin.
    */
   void AddAllItems( Bin& b )
   {
      for( unsigned i = 0; i < b.Count(); ++i ) AddItem( b[i] );
   }

   /**
    * @brief Remove all items of the current bin.
    */
   void DelAllItems() { m_items.clear(); m_sum_sizes = 0.0; }

   /**
    * @brief Returns the ith item.
    */
   const SizeName* operator[](int i) const { return m_items[i]; }

   /**
    * @brief Overloaded operator <
    *
    * It compares the bins regarding the free space; the most filled bin comes
    * first. It they have the same amount of free space, then the bin having
    * smaller number of items is considered the smaller one.
    */
   bool operator<( const Bin& bin ) const 
   {
      if ( Free() < bin.Free() ) return true;
      if ( Free() == bin.Free() ) return Count() < bin.Count();

      return false;
   }

   /**
    * @brief Compares two bins in a relaxed fashion.
    *
    * To achieve better performance, this function only compares the <b>free
    * space</b> (@ref Free()) and the <b>number of bins</b> (@ref Count()).
    *
    * For a complete comparison, this function should also verify if the two
    * bins contain exactly the same items.
    */
   bool operator==( const Bin& bin ) const 
   {
      return ( AlmostEqual( bin.Free(), Free() ) && bin.Count() == Count() );
   }

   /**
    * @brief Pointer (iterator) to the first item of m_items (begin())
    */
   std::vector<const SizeName*>::iterator begin()  { return m_items.begin(); }
   /**
    * @brief Pointer (iterator) to the last item plus one of m_items (end())
    */
   std::vector<const SizeName*>::iterator end()  { return m_items.end(); }

   /**
    * @brief Verify if a bin contains items in common with a @b set of bins,
    * and fill @c unassigned with the non-duplicate (non-common) items.
    *
    * @param[in] a  Bin to verify
    * @param[in] g1 First bin of the set of bins
    * @param[in] g2 Last bin of the set of bins
    * @param[out] unassigned List where the non-common items will be put.
    */
   friend bool Intersect( Bin& a, std::vector<Bin>::iterator g1, 
                                  std::vector<Bin>::iterator g2, 
                                  std::vector<const SizeName*>& unassigned );
private:
   std::vector<const SizeName*> m_items; /**< Vector of items. */
   Params::Size_t m_sum_sizes; /**< Total occupied size by the items. */
   Params::Size_t m_capacity; /**< Bin's capacity. */
};

// ---------------------------------------------------------------------------
/**
 * @class BinSet
 * @brief A set of bins
 */
class BinSet {
public:
   /**
    * @brief Returns the ith bin.
    */
   Bin& operator[]( int i ) { return m_bins[i]; }
   /**
    * @brief Returns the ith bin; const version.
    */
   const Bin& operator[]( int i ) const { return m_bins[i]; }

   /**
    * @brief Add a bin of capacity @c capacity.
    */
   Bin& AddBin( Params::Size_t capacity ) 
   { 
      m_bins.push_back( Bin( capacity ) );
      return m_bins.back(); 
   }
   /**
    * @brief Add the bin @c b.
    */
   Bin& AddBin(const Bin& b) { m_bins.push_back(b); return m_bins.back(); }

   /**
    * @brief Pointer (iterator) to the first bin of m_bins (begin())
    */
   std::vector<Bin>::iterator begin() { return m_bins.begin(); }
   /**
    * @brief Pointer (iterator) to the last bin plus one of m_bins (end())
    */
   std::vector<Bin>::iterator end() { return m_bins.end(); }

   /**
    * @brief Remove the ith bin.
    */
   void DelBin( int i ) { m_bins.erase( m_bins.begin() + i ); }
   /**
    * @brief Remove all bins (clear).
    */
   void DelAllBins() { m_bins.clear(); }

   /**
    * @brief Returns the number of bins.
    */
   unsigned int Count() const { return m_bins.size(); }

   /**
    * @brief Verify if two bins are different.
    */
   bool operator!=( const BinSet& bs ) const { return !( *this == bs ); }
   /**
    * @brief Verify if two bins are equal.
    */
   bool operator==( const BinSet& bs ) const 
   {
      if( Count() != bs.Count() ) return false;
      std::vector<Bin>::const_iterator i = m_bins.begin(), j = bs.m_bins.begin();

      while( i != m_bins.end() ) if( !(*i++ == *j++) ) return false;

      return true;
   }

private:
   std::vector<Bin> m_bins; /**< Vector of bins. */
};

// ---------------------------------------------------------------------------
/** 
 * @class Optimizer
 * @brief Base class for the search algorithms. 
 */
class Optimizer {
public:
   /** 
    * @brief Initialize some variables.
    */
   Optimizer( Input& i ): m_files( i.m_files ), m_params( i.m_params ), 
                          m_total_size( i.m_total_size ), m_solution( 0 )
   {
      /* The correct way to calculate m_bin_theo. A problematic scenario
       * occurs, for instance, when the total size is 2000 and the bin
       * capacity (target) is 100, and unfortunately the division
       * 2000/100 is represented as 20.000001 rather than the exact
       * 20.0; so, ceil( 20.000001) is 21 instead of giving 20 as the
       * theoretical minimum number of bin. */
      if( !m_params.m_bins_theo ) // if not given calculate it!
      {
         const Params::Size_t div = m_total_size / m_params.m_target;
         if( div > ROUND( div ) && div - ROUND( div ) < EPSILON )
            m_params.m_bins_theo = static_cast<unsigned>( ROUND( div ) );
         else
            m_params.m_bins_theo = static_cast<unsigned>( ceil( div ) );
      }

      if( m_params.m_verbose ) std::cout << "> Searching... " 
                                         << std::flush << std::endl;
   }

   /**
    * @brief Optimizer's destructor.
    */
   virtual ~Optimizer() {};

private:
   /** 
    * @brief Copies are not allowed. 
    */
   Optimizer( const Optimizer& );
   /** 
    * @brief Attributions are not allowed. 
    */
   Optimizer& operator=( const Optimizer& );

public:
   /**
    * @brief Searches for a optimal solution (via Genetic Algorithm, Best Fit
    * or Split)
    */
   virtual void Evolve() = 0;
   /**
    * @brief Print the result to the standard output.
    *
    * Print the selected files, a brief summary (sum, diff and number of
    * selected files).
    */
   void Output();
   /**
    * @brief This overloaded operator prints the name and parameters of the
    * search algorithms.
    */
   friend std::ostream& operator<<( std::ostream&, const Optimizer& );

protected:
   /**
    * @brief Write to streams the name and parameters of the search algorithms
    */
   virtual std::ostream& Write( std::ostream& s ) const
   {
      s << "> Target: " << m_params.PrettySize(m_params.m_target)
        << "\n> Input size: " << m_files.size()
        << "\n> Theoretical minimum number of bins: " << m_params.m_bins_theo
        << std::endl;

      return s;
   }

protected:
   /**
    * @brief Evaluate candidates.
    */
   Params::Size_t Evaluate( const BinSet& bs ) const;
   std::vector<const SizeName*>& m_files; /**< @brief Vector of input 
                                               files/sizes. */
   Params& m_params; /**< @brief Global parameters. */
   const Params::Size_t m_total_size; /**< Total size of the input items. */
protected:
   BinSet* m_solution; /**< Pointer to the best solution found. */
};

// ----------------------------------------------------------------------------
inline std::ostream& operator<<( std::ostream& s, const Optimizer& optimizer )
{
   return optimizer.Write( s );
}

// ----------------------------------------------------------------------------
#endif
