/* CirKit: A circuit toolkit
 * Copyright (C) 2009-2015  University of Bremen
 * Copyright (C) 2015-2017  EPFL
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#include "esop_minimization.hpp"

#include <iomanip>
#include <list>

#include <boost/algorithm/string/join.hpp>
#include <boost/assign/std/list.hpp>
#include <boost/assign/std/vector.hpp>
#include <boost/format.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int_distribution.hpp>
#include <boost/range/adaptors.hpp>
#include <boost/range/algorithm.hpp>
#include <boost/range/algorithm_ext/push_back.hpp>
#include <boost/range/numeric.hpp>

#include <core/io/read_pla_to_bdd.hpp>
#include <core/utils/range_utils.hpp>
#include <core/utils/terminal.hpp>
#include <core/utils/timer.hpp>

using namespace boost::assign;

namespace cirkit
{

/******************************************************************************
 * Types                                                                      *
 ******************************************************************************/
enum decomposition_type
{
  NegativeDavio,
  PositiveDavio,
  Shannon
};

enum var_values_enum
{
  VariableNegative = 0,
  VariablePositive,
  VariableAbsent
};

std::ostream& operator<<( std::ostream& os, const cube_t& cube )
{
  for ( unsigned i = 0u; i < cube.first.size(); ++i )
  {
    os << ( cube.second[i] ? ( cube.first[i] ? "1" : "0" ) : "-" );
  }
  return os;
}

boost::dynamic_bitset<> diff_cube( const cube_t& c1, const cube_t& c2 )
{
  return ((c1.second ^ c2.second) | ((c1.second & c1.first) ^ (c2.second & c2.first)));
}

/* Returns the distance of c1 and c2. If distance is 1 and bit_pos == -1, then bit_pos stores
 * the bit position in which they differ.
 */
boost::dynamic_bitset<>::size_type compute_distance( const cube_t& c1, const cube_t& c2, int& bit_pos )
{
  boost::dynamic_bitset<> diff = diff_cube( c1, c2 );
  auto d = diff.count();
  if ( d == 1 && bit_pos == -1 )
  {
    bit_pos = diff.find_first();
  }
  return d;
}

/* This changes cube c1 at position with respect to the value of c2 at this position. */
void change( cube_t& c1, const cube_t& c2, unsigned position )
{
  if ( c1.second[position] && c2.second[position] ) /* 0, 1 -> - */
  {
    c1.first.reset( position );
    c1.second.reset( position );
  }
  else if ( !c1.second[position] ) /* -, X -> ~X */
  {
    c1.first.set( position, !c2.first[position] );
    c1.second.set( position );
  }
  else if ( !c2.second[position] ) /* X, - -> ~X */
  {
    c1.first.flip( position );
  }
}

/* Alternative implementation of change, but seems to be a tiny bit slower. */
void change_alternative( cube_t& c1, const cube_t& c2, unsigned position )
{
  bool V1 = c1.first[position];
  bool V2 = c2.first[position];
  bool C1 = c1.second[position];
  bool C2 = c2.second[position];

  c1.first.set( !V1 && !V2 && (C1 ^ C2) );
  c1.second.set( !C1 && !C2 && (V1 ^ V2) );
}

class esop_manager
{
public:
  typedef std::pair<unsigned, unsigned> cube_pair_t;
  typedef std::list<cube_pair_t> cube_pair_list_t;

  esop_manager( DdManager * cudd, bool verbose = false, unsigned capacity = 1000u )
    : cudd( cudd ),
      verbose( verbose ),
      distance_lists( 3u )
  {
    _cubes.reserve( capacity );
  }

  void add_cube( cube_t cube )
  {
    unsigned cubeid = 0u;
    std::vector<char> distances( _cubes.size() );
    int bit_pos = -1;

    for ( ; cubeid < _cubes.size(); ++cubeid )
    {
      const cube_t& c = _cubes.at( cubeid );

      /* distance-0 */
      if ( ( distances[cubeid] = compute_distance( c, cube, bit_pos ) ) == 0 )
      {
        remove_cube( cubeid );
        return;
      }
    }

    /* distance-1 */
    auto it = boost::find( distances, 1 );
    if ( it != distances.end() )
    {
      unsigned distance_one_cubeid = std::distance( distances.begin(), it );
      cube_t c = _cubes.at( distance_one_cubeid );
      change( c, cube, bit_pos );
      remove_cube( distance_one_cubeid );
      add_cube( c );
      return;
    }

    /* Add cube */
    for ( unsigned i = 0u ; i < distances.size(); ++i )
    {
      if ( distances[i] <= 4 )
      {
        distance_lists[distances[i] - 2u] += std::make_pair( i, _cubes.size() );
      }
    }

    _cubes += cube;
  }

  void remove_from_distance_list( cube_pair_list_t& l, unsigned cubeid, bool remove_first = true, bool remove_second = true )
  {
    l.remove_if( [&cubeid, &remove_first, &remove_second]( const std::pair<unsigned, unsigned>& p ) {
        return ( remove_first && p.first == cubeid ) || ( remove_second && p.second == cubeid );
      } );
    for ( auto it = l.begin(); it != l.end(); ++it )
    {
      if ( remove_first  && it->first  > cubeid ) { it->first--;  }
      if ( remove_second && it->second > cubeid ) { it->second--; }
    }
  }

  void remove_cube( unsigned cubeid )
  {
    _cubes.erase( _cubes.begin() + cubeid );

    /* remove from distance lists */
    for ( unsigned i = 0u; i < 3u; ++i )
    {
      remove_from_distance_list( distance_lists[i], cubeid );
    }
  }

  /* We know the distance when we call this function, so we do not want to recompute it */
  void get_different_positions( const cube_t& c1, const cube_t& c2, unsigned distance, std::vector<unsigned>& positions )
  {
    boost::dynamic_bitset<> diff = diff_cube( c1, c2 );
    unsigned pos;

    for ( unsigned i = 0u; i < distance; ++i )
    {
      positions += ( pos = diff.find_first() );
      diff.flip( pos );
    }
  }

  std::string pair_list_to_string( const cube_pair_list_t& l )
  {
    using boost::adaptors::transformed;

    return boost::join( l | transformed( []( const cube_pair_t& p ) {
          return boost::str( boost::format( "(%d,%d)" ) % p.first % p.second ); } ), ", " );
  }

  void get_exorlink_group( const cube_t& c1, const cube_t& c2, cube_t * tmp_cubes, unsigned group, const std::vector<unsigned>& positions )
  {
    /* distance is positions.size() */
    unsigned distance = positions.size();
    for ( unsigned i = 0u; i < distance; ++i )
    {
      tmp_cubes[i] = c1;
      for ( unsigned j = 0u; j < distance; ++j )
      {
        switch ( cube_groups[cube_group_offsets[distance - 2u] + group * distance * distance + i * distance + j] )
        {
        case 1u:
          tmp_cubes[i].first.set( positions[j], c2.first[positions[j]] );
          tmp_cubes[i].second.set( positions[j], c2.second[positions[j]] );
          break;
        case 2u:
          change( tmp_cubes[i], c2, positions[j] );
          break;
        }
      }
    }
  }

  bool leads_to_improvement( unsigned cubeid1, unsigned cubeid2, unsigned distance )
  {
    assert( cubeid1 < cubeid2 );

    using boost::adaptors::transformed;

    const cube_t& c1 = _cubes.at( cubeid1 ); /* easy access to c1 */
    const cube_t& c2 = _cubes.at( cubeid2 ); /* easy access to c2 */

    std::vector<unsigned> positions;        /* positions of different cubes in c1 and c2 */
    cube_t tmp_cubes[4];                    /* used for current cube computation */
    int improvement;                        /* store the current possible improvement */
    int bit_pos;

    get_different_positions( c1, c2, distance, positions );

    /* loop over all grous */
    for ( unsigned group = 0u; group < cube_group_count[distance - 2u]; ++group )
    {
      if ( verbose )
      {
        std::cout << "  Group: " << group << std::endl;
      }

      /* reset values */
      improvement = distance - 2;

      get_exorlink_group( c1, c2, tmp_cubes, group, positions );

      /* follow exor link */
      for ( unsigned i = 0; i < distance; ++i )
      {
        if ( verbose )
        {
          std::cout << "    " << i << ": " << tmp_cubes[i] << std::endl;
        }

        bit_pos = -1;
        for ( unsigned cubeid = 0u; cubeid < _cubes.size(); ++cubeid )
        {
          /* do not calculate distance to given cubes */
          if ( cubeid == cubeid1 || cubeid == cubeid2 ) continue;

          const cube_t& ex_cube = _cubes.at( cubeid );
          auto d = compute_distance( ex_cube, tmp_cubes[i], bit_pos );
          if ( d == 0u )
          {
            improvement -= 2;
          }
          else if ( d == 1u )
          {
            improvement -= 1;
          }
        }
      }

      /* did we find a good permutation? */
      if ( ( distance == 2u && improvement < 0 ) || ( distance >= 3u && improvement <= 0 ) )
      {
        if ( verbose )
        {
          std::cout << "    Found improvement" << std::endl;
        }

        /* remove old pair */
        remove_cube( cubeid2 );
        remove_cube( cubeid1 );

        /* add new cubes */
        for ( unsigned i = 0u; i < distance; ++i )
        {
          add_cube( tmp_cubes[i] );
        }

        return true;
      }
    }

    return false;
  }

  bool exorlink( unsigned distance )
  {
    using boost::adaptors::transformed;

    assert( distance >= 2 && distance <= 4 );

    if ( verbose )
    {
      print_banner( boost::str( boost::format( "EXOR-LINK (d = %d)" ) % distance ) );
    }

    std::vector<unsigned> positions;
    for ( const auto& p : distance_lists.at( distance - 2u ) )
    {
      if ( verbose )
      {
        std::cout << "Try to optimize with cube " << p.first << " and " << p.second << std::endl;
      }

      if ( leads_to_improvement( p.first, p.second, distance ) )
      {
        return true;
      }
    }

    return false;
  }

  inline unsigned cube_count() const
  {
    return _cubes.size();
  }

  inline unsigned literal_count() const
  {
    using boost::adaptors::transformed;

    return boost::accumulate( _cubes | transformed( []( const cube_t& c ) { return c.second.count(); } ), 0u );
  }

  inline const std::vector<cube_t>& cubes() const
  {
    return _cubes;
  }

  void print_statistics()
  {
    using boost::adaptors::indexed;
    using boost::adaptors::transformed;

    print_banner( "Statistics" );

    std::cout << "Number of cubes:    " << cube_count() << std::endl;
    std::cout << "Number of literals: " << literal_count() << std::endl;
    std::cout << "Cubes:" << std::endl;
    for ( auto it : index( _cubes ) )
    {
      std::cout << boost::format( "%4d: " ) % it.index << it.value << std::endl;
    }
    std::cout << "Distance lists:" << std::endl;
    for ( unsigned i = 0u; i < 3u; ++i )
    {
      std::cout << boost::format( "%4d: " ) % (i + 2u);
      std::cout << pair_list_to_string( distance_lists[i] ) << std::endl;
    }
  }

  DdNode * to_bdd( const cube_t& cube )
  {
    DdNode * cubef = Cudd_ReadOne( cudd ), *tmp;
    Cudd_Ref( cubef );

    for ( unsigned i = 0u; i < cube.first.size(); ++i )
    {
      if ( cube.second[i] )
      {
        tmp = Cudd_bddAnd( cudd, cubef, cube.first[i] ? Cudd_bddIthVar( cudd, i ) : Cudd_Not( Cudd_bddIthVar( cudd, i ) ) );
        Cudd_Ref( tmp );
        Cudd_RecursiveDeref( cudd, cubef );
        cubef = tmp;
      }
    }

    return cubef;
  }

  DdNode * to_bdd( const std::vector<cube_t>& cube_list )
  {
    DdNode * f = Cudd_ReadLogicZero( cudd ), * tmp;
    Cudd_Ref( f );

    for ( const auto& cube : cube_list )
    {
      DdNode * cubef = to_bdd( cube );

      tmp = Cudd_bddXor( cudd, f, cubef );
      Cudd_Ref( tmp );
      Cudd_RecursiveDeref( cudd, f );
      Cudd_RecursiveDeref( cudd, cubef );
      f = tmp;
    }

    return f;
  }

  DdNode * to_bdd()
  {
    return to_bdd( _cubes );
  }

  bool verify( DdNode * f )
  {
    DdNode * esopf = to_bdd();
    DdNode * compare = Cudd_bddXnor( cudd, f, esopf );
    Cudd_Ref( compare );
    Cudd_RecursiveDeref( cudd, esopf );

    bool equal = compare == Cudd_ReadOne( cudd );

    Cudd_RecursiveDeref( cudd, compare );

    return equal;
  }

private:
  DdManager * cudd;
  bool verbose;
  std::vector<cube_t> _cubes;
  std::vector<cube_pair_list_t> distance_lists;

  static unsigned cube_groups[];
  static unsigned cube_group_count[];
  static unsigned cube_group_offsets[];
};

/**
 * (2 0) (1 2)
 * (0 2) (2 1)
 */

/**
 * (2 0 0) (1 2 0) (1 1 2)
 * (2 0 0) (1 0 2) (1 2 1)
 * (0 2 0) (2 1 0) (1 1 2)
 * (0 2 0) (0 1 2) (2 1 1)
 * (0 0 2) (2 0 1) (1 2 1)
 * (0 0 2) (0 2 1) (2 1 1)
 */

/**
 * (2 0 0 0) (1 2 0 0) (1 1 2 0) (1 1 1 2)
 * (2 0 0 0) (1 2 0 0) (1 1 0 2) (1 1 2 1)
 * (2 0 0 0) (1 0 2 0) (1 2 1 0) (1 1 1 2)
 * (2 0 0 0) (1 0 2 0) (1 0 1 2) (1 2 1 1)
 * (2 0 0 0) (1 0 0 2) (1 2 0 1) (1 1 2 1)
 * (2 0 0 0) (1 0 0 2) (1 0 2 1) (1 2 1 1)
 * (0 2 0 0) (2 1 0 0) (1 1 2 0) (1 1 1 2)
 * (0 2 0 0) (2 1 0 0) (1 1 0 2) (1 1 2 1)
 * (0 2 0 0) (0 1 2 0) (2 1 1 0) (1 1 1 2)
 * (0 2 0 0) (0 1 2 0) (0 1 1 2) (2 1 1 1)
 * (0 2 0 0) (0 1 0 2) (2 1 0 1) (1 1 2 1)
 * (0 2 0 0) (0 1 0 2) (0 1 2 1) (2 1 1 1)
 * (0 0 2 0) (2 0 1 0) (1 2 1 0) (1 1 1 2)
 * (0 0 2 0) (2 0 1 0) (1 0 1 2) (1 2 1 1)
 * (0 0 2 0) (0 2 1 0) (2 1 1 0) (1 1 1 2)
 * (0 0 2 0) (0 2 1 0) (0 1 1 2) (2 1 1 1)
 * (0 0 2 0) (0 0 1 2) (2 0 1 1) (1 2 1 1)
 * (0 0 2 0) (0 0 1 2) (0 2 1 1) (2 1 1 1)
 * (0 0 0 2) (2 0 0 1) (1 2 0 1) (1 1 2 1)
 * (0 0 0 2) (2 0 0 1) (1 0 2 1) (1 2 1 1)
 * (0 0 0 2) (0 2 0 1) (2 1 0 1) (1 1 2 1)
 * (0 0 0 2) (0 2 0 1) (0 1 2 1) (2 1 1 1)
 * (0 0 0 2) (0 0 2 1) (2 0 1 1) (1 2 1 1)
 * (0 0 0 2) (0 0 2 1) (0 2 1 1) (2 1 1 1)
 */
unsigned esop_manager::cube_groups[] = { 2, 0, 1, 2,
                                         0, 2, 2, 1,
                                         2, 0, 0, 1, 2, 0, 1, 1, 2,
                                         2, 0, 0, 1, 0, 2, 1, 2, 1,
                                         0, 2, 0, 2, 1, 0, 1, 1, 2,
                                         0, 2, 0, 0, 1, 2, 2, 1, 1,
                                         0, 0, 2, 2, 0, 1, 1, 2, 1,
                                         0, 0, 2, 0, 2, 1, 2, 1, 1,
                                         2, 0, 0, 0, 1, 2, 0, 0, 1, 1, 2, 0, 1, 1, 1, 2,
                                         2, 0, 0, 0, 1, 2, 0, 0, 1, 1, 0, 2, 1, 1, 2, 1,
                                         2, 0, 0, 0, 1, 0, 2, 0, 1, 2, 1, 0, 1, 1, 1, 2,
                                         2, 0, 0, 0, 1, 0, 2, 0, 1, 0, 1, 2, 1, 2, 1, 1,
                                         2, 0, 0, 0, 1, 0, 0, 2, 1, 2, 0, 1, 1, 1, 2, 1,
                                         2, 0, 0, 0, 1, 0, 0, 2, 1, 0, 2, 1, 1, 2, 1, 1,
                                         0, 2, 0, 0, 2, 1, 0, 0, 1, 1, 2, 0, 1, 1, 1, 2,
                                         0, 2, 0, 0, 2, 1, 0, 0, 1, 1, 0, 2, 1, 1, 2, 1,
                                         0, 2, 0, 0, 0, 1, 2, 0, 2, 1, 1, 0, 1, 1, 1, 2,
                                         0, 2, 0, 0, 0, 1, 2, 0, 0, 1, 1, 2, 2, 1, 1, 1,
                                         0, 2, 0, 0, 0, 1, 0, 2, 2, 1, 0, 1, 1, 1, 2, 1,
                                         0, 2, 0, 0, 0, 1, 0, 2, 0, 1, 2, 1, 2, 1, 1, 1,
                                         0, 0, 2, 0, 2, 0, 1, 0, 1, 2, 1, 0, 1, 1, 1, 2,
                                         0, 0, 2, 0, 2, 0, 1, 0, 1, 0, 1, 2, 1, 2, 1, 1,
                                         0, 0, 2, 0, 0, 2, 1, 0, 2, 1, 1, 0, 1, 1, 1, 2,
                                         0, 0, 2, 0, 0, 2, 1, 0, 0, 1, 1, 2, 2, 1, 1, 1,
                                         0, 0, 2, 0, 0, 0, 1, 2, 2, 0, 1, 1, 1, 2, 1, 1,
                                         0, 0, 2, 0, 0, 0, 1, 2, 0, 2, 1, 1, 2, 1, 1, 1,
                                         0, 0, 0, 2, 2, 0, 0, 1, 1, 2, 0, 1, 1, 1, 2, 1,
                                         0, 0, 0, 2, 2, 0, 0, 1, 1, 0, 2, 1, 1, 2, 1, 1,
                                         0, 0, 0, 2, 0, 2, 0, 1, 2, 1, 0, 1, 1, 1, 2, 1,
                                         0, 0, 0, 2, 0, 2, 0, 1, 0, 1, 2, 1, 2, 1, 1, 1,
                                         0, 0, 0, 2, 0, 0, 2, 1, 2, 0, 1, 1, 1, 2, 1, 1,
                                         0, 0, 0, 2, 0, 0, 2, 1, 0, 2, 1, 1, 2, 1, 1, 1 };

unsigned esop_manager::cube_group_count[] = { 2u, 6u, 24u };

unsigned esop_manager::cube_group_offsets[] = { 0u, 8u, 62u };

/******************************************************************************
 * PSDKRO functions                                                           *
 ******************************************************************************/
exp_cost_t count_cubes_in_exact_psdkro( DdManager * cudd, DdNode * f, exp_cache_t& exp_cache )
{
  exp_cost_t r;

  // terminal cases
  if ( f == Cudd_ReadLogicZero( cudd ) ) return std::make_pair( PositiveDavio, 0u );
  if ( f == Cudd_ReadOne( cudd ) )       return std::make_pair( PositiveDavio, 1u );

  // in cache?
  auto it = exp_cache.find( f );
  if ( it != exp_cache.end() )
  {
    return it->second;
  }

  // get co-factors
  DdNode * f0 = Cudd_NotCond( Cudd_E( f ), Cudd_IsComplement( f ) );
  DdNode * f1 = Cudd_NotCond( Cudd_T( f ), Cudd_IsComplement( f ) );
  DdNode * f2 = Cudd_bddXor( cudd, f0, f1 );
  Cudd_Ref( f2 );

  // recursively solve subproblems
  int n0, n1, n2, nmax;
  n0 = count_cubes_in_exact_psdkro( cudd, f0, exp_cache ).second;
  n1 = count_cubes_in_exact_psdkro( cudd, f1, exp_cache ).second;
  n2 = count_cubes_in_exact_psdkro( cudd, f2, exp_cache ).second;

  // determine the mostly costly expansion
  nmax = n0 > n1 ? n0 : n1;
  nmax = n2 > nmax ? n2 : nmax;

  // choose the least costly expansion
  if      ( nmax == n0 ) r = std::make_pair( NegativeDavio, n1 + n2 );
  else if ( nmax == n1 ) r = std::make_pair( PositiveDavio, n0 + n2 );
  else                   r = std::make_pair( Shannon,       n0 + n1 );

  //Cudd_RecursiveDeref( cudd, f2 );

  // cache and return result
  return exp_cache[f] = r;
}

// TODO can we do something nicer with last_index
void generate_exact_psdkro( DdManager * cudd, DdNode * f, char * var_values, int last_index, const exp_cache_t& exp_cache, const std::function<void()>& on_cube )
{
  // terminal cases
  if ( f == Cudd_ReadLogicZero( cudd ) ) return;
  if ( f == Cudd_ReadOne( cudd ) )
  {
    using boost::adaptors::indexed;

    unsigned n = Cudd_ReadSize( cudd );
    for ( unsigned i = last_index + 1; i < n; ++i )
    {
      var_values[i] = VariableAbsent;
    }

    on_cube();
    return;
  }

  // find the best expansion by a cache lookup
  unsigned exp = exp_cache.find( f )->second.first;

  // determine the top-most variable
  int index = Cudd_NodeReadIndex( f );

  // clear intermediate variables that have not been used
  for ( int i = last_index + 1; i < index; ++i )
  {
    var_values[i] = VariableAbsent;
  }

  // get co-factors
  DdNode * f0 = Cudd_NotCond( Cudd_E( f ), Cudd_IsComplement( f ) );
  DdNode * f1 = Cudd_NotCond( Cudd_T( f ), Cudd_IsComplement( f ) );
  DdNode * f2 = Cudd_bddXor( cudd, f0, f1 );
  Cudd_Ref( f2 );

  if ( exp == PositiveDavio )
  {
    var_values[index] = VariableAbsent;
    generate_exact_psdkro( cudd, f0, var_values, index, exp_cache, on_cube );
    var_values[index] = VariablePositive;
    generate_exact_psdkro( cudd, f2, var_values, index, exp_cache, on_cube );
  }
  else if ( exp == NegativeDavio )
  {
    var_values[index] = VariableAbsent;
    generate_exact_psdkro( cudd, f1, var_values, index, exp_cache, on_cube );
    var_values[index] = VariableNegative;
    generate_exact_psdkro( cudd, f2, var_values, index, exp_cache, on_cube );
  }
  else
  {
    var_values[index] = VariableNegative;
    generate_exact_psdkro( cudd, f0, var_values, index, exp_cache, on_cube );
    var_values[index] = VariablePositive;
    generate_exact_psdkro( cudd, f1, var_values, index, exp_cache, on_cube );
  }

  Cudd_RecursiveDeref( cudd, f2 );
}

void generate_exact_psdkro( esop_manager& esop, DdManager * cudd, DdNode * f, char * var_values, int last_index, const exp_cache_t& exp_cache )
{
  generate_exact_psdkro( cudd, f, var_values, last_index, exp_cache, [&esop, &cudd, &var_values]() {
      const auto n = Cudd_ReadSize( cudd );
      boost::dynamic_bitset<> lits( n, 0u ), care( n, 0u );

      auto index = 0u;
      for ( auto it : boost::make_iterator_range( var_values, var_values + Cudd_ReadSize( cudd ) ) )
      {
        lits.set( index, it == VariablePositive );
        care.set( index, it != VariableAbsent );
        ++index;
      }

      esop.add_cube( std::make_pair( lits, care ) );
    } );
}

/******************************************************************************
 * Public functions                                                           *
 ******************************************************************************/

void esop_minimization( DdManager * cudd, DdNode * f, properties::ptr settings, properties::ptr statistics )
{
  using boost::adaptors::map_keys;

  /* Settings */
  bool            verbose = get( settings, "verbose", false             );
  unsigned        runs    = get( settings, "runs",    1u                );
  bool            verify  = get( settings, "verify",  false             );
  cube_function_t on_cube = get( settings, "on_cube", cube_function_t() );

  esop_manager esop( cudd, verbose );

  /* block for timing */
  {
    properties_timer t( statistics );

    /* get initial cover using exact PSDKRO optimization */
    exp_cache_t exp_cache;
    count_cubes_in_exact_psdkro( cudd, f, exp_cache );

    char * var_values = new char[Cudd_ReadSize( cudd )];
    std::fill( var_values, var_values + Cudd_ReadSize( cudd ), VariableAbsent );
    generate_exact_psdkro( esop, cudd, f, var_values, -1, exp_cache );

    delete[] var_values;

    if ( verbose )
    {
      esop.print_statistics();
    }

    /* EXOR-LINK */
    for ( unsigned i = 0u; i < runs; ++i )
    {
      unsigned old_count, cur_count = esop.cube_count();

      do {
        old_count = cur_count;

        do {
          old_count = cur_count;

          esop.exorlink( 2u );
          esop.exorlink( 3u );
          esop.exorlink( 4u );

          cur_count = esop.cube_count();
        } while ( cur_count < old_count );

        /* last gasp */
        for ( unsigned j = 0u; j < 10u; ++j )
        {
          esop.exorlink( 4u );
        }

        cur_count = esop.cube_count();
      } while ( cur_count < old_count );
    }

    if ( verbose )
    {
      esop.print_statistics();
    }
  }

  /* pass cubes */
  if ( on_cube )
  {
    boost::for_each( esop.cubes(), on_cube );
  }

  if ( statistics )
  {
    statistics->set( "cube_count", esop.cube_count() );
    statistics->set( "literal_count", esop.literal_count() );
  }

  if ( verify )
  {
    assert( esop.verify( f ) );
  }
}

void esop_minimization( const std::string& filename, properties::ptr settings, properties::ptr statistics )
{
  BDDTable bdd;
  read_pla_to_bdd( bdd, filename );

  esop_minimization( bdd.cudd, bdd.outputs.front().second, settings, statistics );
}

dd_based_esop_optimization_func dd_based_esop_minimization_func(properties::ptr settings, properties::ptr statistics)
{
  dd_based_esop_optimization_func f = [&settings, &statistics]( DdManager * cudd, DdNode * node ) {
    return esop_minimization( cudd, node, settings, statistics );
  };
  f.init( settings, statistics );
  return f;
}

pla_based_esop_optimization_func pla_based_esop_minimization_func(properties::ptr settings, properties::ptr statistics)
{
  pla_based_esop_optimization_func f = [&settings, &statistics]( const std::string& filename ) {
    return esop_minimization( filename, settings, statistics );
  };
  f.init( settings, statistics );
  return f;
}

/******************************************************************************
 * Tests                                                                      *
 ******************************************************************************/

void test_change_performance()
{
  unsigned n = 10u;
  unsigned count = 1u << 21u;

  boost::random::mt19937 gen;
  boost::random::uniform_int_distribution<> dist( 0u, (1u << n) - 1u );

  std::vector<std::pair<cube_t, cube_t> > cubes( count );
  boost::generate( cubes, [&n, &gen, &dist]() { return std::make_pair(
                                                                      std::make_pair( boost::dynamic_bitset<>( n, dist( gen ) ), boost::dynamic_bitset<>( n, dist( gen ) ) ),
                                                                      std::make_pair( boost::dynamic_bitset<>( n, dist( gen ) ), boost::dynamic_bitset<>( n, dist( gen ) ) ) ); } );

  {
    print_timer t;

    for ( const auto& p : cubes )
    {
      cube_t c = p.first;
      change( c, p.second, 5u );
    }
  }

  {
    print_timer t;

    for ( const auto& p : cubes )
    {
      cube_t c = p.first;
      change_alternative( c, p.second, 5u );
    }
  }
}

}

// Local Variables:
// c-basic-offset: 2
// eval: (c-set-offset 'substatement-open 0)
// eval: (c-set-offset 'innamespace 0)
// End:


// Software License for MTL
//
// Copyright (c) 2007 The Trustees of Indiana University.
//               2008 Dresden University of Technology and the Trustees of Indiana University.
//               2010 SimuNova UG (haftungsbeschränkt), www.simunova.com.
// All rights reserved.
// Authors: Peter Gottschling and Andrew Lumsdaine
//
// This file is part of the Matrix Template Library
//
// See also license.mtl.txt in the distribution.

#ifndef MTL_MATRIX_RECURATOR_INCLUDE
#define MTL_MATRIX_RECURATOR_INCLUDE

#include <cmath>
#include <boost/shared_ptr.hpp>
#include <boost/type_traits/remove_const.hpp>
#include <boost/numeric/mtl/concept/collection.hpp>
#include <boost/numeric/mtl/operation/sub_matrix.hpp>
#include <boost/numeric/mtl/operation/print_matrix.hpp>
#include <boost/numeric/mtl/matrix/transposed_view.hpp>
#include <boost/numeric/mtl/recursion/dim_splitter.hpp>
#include <boost/numeric/mtl/recursion/utility.hpp>
#include <boost/numeric/mtl/utility/exception.hpp>

namespace mtl { namespace mat {

template <typename Recursator1, typename Recursator2>
void inline equalize_depth(Recursator1& r1, Recursator2& r2);

template <typename Recursator1, typename Recursator2, typename Recursator3>
void inline equalize_depth(Recursator1& r1, Recursator2& r2, Recursator3& r3);

/*! Class for matrix recursator

    How to use this class is described in the \ref rec_intro "recursion introduction".

    \sa \ref mtl::mat::north_west, \ref mtl::mat::north_east,
    \ref mtl::mat::south_west, \ref mtl::mat::south_east,
    \ref mtl::mat::is_empty(const recursator<Matrix>&),
    \ref mtl::mat::is_full(const recursator<Matrix>&),
    \ref mtl::mat::num_rows(const recursator<Matrix>&),
    \ref mtl::mat::num_cols(const recursator<Matrix>&),
    \ref mtl::mat::size(const recursator<Matrix>&)
**/
template <typename Matrix>
struct recursator
{
    typedef recursator                                     self;
    typedef Matrix                                                matrix_type;
    typedef typename sub_matrix_t<Matrix>::sub_matrix_type        sub_matrix_type;
    typedef typename sub_matrix_t<Matrix>::const_sub_matrix_type  const_sub_matrix_type;
    typedef typename Collection<Matrix>::size_type                size_type;
    typedef typename Collection<Matrix>::value_type               matrix_value_type;
    typedef recursion::outer_bound_splitter<self>                 splitter_type;

private:

    template <typename MatrixType> // why was it templated ???
    sub_matrix_type constructor_helper(MatrixType const& matrix)
    {
	return sub_matrix(matrix, matrix.begin_row(), matrix.end_row(),
			  matrix.begin_col(), matrix.end_col());
    }

    // For views without own data, we need to generate a new sub_matrix as shared_ptr
    template <typename MatrixType>
    sub_matrix_type constructor_helper(transposed_view<MatrixType> const& view)
    {
	typedef typename boost::remove_const<MatrixType>::type   tmp_type;
	typedef typename sub_matrix_t<tmp_type>::sub_matrix_type ref_sub_type;
	typedef boost::shared_ptr<ref_sub_type>                  pointer_type;
	typedef typename transposed_view<MatrixType>::other      ref_type;

	// Submatrix of referred matrix, colums and rows interchanged
	// Create a submatrix, whos address will be kept by transposed_view
	pointer_type p(new ref_sub_type(sub_matrix(const_cast<ref_type&>(view.ref), view.begin_col(), view.end_col(),
						   view.begin_row(), view.end_row())));
	return sub_matrix_type(p);
    }

public:
    /*! Construct a recursator from a matrix.
        \param matrix The matrix to which the recursator refers.
	\param bound  Explicit bound declaration; must not be smaller than the numbers of rows and the number of columns;
	              must also be a power of 2.

        Constructor takes the entire matrix as sub-matrix.
        This allows to have different type for the matrix and the sub-matrix.
    **/
    explicit recursator(Matrix const& matrix,
			       size_type bound= 0
			       )
	: my_sub_matrix(constructor_helper(matrix)), my_bound(recursion::outer_bound(matrix)),
	  my_first_row(0), my_first_col(0)         // splitter(*this)
    {
      if (bound == 0)
	my_bound= recursion::outer_bound(matrix);
      else {
	MTL_DEBUG_THROW_IF(!recursion::is_power_of_2(bound), range_error("Bound must be a power of 2"));
	MTL_DEBUG_THROW_IF(bound < num_rows(matrix) || bound < num_cols(matrix),
			   range_error("Bound must not be smaller than matrix dimensions"));
	my_bound= bound;
      }
    }

private:

    template <typename SubMatrix>
    sub_matrix_type get_value_dispatch(const SubMatrix& ,
				   size_type br, size_type er, size_type bc, size_type ec) const
    {
	return sub_matrix(my_sub_matrix, br, er, bc, ec);
    }

    template <typename SubMatrix>
    sub_matrix_type get_value_dispatch(transposed_view<SubMatrix> view,
				       size_type br, size_type er, size_type bc, size_type ec) const
    {
	typedef typename sub_matrix_t<SubMatrix>::sub_matrix_type   ref_sub_type;
	typedef boost::shared_ptr<ref_sub_type>                     pointer_type;
	typedef typename transposed_view<SubMatrix>::other          ref_type;

	pointer_type p(new ref_sub_type(sub_matrix(const_cast<ref_type&>(view.ref), bc, ec, br, er)));
	return sub_matrix_type(p);
    }

public:
    sub_matrix_type get_value() const
    {
	using std::min;
	size_type begin_row= my_sub_matrix.begin_row() + my_first_row,
	          end_row= min(begin_row + my_bound, my_sub_matrix.end_row()),
	          begin_col= my_sub_matrix.begin_col() + my_first_col,
	          end_col= min(begin_col + my_bound, my_sub_matrix.end_col());

#if 0
	std::cout << "get_value [" << begin_row << "-" << end_row << "]["
		  << begin_col << "-" << end_col << "]\n";
#endif
	return get_value_dispatch(my_sub_matrix, begin_row, end_row, begin_col, end_col);
    }

    /// Compute the sub-matrix corresponding to this recursator.
    sub_matrix_type operator*() const
    {
	return get_value();
    }

    // Returning quadrants for non-const recursator

    self north_west() const
    {
	self tmp(*this);
	tmp.my_bound >>= 1; // divide by 2
	return tmp;
    }

    self south_west() const
    {
	self tmp(*this);
	tmp.my_bound >>= 1; // divide by 2
	tmp.my_first_row += tmp.my_bound;
	return tmp;
    }

    self north_east() const
    {
	self tmp(*this);
	tmp.my_bound >>= 1; // divide by 2
	tmp.my_first_col += tmp.my_bound;
	return tmp;
    }

    self south_east() const
    {
	self tmp(*this);
	tmp.my_bound >>= 1; // divide by 20
	tmp.my_first_row += tmp.my_bound;
	tmp.my_first_col += tmp.my_bound;
	return tmp;
    }

    bool is_empty() const
    {
	return my_first_row >= num_rows(my_sub_matrix) || my_first_col >= num_cols(my_sub_matrix);
    }

    /// Return the bound of the recursator
    size_type bound() const
    {
	return my_bound;
    }

    /*! Set the bound of the recursator.
	\param b  The new virtual bound; must be a power of 2.

        This function allows to declare a virtual bound smaller than the number of rows and/or columns.
	It must be used with uttermost care.
    **/
    void set_bound(size_type b)
    {
	my_bound= b;
    }

    template <typename R1, typename R2> friend void equalize_depth (R1&, R2&);
    template <typename R1, typename R2, typename R3> friend void equalize_depth (R1&, R2&, R3&);

    template <typename M> friend typename recursator<M>::size_type num_rows(const recursator<M>& rec);
    template <typename M> friend typename recursator<M>::size_type num_cols(const recursator<M>& rec);

    // Dirty feature to be used with care
    matrix_value_type* first_address()
    {
	return &my_sub_matrix[my_first_row][my_first_col];
    }

    const matrix_value_type* first_address() const
    {
	return &my_sub_matrix[my_first_row][my_first_col];
    }

  protected:
    sub_matrix_type     my_sub_matrix; /// Referred matrix (from which the sub-matrices are built)
    size_type           my_bound,      /// Virtual matrix size, i.e. upper bound for size of sub-matrix.
	                my_first_row,  /// Row of first entry in submatrix
                        my_first_col;  /// Row of first entry in submatrix
};

#if 0

// Obsolete, only left in code because discussed in a paper
// To use recursator with const matrices Reference must be 'Matrix const&'
template <typename Matrix, typename Splitter = recursion::max_dim_splitter<Matrix> >
struct recursator_s
{
    typedef recursator_s                                    self;
    typedef Matrix                                                matrix_type;
    typedef Splitter                                              splitter_type;
    typedef typename sub_matrix_t<Matrix>::sub_matrix_type        sub_matrix_type;
    typedef typename sub_matrix_t<Matrix>::const_sub_matrix_type  const_sub_matrix_type;
    typedef typename Matrix::size_type                            size_type;
    // typedef outer_bound_splitter<self>                            splitter_type;

private:

    // template <typename Matrix> why was it templated ???
    sub_matrix_type constructor_helper(Matrix const& matrix)
    {
	return sub_matrix(matrix, matrix.begin_row(), matrix.end_row(),
			  matrix.begin_col(), matrix.end_col());
    }

    // For views without own data, we need to generate a new sub_matrix as shared_ptr
    // template <typename Matrix>
    sub_matrix_type constructor_helper(transposed_view<Matrix> const& matrix)
    {
	typedef typename sub_matrix_t<Matrix>::sub_matrix_type   ref_sub_type;
	typedef boost::shared_ptr<ref_sub_type>                  pointer_type;

	// Submatrix of referred matrix, colums and rows interchanged
	// Create a submatrix, whos address will be kept by transposed_view
	pointer_type p(new ref_sub_type(sub_matrix(matrix.ref, matrix.begin_col(), matrix.end_col(),
						   matrix.begin_row(), matrix.end_row())));
	return sub_matrix_type(p);
    }

public:
    // Constructor takes the whole matrix as sub-matrix
    // This allows to have different type for the matrix and the sub-matrix
    // This also enables matrices to have references as sub-matrices
    explicit recursator_s(Matrix const& matrix, size_type bound= 0)
	: my_sub_matrix(constructor_helper(matrix)), my_bound(outer_bound(matrix)),
	  splitter(my_sub_matrix)
    {
      if (bound == 0)
	my_bound= outer_bound(matrix);
      else {
	assert(is_power_of_2(bound));
	assert(bound >= matrix.num_rows() && bound >= matrix.num_cols());
	my_bound= bound;
      }
    }

    // Sub-matrices are copied directly
    // explicit recursator(sub_matrix_type sub_matrix) : my_sub_matrix(sub_matrix) {}

    sub_matrix_type& get_value()
    {
	return my_sub_matrix;
    }

    sub_matrix_type const& get_value() const
    {
	return my_sub_matrix;
    }

    // Returning quadrants for non-const recursator

    self north_west()
    {
	sub_matrix_type sm(sub_matrix(my_sub_matrix, my_sub_matrix.begin_row(), splitter.row_split(),
				      my_sub_matrix.begin_col(), splitter.col_split()));
	self tmp(sm, my_bound / 2);
	return tmp;
    }

    self south_west()
    {
	sub_matrix_type sm(sub_matrix(my_sub_matrix, splitter.row_split(), my_sub_matrix.end_row(),
				      my_sub_matrix.begin_col(), splitter.col_split()));
	self tmp(sm, my_bound / 2);
	return tmp;
    }

    self north_east()
    {
	sub_matrix_type sm(sub_matrix(my_sub_matrix, my_sub_matrix.begin_row(), splitter.row_split(),
				      splitter.col_split(), my_sub_matrix.end_col()));
	self tmp(sm, my_bound / 2);
	return tmp;
    }

    self south_east()
    {
	sub_matrix_type sm(sub_matrix(my_sub_matrix, splitter.row_split(), my_sub_matrix.end_row(),
				      splitter.col_split(), my_sub_matrix.end_col()));
	self tmp(sm, my_bound / 2);
	return tmp;
    }

    // Returning quadrants for const recursator

    self const north_west() const
    {
	sub_matrix_type sm(sub_matrix(const_cast<self*>(this)->my_sub_matrix, my_sub_matrix.begin_row(), splitter.row_split(),
				      my_sub_matrix.begin_col(), splitter.col_split()));
	self tmp(sm, my_bound / 2);
	return tmp;
    }

    self const south_west() const
    {
	sub_matrix_type sm(sub_matrix(const_cast<self*>(this)->my_sub_matrix, splitter.row_split(), my_sub_matrix.end_row(),
				      my_sub_matrix.begin_col(), splitter.col_split()));
	self tmp(sm, my_bound / 2);
	return tmp;
    }

    self const north_east() const
    {
	sub_matrix_type sm(sub_matrix(const_cast<self*>(this)->my_sub_matrix, my_sub_matrix.begin_row(), splitter.row_split(),
				      splitter.col_split(), my_sub_matrix.end_col()));
	self tmp(sm, my_bound / 2);
	return tmp;
    }

    self const south_east() const
    {
	sub_matrix_type sm(sub_matrix(const_cast<self*>(this)->my_sub_matrix, splitter.row_split(), my_sub_matrix.end_row(),
				      splitter.col_split(), my_sub_matrix.end_col()));
	self tmp(sm, my_bound / 2);
	return tmp;
    }

    // Checking whether a quadrant is empty

    // For completeness
    bool north_west_empty() const
    {
	return false;
    }

    bool north_east_empty() const
    {
	return splitter.col_split() == my_sub_matrix.end_col();
    }

    bool south_west_empty() const
    {
	return splitter.row_split() == my_sub_matrix.end_row();
    }

    bool south_east_empty() const
    {
	return splitter.row_split() == my_sub_matrix.end_row()
	       || splitter.col_split() == my_sub_matrix.end_col();
    }

    bool is_empty() const
    {
	return my_sub_matrix.begin_row() == my_sub_matrix.end_row()
	       || my_sub_matrix.begin_col() == my_sub_matrix.end_col();
    }

#if 0
    bool is_leaf() const
    {
	return my_sub_matrix.num_rows() < 2 || my_sub_matrix.num_cols() < 2;
    }
#endif

    size_type bound() const
    {
	assert(my_bound >= my_sub_matrix.num_rows() && my_bound >= my_sub_matrix.num_cols());
	return my_bound;
    }

    template <typename R1, typename R2> friend void equalize_depth (R1&, R2&);
    template <typename R1, typename R2, typename R3> friend void equalize_depth (R1&, R2&, R3&);

  protected:
    sub_matrix_type     my_sub_matrix;
    size_type           my_bound;
    splitter_type       splitter;
};

#endif

template <typename Recursator1, typename Recursator2>
void inline equalize_depth(Recursator1& r1, Recursator2& r2)
{
    typename Recursator1::size_type max_bound= std::max(r1.bound(), r2.bound());
    r1.my_bound= max_bound;
    r2.my_bound= max_bound;
}

template <typename Recursator1, typename Recursator2, typename Recursator3>
void inline equalize_depth(Recursator1& r1, Recursator2& r2, Recursator3& r3)
{
    typename Recursator1::size_type max_bound= std::max(std::max(r1.bound(), r2.bound()), r3.bound());
    r1.my_bound= max_bound;
    r2.my_bound= max_bound;
    r3.my_bound= max_bound;
}

// Define free functions (from member functions)

/*! Compute the north-west quadrant of a recursator (i.e. its referred matrix).
    The result is itself a recursator.
    \sa \ref rec_intro "recursion intro"
**/
template <typename Matrix>
recursator<Matrix> inline north_west(const recursator<Matrix>& rec)
{
    return rec.north_west();
}

/*! Compute the north-east quadrant of a recursator (i.e. its referred matrix).
    The result is itself a recursator.
    \sa \ref rec_intro "recursion intro"
**/
template <typename Matrix>
recursator<Matrix> inline north_east(const recursator<Matrix>& rec)
{
    return rec.north_east();
}

/*! Compute the south-west quadrant of a recursator (i.e. its referred matrix).
    The result is itself a recursator.
    \sa \ref rec_intro "recursion intro"
**/
template <typename Matrix>
recursator<Matrix> inline south_west(const recursator<Matrix>& rec)
{
    return rec.south_west();
}

/*! Compute the south-east quadrant of a recursator (i.e. its referred matrix).
    The result is itself a recursator.
    \sa \ref rec_intro "recursion intro"
**/
template <typename Matrix>
recursator<Matrix> inline south_east(const recursator<Matrix>& rec)
{
    return rec.south_east();
}

/*! Check if a recursator (i.e. its referred matrix) is empty.
    \sa \ref rec_intro "recursion intro"
**/
template <typename Matrix>
bool inline is_empty(const recursator<Matrix>& rec)
{
    return rec.is_empty();
}

/*! Check if a recursator (i.e. its referred matrix) fills the
    entire block, i.e. if the number of rows and columns are both
    equal to the virtual bound.
    \sa \ref rec_intro "recursion intro"
**/
template <typename Matrix>
bool inline is_full(const recursator<Matrix>& rec)
{
    return num_rows(rec) == rec.bound() && num_cols(rec) == rec.bound();
}

/*! The number of rows that a sub-matrix would have if it was constructed.
    \sa \ref rec_intro "recursion intro"
**/
template <typename Matrix>
typename recursator<Matrix>::size_type
inline num_rows(const recursator<Matrix>& rec)
{
    using std::min;
    typename recursator<Matrix>::size_type tmp= num_rows(rec.my_sub_matrix);
    return rec.my_first_row >= tmp ? 0 : min(rec.my_bound, tmp - rec.my_first_row);
}

/*! The number of columns that a sub-matrix would have if it was constructed.
    \sa \ref rec_intro "recursion intro"
**/
template <typename Matrix>
typename recursator<Matrix>::size_type
inline num_cols(const recursator<Matrix>& rec)
{
    using std::min;
    typename recursator<Matrix>::size_type tmp= num_cols(rec.my_sub_matrix);
    return rec.my_first_col >= tmp ? 0 : min(rec.my_bound, tmp - rec.my_first_col);
}

/*! The number of elements (rows times columns) that a sub-matrix would have if it was constructed.
    \sa \ref rec_intro "recursion intro"
**/
template <typename Matrix>
typename recursator<Matrix>::size_type
inline size(const recursator<Matrix>& rec)
{
    return num_rows(rec) * num_cols(rec);
}

}} // namespace mtl::matrix

#endif // MTL_MATRIX_RECURATOR_INCLUDE


/**
 * @file bidirectional_dijkstra_impl.hpp
 * @author Leonardo Arcari (leonardo1.arcari@gmail.com)
 * @version 1.0.0
 * @date 2018-10-28
 *
 * @copyright Copyright (c) 2018 Leonardo Arcari
 *
 * MIT License
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#ifndef KSPWLO_BIDIRECTIONAL_DIJKSTRA_IMPL_HPP
#define KSPWLO_BIDIRECTIONAL_DIJKSTRA_IMPL_HPP

#include <boost/graph/graph_concepts.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/properties.hpp>
#include <boost/graph/reverse_graph.hpp>
#include <boost/property_map/property_map.hpp>

#include <arlib/routing_kernels/visitor.hpp>
#include <arlib/type_traits.hpp>

#include <deque>
#include <iostream>
#include <limits>
#include <queue>
#include <unordered_map>
#include <vector>

namespace arlib {
/**
 * Implementations details of kSPwLO algorithms
 */
namespace details {
//===----------------------------------------------------------------------===//
//                      Bidirectional Dijkstra types
//===----------------------------------------------------------------------===//

/**
 * Enum to describe whether a forward or a backward step of bidirectional
 * dijkstra is executing.
 */
enum class Direction { forward = 1, backward };

/**
 * Returns the opposite direction.
 *
 * @param prev_dir The current execution direction.
 * @return Direction::forward if @p prev_dir == Direction::backward.
 *         Direction::backward otherwise.
 */
constexpr Direction switch_direction(Direction prev_dir) {
  return (prev_dir == Direction::backward) ? Direction::forward
                                           : Direction::backward;
}

/**
 * Return code of bi_dijkstra_step()
 */
enum class BiDijkStepRes {
  next = 1, /**< arlib::bidirectional_dijkstra() should execute another step. */
  end,      /**< arlib::bidirectional_dijkstra() should end. */
  negative_weights /**< arlib::bidirectional_dijkstra() should exit because
                        preconditions are violated. */
};

//===----------------------------------------------------------------------===//
//                      Bidirectional Dijkstra  aliases
//===----------------------------------------------------------------------===//
template <typename Vertex>
using PathMap =
    std::unordered_map<Vertex, std::vector<Vertex>, boost::hash<Vertex>>;

template <typename Vertex, typename Length>
using FringeElem = std::pair<Vertex, Length>;

template <typename Vertex, typename Length> struct FringeComparator {
  using Elem = FringeElem<Vertex, Length>;
  bool operator()(const Elem &lhs, const Elem &rhs) const {
    return lhs.second > rhs.second;
  }
};

template <typename Vertex, typename Length>
using Fringe = std::priority_queue<FringeElem<Vertex, Length>,
                                   std::vector<FringeElem<Vertex, Length>>,
                                   FringeComparator<Vertex, Length>>;

template <typename Vertex, typename Length>
using Seen = std::unordered_map<Vertex, Length, boost::hash<Vertex>>;
//===----------------------------------------------------------------------===//
//                      Bidirectional Dijkstra routines
//===----------------------------------------------------------------------===//
/**
 * Initialize a DistanceMap with @c infinite value for each vertex in the
 * graph.
 *
 * @post For each vertex @c v in @p G, <tt>distance[v] = +inf</tt>, given
 *       <tt>inf = std::numeric_limits<distance_type>::max()</tt>
 *
 * @tparam Graph A Boost::Graph.
 * @tparam DistanceMap The distance PropertyMap.
 * @param G The graph.
 * @param distance the DistanceMap to initialize.
 */
template <typename Graph, typename DistanceMap>
void init_distance_vector(Graph &G, DistanceMap distance) {
  using Length = typename boost::property_traits<DistanceMap>::value_type;
  constexpr Length inf = std::numeric_limits<Length>::max();

  for (auto [it, end] = vertices(G); it != end; ++it) {
    distance[*it] = inf;
  }
}

template <typename Vertex, typename PredecessorMap, typename BackPredecessorMap>
void merge_paths(Vertex s, Vertex t, Vertex w, PredecessorMap predecessor_f,
                 BackPredecessorMap predecessor_b,
                 std::deque<Vertex> &final_path) {
  final_path.clear();
  final_path.push_front(w);
  // Fill with forward partial result
  auto cur_f = w;
  while (cur_f != s) {
    auto pred_f = predecessor_f[cur_f];
    assert(pred_f != cur_f); // otherwise cur_f wouldnt be reachable, but if
                             // cur_f wouldnt be reachable we shouldnt be here.
    final_path.push_front(pred_f);
    cur_f = pred_f;
  }

  auto cur_b = w;
  while (cur_b != t) {
    auto pred_b = predecessor_b[cur_b];
    assert(pred_b != cur_b); // otherwise cur_b wouldnt be reachable, but if
                             // cur_b wouldnt be reachable we shouldnt be here.
    final_path.push_back(pred_b);
    cur_b = pred_b;
  }
}

template <typename Vertex, typename Length, typename PredecessorMap,
          typename BackPredecessorMap>
void compare_and_update_shortest_path(Seen<Vertex, Length> &seen_f,
                                      Seen<Vertex, Length> &seen_b,
                                      PredecessorMap predecessor_f,
                                      BackPredecessorMap predecessor_b,
                                      Vertex s, Vertex t, Vertex w,
                                      Length &final_distance,
                                      std::deque<Vertex> &final_path) {
  auto search_w_f = seen_f.find(w);
  auto search_w_b = seen_b.find(w);
  if (search_w_f != std::end(seen_f) && search_w_b != std::end(seen_b)) {
    auto total_distance = search_w_f->second + search_w_b->second;
    if (final_distance > total_distance) {
      final_distance = total_distance;
      // With predecessor map
      merge_paths(s, t, w, predecessor_f, predecessor_b, final_path);
    }
  }
}

template <typename Graph, typename PredecessorMap, typename DistanceMap,
          typename WeightMap, typename OtherPredecessorMap,
          typename OtherDistanceMap, typename BiDijkstraVisitorImpl,
          typename Vertex = vertex_of_t<Graph>,
          typename Length = value_of_t<DistanceMap>>
BiDijkStepRes bi_dijkstra_step(
    const Graph &G, Vertex s, Vertex t, PredecessorMap predecessor,
    DistanceMap distance, WeightMap weight, Fringe<Vertex, Length> &fringe,
    Seen<Vertex, Length> &seen, OtherPredecessorMap other_predecessor,
    OtherDistanceMap other_distance, Fringe<Vertex, Length> &other_fringe,
    Seen<Vertex, Length> &other_seen, Direction direction,
    Length &final_distance, std::deque<Vertex> &final_path,
    BiDijkstraVisitor<BiDijkstraVisitorImpl> &visitor) {
  constexpr Length inf = std::numeric_limits<Length>::max();
  // Extract closest node to expand
  const auto [v, dist] = fringe.top();
  fringe.pop();

  if (distance[v] != inf) {
    // Shortest path to 'node' already found. Continue.
    return BiDijkStepRes::next;
  }

  // Ask the visitor if vertex should be expanded
  auto lower_bound_v = other_fringe.top().second;
  if (!visitor.expand_vertex(v, dist, lower_bound_v, final_distance)) {
    return BiDijkStepRes::next;
  }

  // Update distance
  distance[v] = dist; // Equal to seen[v]
  if (other_distance[v] != inf) {
    // If we have scanned v in both directions we are done,
    // we have now discovered the shortest path. But the visitor may ask to keep
    // on searching.

    // Check terminating condition:
    auto min_dist = fringe.top().second;
    auto other_min_dist = other_fringe.top().second;

    if (visitor.terminating_condition(min_dist, other_min_dist,
                                      final_distance)) {
      return BiDijkStepRes::end;
    }
    // Please refer to:
    // Andreas Paraskevopoulos, Christos Zaroliagis. Improved Alternative Route
    // Planning. Daniele Frigioni and Sebastian Stiller. ATMOS - 13th Workshop
    // on Algorithmic Approaches for Transportation Modelling, Optimizations and
    // Systems - 2013
    // auto terminating_condition = min_dist + other_min_dist > d_s_t;
  }

  // Compute neighbor distances
  for (auto [it, end] = out_edges(v, G); it != end; ++it) {
    using boost::get;
    auto w = target(*it, G);
    auto min_weight = get(weight, *it);
    auto vw_length = distance[v] + min_weight;

    if (distance[w] != inf) {
      if (vw_length < distance[v]) {
        // Throw some exception "Contradictory paths found: negative weights?"
        return BiDijkStepRes::negative_weights;
      }
    } else if (auto search_w = seen.find(w);
               search_w == std::end(seen) || vw_length < search_w->second) {
      // Relax v-w edge
      seen.insert_or_assign(w, vw_length);
      fringe.push(std::make_pair(w, vw_length));

      // Build new path to w
      predecessor[w] = v;

      // See if this path is better than the already discovered shortests path
      if (direction == Direction::forward) {
        compare_and_update_shortest_path(seen, other_seen, predecessor,
                                         other_predecessor, s, t, w,
                                         final_distance, final_path);
      } else {
        compare_and_update_shortest_path(other_seen, seen, other_predecessor,
                                         predecessor, s, t, w, final_distance,
                                         final_path);
      }
    }
  }
  // Everything went fine, go with other direction
  return BiDijkStepRes::next;
}

template <typename PredecessorMap, typename Vertex>
void fill_predecessor(PredecessorMap predecessor,
                      const std::deque<Vertex> &final_path) {
  if (!final_path.empty()) {
    for (std::size_t i = 0; i < final_path.size() - 1; ++i) {
      predecessor[final_path[i + 1]] = final_path[i];
    }
  }
}
} // namespace details
} // namespace arlib

#endif


///////////////////////////////////////////////////////////////////////////////
// ars::sampler.hpp                                                          //
//                                                                           //
//  Copyright 2009 Erwann Rogard. Distributed under the Boost                //
//  Software License, Version 1.0. (See accompanying file                    //
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)         //
///////////////////////////////////////////////////////////////////////////////
#ifndef BOOST_STATISTICS_DETAIL_ARS_SAMPLER_HPP_ER_2009
#define BOOST_STATISTICS_DETAIL_ARS_SAMPLER_HPP_ER_2009
#include <boost/ars/error.hpp>
#include <boost/ars/proposal_sampler.hpp>

namespace boost{
namespace statistics{
namespace detail{
namespace ars{

// This class adds a rejection steop to proposal_sampler
//
// PropS is an instance of the class template proposal_sampler
template<typename PropS>
class sampler : public PropS{
    typedef PropS super_t;
    public:

    sampler():
    super_t(),n_max_reject_(super_t::param_::n_max_reject),n_reject_(0){}

    sampler(const sampler& that)
    :PropS(that),n_max_reject_(that.n_max_reject_),n_reject_(that.n_reject_)
    {}

    sampler& operator=(const sampler& that){
        if(&that!=this){
            PropS::operator=(that);
            n_max_reject_ = that.n_max_reject_;
            n_reject_ = that.n_reject_;
        }
        return *this;
    }

    void set_n_max_reject(unsigned n){ n_max_reject_ = n; }

    template<typename U>
    typename super_t::result_type operator()(U& u)const{
        static const char* method
            = "ars::sampler::operator()(U&) after n = %1%";
        typename super_t::result_type draw;
        for(typename super_t::size_type i = 0; i<n_max_reject(); i++){
            n_reject_ = i;
            if(this->sample(u,draw)){
                return draw;
            }
        }
        boost::format f(method); f%n_max_reject();
        throw exception(method,f.str(),*this);
    }
    typename super_t::size_type n_max_reject()const{ return n_max_reject_; }
    typename super_t::size_type n_reject()const{ return n_reject_; }

    protected:
    typename super_t::size_type n_max_reject_;
    mutable typename super_t::size_type n_reject_;
};

}// ars
}// detail
}// statistics
}// boost

#endif


/**********************************************************************************/
/* This file is part of spla project                                              */
/* https://github.com/JetBrains-Research/spla                                     */
/**********************************************************************************/
/* MIT License                                                                    */
/*                                                                                */
/* Copyright (c) 2021 JetBrains-Research                                          */
/*                                                                                */
/* Permission is hereby granted, free of charge, to any person obtaining a copy   */
/* of this software and associated documentation files (the "Software"), to deal  */
/* in the Software without restriction, including without limitation the rights   */
/* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell      */
/* copies of the Software, and to permit persons to whom the Software is          */
/* furnished to do so, subject to the following conditions:                       */
/*                                                                                */
/* The above copyright notice and this permission notice shall be included in all */
/* copies or substantial portions of the Software.                                */
/*                                                                                */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR     */
/* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,       */
/* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE    */
/* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER         */
/* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,  */
/* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE  */
/* SOFTWARE.                                                                      */
/**********************************************************************************/

#ifndef SPLA_SPLAINDICESTOROWOFFSETS_HPP
#define SPLA_SPLAINDICESTOROWOFFSETS_HPP

#include <boost/compute/algorithm.hpp>
#include <boost/compute/command_queue.hpp>

#include <algorithm>
#include <numeric>
#include <vector>

#include <spla-cpp/SplaConfig.hpp>

namespace spla {

    /**
     * @addtogroup Internal
     * @{
     */

    /**
     * @brief Compute row offsets from coo row indices buffer.
     *
     * Result offsets array has size n + 1.
     * Offsets[i] stores first index of row i in indices buffer.
     * Offsets[i + 1] - Offsets[i] equals number of nnz values in row i in indices buffer.
     * Offsets[n] equals number off values in indices buffer.
     * Lengths[i] equals number of nnz values in row i in indices buffer.
     *
     * @param indices Array of rows indices
     * @param[out] offsets Output array with offsets
     * @param[out] lengths Output array of row lengths
     * @param n Number of rows in matrix
     * @param queue Command queue to execute
     */
    inline void IndicesToRowOffsets(const boost::compute::vector<unsigned int> &indices,
                                    boost::compute::vector<unsigned int> &offsets,
                                    boost::compute::vector<unsigned int> &lengths,
                                    std::size_t n,
                                    boost::compute::command_queue &queue) {
        using namespace boost;

        lengths.resize(n + 1, queue);
        offsets.resize(n + 1, queue);
        compute::fill(lengths.begin(), lengths.end(), 0u, queue);

        if (indices.empty()) {
            compute::fill(offsets.begin(), offsets.end(), 0u, queue);
            return;
        }

        BOOST_COMPUTE_CLOSURE(void, countRowLengths, (unsigned int i), (indices, lengths), {
            uint rowId = indices[i];
            atomic_inc(&lengths[rowId]);
        });

        compute::for_each_n(compute::counting_iterator<unsigned int>(0), indices.size(), countRowLengths, queue);
        compute::exclusive_scan(lengths.begin(), lengths.end(), offsets.begin(), queue);
    }

    /**
     * Overload
     */
    inline void IndicesToRowOffsets(const boost::compute::vector<unsigned int> &indices,
                                    boost::compute::vector<unsigned int> &offsets,
                                    std::size_t n,
                                    boost::compute::command_queue &queue) {
        using namespace boost;
        boost::compute::vector<unsigned int> lengths(queue.get_context());
        IndicesToRowOffsets(indices, offsets, lengths, n, queue);
    }

    /**
     * @brief Compute row offsets from coo row indices buffer.
     *
     * Result offsets array has size n + 1.
     * Offsets[i] stores first index of row i in indices buffer.
     * Offsets[i + 1] - Offsets[i] equals number of nnz values in row i in indices buffer.
     * Offsets[n] equals number off values in indices buffer.
     * Lengths[i] equals number of nnz values in row i in indices buffer.
     *
     * @param indices Array of rows indices
     * @param[out] offsets Output array with offsets
     * @param[out] lengths Output array of row lengths
     * @param n Number of rows in matrix
     */
    inline void IndicesToRowOffsets(const std::vector<unsigned int> &indices,
                                    std::vector<Index> &offsets,
                                    std::vector<Index> &lengths,
                                    std::size_t n) {
        offsets.resize(n + 1);
        lengths.resize(n + 1);
        std::fill(lengths.begin(), lengths.end(), 0u);

        if (indices.empty()) {
            std::fill(offsets.begin(), offsets.end(), 0u);
            return;
        }

        std::for_each(indices.begin(), indices.end(), [&](auto i) { lengths[i] += 1; });
        std::exclusive_scan(lengths.begin(), lengths.end(), offsets.begin(), 0u);
    }

    /**
     * Overload
     */
    inline void IndicesToRowOffsets(const std::vector<unsigned int> &indices,
                                    std::vector<Index> &offsets,
                                    std::size_t n) {
        std::vector<unsigned int> lengths;
        IndicesToRowOffsets(indices, offsets, lengths, n);
    }

    /**
     * @}
     */

}// namespace spla

#endif//SPLA_SPLAINDICESTOROWOFFSETS_HPP


#ifndef GRAPH_HPP
#define GRAPH_HPP

using namespace std;

#ifdef MPI_ENABLED

//#include <cereal/types/map.hpp>
//#include <cereal/types/set.hpp>
//#include <cereal/types/vector.hpp>
//#include <cereal/access.hpp>

#include <boost/serialization/map.hpp>
#include <boost/serialization/set.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/access.hpp>

#else

#include <map>
#include <set>
#include <vector>

#endif

#include <fmt/format.h>
#include "util.hpp"

#include <climits>
#include <cmath> /* floor, ceil */
#include <cassert>
#include <mutex>
#include <random>
#include <sstream>

class Graph
{
private:
#ifdef MPI_ENABLED
	//friend class cereal::access;
	friend class boost::serialization::access;

#endif

	struct FoldedVertices
	{
		int u{-1};
		int v{-1};
		int w{-1};
		/*this id is negative so it does not get confused with
			positive vertices' names, then negative vertices in graph
			will be those ones who were folded*/
		//int id;

		FoldedVertices()
		{
		}

		FoldedVertices(int u, int v, int w)
		{
			this->u = u;
			this->v = v;
			this->w = w;
		}
#ifdef MPI_ENABLED
		// cereal
		//template <class Archive>
		//void serialize(Archive &ar, const unsigned int version)
		//{
		//	ar(u, v, w);
		//}

		//boost
		template <class Archive>
		void serialize(Archive &ar, const unsigned int version)
		{
			ar &u;
			ar &v;
			ar &w;
		}
#endif
	};

	void _addRowToList(int vec0)
	{
		this->adj.insert(pair<int16_t, set<int16_t>>(vec0, rows));
		this->rows.clear();
	}

	void _calculerVertexMaxDegree()
	{
		int DEG;
		/*Finding vertex degrees, in order to start exploring by these ones.*/
		if (!vertexDegree.empty())
		{
			vertexDegree.clear();
			idsMax.clear();
			max = INT_MIN;
		}

		for (auto const &[v, neighbours] : adj)
		{
			DEG = neighbours.size();
			this->vertexDegree.insert({v, DEG});
			if (DEG > max)
				max = DEG;
		}

		for (auto const &[v, neighbours] : adj)
		{
			if (vertexDegree[v] == max)
				idsMax.push_back(v);
		}
	}

	void _calculerVertexMinDegree()
	{
		int DEG;
		/*Finding vertex degrees, in order to start exploring by these ones.*/
		if (!vertexDegree.empty())
		{
			vertexDegree.clear();
			idsMin.clear();
			min = INT_MAX;
		}

		for (auto const &[v, neighbours] : adj)
		{
			DEG = neighbours.size();
			this->vertexDegree.insert({v, DEG});
			if (DEG < min)
				min = DEG;
		}

		for (auto const &[v, neighbours] : adj)
		{
			if (vertexDegree[v] == min)
				idsMin.push_back(v);
		}
	}

	void _updateVertexDegree()
	{
		//Recalculating the vertex with maximum number of edges
		int max_tmp = INT_MIN;
		int min_tmp = INT_MAX;

		for (auto &[v, degree] : vertexDegree)
		{
			if (degree > max_tmp)
				max_tmp = degree;

			if (degree < min_tmp)
				min_tmp = degree;
		}

		max = max_tmp;
		min = min_tmp;
		idsMax.clear();
		idsMin.clear();
		/*storing position of highest degree vertices within adjacency list*/
		for (auto &[v, degree] : vertexDegree)
		{
			if (degree == max)
				this->idsMax.push_back(v);
			if (degree == min)
				this->idsMin.push_back(v);
		}
	}

	int _getRandomVertex(std::vector<int16_t> &target)
	{
		/*Here this will explore the list of higest degree vertices and
			it will choose any of them randomly*/

		std::random_device rd;	// Will be used to obtain a seed for the random number engine
		std::mt19937 gen(rd()); // Standard mersenne_twister_engine seeded with rd()
		std::uniform_int_distribution<> distrib(0, target.size() - 1);

		int random = distrib(gen);
		return target[random];
	}

	void _readEdgesFromGraph()
	{

		int _counterEdges = 0;
		auto adj_cpy = this->adj;
		auto it = adj_cpy.begin();

		while (it != adj_cpy.end())
		{
			_counterEdges += it->second.size();
			auto it2 = it->second.begin();
			while (it2 != it->second.end())
			{
				adj_cpy[*it2].erase(it->first);
				++it2;
			}
			++it;
		}
		this->numEdges = _counterEdges;
	}

	/*Preprocessing methods*/
	/*An isolated vertex (one of degree zero) cannot be in a vertex
	cover of optimal size. Because there are no edges incident upon
	such a vertex, there is no benefit in including it in any cover.
	Thus, in G0, an isolated vertex can be eliminated, reducing n0 by one.
	This rule is applied repeatedly until all isolated
	vertices are eliminated.*/
	bool _rule1(map<int16_t, set<int16_t>> &adj)
	{
		std::vector<int16_t> degree_zero;

		// this loop finds vertices of degree zero
		for (auto const &[v, neighbours] : adj)
		{
			if (neighbours.size() == 0)
				degree_zero.push_back(v);
		}

		if (degree_zero.size() == 0)
			return false; // no vertices then no changes where made

		// this loop removes vertices of zero degree from the adjacency list
		for (auto &vertex : degree_zero)
		{
			adj.erase(vertex);
			vertexDegree.erase(vertex);
		}
		return true;
	}

	/*In the case of a pendant vertex (one of degree one), there is
	an optimal vertex cover that does not contain the pendant vertex
	but does contain its unique neighbor. Thus, in G0, both the pendant
	vertex and its neighbor can be eliminated. This also eliminates any
	additional edges incident on the neighbor, which may leave isolated
	vertices for deletion under Rule 1. This reduces n0 by the number
	of deleted vertices and reduces k by one. This rule is applied repeatedly
	until all pendant vertices are eliminated.*/
	bool _rule2(map<int16_t, set<int16_t>> &adj, int16_t &added_to_cover)
	{
		vector<int16_t> pendant_neighbour;
		vector<pair<int16_t, int16_t>> matching_ccs;

		for (auto &[v, neighbours] : adj)
		{
			if (neighbours.size() == 1)
			{
				int w = *neighbours.begin(); // only neighbour of v
				pendant_neighbour.push_back(w);

				//special case : what if the neighbor also has degree 1?
				if (adj[w].size() == 1) // v is only neighbour of w?
				{
					if (v < w) //not add the same matching twice
						matching_ccs.push_back(make_pair(v, w));
				}
				else
				{
					if (!_cover.contains(w)) // avoid added_to_cover++ twice for the same pendant vertex
					{
						_cover.insert(w);
						added_to_cover++;
					}
				}
			}
		}

		if (pendant_neighbour.size() == 0 && matching_ccs.size() == 0)
			return false; // no vertices then no changes where made

		for (auto &match : matching_ccs)
		{
			_cover.insert(match.first);
			this->erase(adj, match.first); // only one is erased, since erase(..) has its own way to handle counter
			//this->erase(adj, match.second); // this will be handled by _rule1(..) since it is  iterative
			added_to_cover++;
		}

		for (auto &neighbour : pendant_neighbour)
		{
			this->erase(adj, neighbour);
		}

		return true;
	}

	/* this rule states that having a vertex u with two adjacent neighbours v and w,
		then v and w will be in the MVC*/
	bool _rule3(map<int16_t, set<int16_t>> &adj, int16_t &added_to_cover)
	{
		/*		u ---- v ~~~
				 \	  /
				  \  /
				   w ~~~
		*/
		bool flag = false;
		std::once_flag oo_flag;

		auto ite = adj.begin();
		while (ite != adj.end())
		{
			if (ite->second.size() == 2)
			{
				int16_t u = ite->first;
				auto it = ite->second.begin();
				int16_t v = *it;
				it++;
				int16_t w = *it;

				if (adj[v].contains(w) && adj[w].contains(v))
				{
					_cover.insert(v); // u is included in the MVC
					_cover.insert(w); // w is included in the MVC
					added_to_cover += 2;

					erase(adj, u); // u is discarded from the MVC
					erase(adj, v); // v is removed from list since it was already included in the MVC
					erase(adj, w); // w is removed from list since it was already included in the MVC

					flag = true; // graph has been changed
					//reset iterator in here
					ite = adj.begin(); // loop reseted since after performing deletions, graph might end up with more similar cases
					// also the iterator breaks
					continue;
				}
				ite++;
			}
			else
				ite++;
		}

		return flag;
	}

	bool _rule4(map<int16_t, set<int16_t>> &adj)
	{
		/*		u ---- v ~~~
				 \				==>  ~~~(u')~~~
				  \
				   w ~~~
		*/

		auto it = adj.begin();
		map<int16_t, set<int16_t>> folded_vertices;
		std::once_flag oo_flag;

		int16_t id = -1;

		bool flag = false;

		while (it != adj.end())
		{
			if (it->second.size() == 2)
			{
				int16_t u = it->first;
				/*adjacent neighbours*/
				set<int16_t>::const_iterator an = it->second.begin();
				int16_t v = *an;
				an++;
				int16_t w = *an;
				if (!adj[v].contains(w) && !adj[w].contains(v))
				{
					//Check to not fold already folded vertices
					if (it->first < 0 || v < 0 || w < 0)
					{
						it++;
						continue;
					}

					//Create (u')
					FoldedVertices u_prime(u, v, w);
					//push to a adj of all the folded vertices
					foldedVertices.insert(pair<int16_t, FoldedVertices>(id, u_prime));

					//Check neighbours of v
					set<int16_t> foldedNeigbours;
					for (auto vertex : adj[v])
					{
						if (vertex != u)
							foldedNeigbours.insert(vertex);
					}

					//Check neighbours of w
					for (auto vertex : adj[w])
					{
						if (vertex != u)
							foldedNeigbours.insert(vertex);
					}

					//Erase u,v and w from the graph
					erase(adj, u);
					erase(adj, v);
					erase(adj, w);

					//Insert (u') into graph
					adj.insert(pair<int16_t, set<int16_t>>(id, foldedNeigbours));
					//link the neighbours of v and w to (u')
					for (auto f_vertex : foldedNeigbours)
					{
						adj[f_vertex].insert(id);
						numEdges++;
					}

					// post-processed graph has two fewer edges due to the folding
					//numEdges -= 2;

					id--;
					// graph was changed
					flag = true;
					// restar iterator

					it = adj.begin();
					continue;
				}

				it++;
			}
			else
				it++;
		}
		return flag;
	}

	void erase(map<int16_t, set<int16_t>> &adj, int16_t v)
	{
		try
		{
			// this loop removes vertex v from all its neighbours
			for (auto &vertex : adj[v])
			{
				adj[vertex].erase(v);
				this->vertexDegree[vertex]--;
			}
			numEdges -= adj[v].size();	 // number of edges reduced by the number of neighbours that v had
			adj.erase(v);				 // vertex v totally removed from adj list
			this->vertexDegree.erase(v); // removed from the vertexDegree list as well
		}
		catch (const std::exception &e)
		{
			std::stringstream ss;
			ss << "Exception while erasing vertex from adj"
			   << '\n'
			   << e.what() << '\n';

			std::cerr << ss.str();
		}
	}

	void build(int n, double p)
	{
		int maxEdgesPossible = n * (n - 1) / 2;
		int maxEdgesPerNode = maxEdgesPossible / n;
		this->max = 0;
		this->min = 0;
		this->numEdges = 0;
		this->numVertices = 0;

		double _p = p * (double)maxEdgesPerNode / 100.0;

		int r = 0, m = 0;

		//srand(time(NULL)); //this commented to obtain always the same graph
		// Build the edges
		std::random_device rd;	// Will be used to obtain a seed for the random number engine
		std::mt19937 gen(rd()); // Standard mersenne_twister_engine seeded with rd()

		for (int i = 0; i < n; i++)
		{
			for (int j = i + 1; j < n; j++)
			{

				std::uniform_int_distribution<> distrib(1, n);
				r = distrib(gen);
				//r = rand() % n + 1;

				//if (r <= _p) {
				if (r <= p)
				{
					++m;
					adj[i].insert(j);
					adj[j].insert(i);
				}
			}
		}

		/*This second loop is used only if adj.size() != n*/
		// rand()%a + b => interval-> [b, b + a)
		if (adj.size() < (size_t)n)
		{
			for (int i = 0; i < n; i++)
			{
				if (!adj.contains(i))
				{
					m++;
					int interval1 = i - 0;
					int interval2 = n - i;

					int low_bnd;
					int upp_bnd;
					int j;

					if (interval1 > interval2)
					{
						upp_bnd = i - 1;
						std::uniform_int_distribution<> distrib(0, upp_bnd);
						int tmp = distrib(gen);
						//int tmp = rand() % upp_bnd;
						j = tmp;
					}
					else
					{
						low_bnd = i + 1;
						upp_bnd = n - (i + 1);
						std::uniform_int_distribution<> distrib(low_bnd, upp_bnd);
						int tmp = distrib(gen);
						//int tmp = rand() % upp_bnd + low_bnd;
						j = tmp;
					}
					adj[i].insert(j);
					adj[j].insert(i);
					//if (adj.size() > n)
					//{
					//	int var = 5;
					//}
				}
				if (adj.size() == (size_t)n)
					break;
			}
		}

		this->numEdges = m;
		this->numVertices = n;
		/*begin<----------4 testing purposes-------*/
		double mean = (double)m / n;
		//double density = mean / (double)maxEdgesPerNode;
	}

public:
	bool empty()
	{
		return _cover.empty();
	}
	//Default constructor
	Graph()
	{
		this->max = 0;
		this->min = 0;
		this->numEdges = 0;
		this->numVertices = 0;

		//const size_t BOUND = 1000000;

		//std::random_device rd;	// Will be used to obtain a seed for the random number engine
		//std::mt19937 gen(rd()); // Standard mersenne_twister_engine seeded with rd()
		//std::uniform_int_distribution<> distrib(0, BOUND);
		//
		//for (size_t i = 0; i < BOUND; i++)
		//{
		//	int random = distrib(gen);
		//	memory_hog.push_back(random);
		//}
	}

	//Parameterized constructor
	//N size, p propability out of 100
	Graph(int n, double p)
	{
		build(n, p);
		/*------------------------------------->end*/
		_calculerVertexMaxDegree();
		_calculerVertexMinDegree();
	}

	void addNeighbour(int val)
	{
		this->rows.insert(val);
	}

	// removes all vertices of degree zero, it returns the number of removed vertices
	int removeZeroVertexDegree()
	{
		int size0 = adj.size();
		_rule1(this->adj);
		int size1 = adj.size();
		return abs(size0 - size1);
	}

	void removeEdge(const int &v, const int &w)
	{
		adj[v].erase(w);
		adj[w].erase(v);

		vertexDegree[v]--;
		vertexDegree[w]--;

		if (adj[v].size() == 0)
			_zeroVertexDegree.insert(v);
		if (adj[w].size() == 0)
			_zeroVertexDegree.insert(w);

		numEdges--;
		_updateVertexDegree();
	}

	int removeVertex(int v)
	{
		try
		{
			if (!adj.contains(v))
			{
				fmt::print("_VERTEX_NOT_FOUND\n");
				throw "_VERTEX_NOT_FOUND";
			}
			numEdges = numEdges - adj[v].size();

			/*Here we explore all the neighbours of v, and then we find
			vertex v inside of those neighbours in order to erase v of them*/
			for (auto &neighbour : adj[v])
			{
				adj[neighbour].erase(v);
				if (adj[neighbour].size() == 0)
				{
					// store temporary position of vertices that end up with no neighbours
					this->_zeroVertexDegree.insert(neighbour);
				}
				this->vertexDegree[neighbour]--;
			}

			/*After v is been erased from its neighbours, then v is erased
			from graph and the VertexDegree is updated*/
			this->adj.erase(v);
			this->vertexDegree.erase(v);

			this->_cover.insert(v);
			_updateVertexDegree();

			numVertices = adj.size();
		}
		catch (const std::exception &e)
		{
			std::stringstream ss;
			ss << "Exception while removing vertex : "
			   << v
			   << '\n'
			   << e.what() << '\n';
			std::cerr << ss.str();
		}
		return 0;
	}

	// it returns the number of neighbours that were removed
	int removeNv(int v)
	{
		std::set<int16_t> neighboursOfv(adj[v]); //copy of neigbours of vertex v
		int numNeighours = neighboursOfv.size();
		for (auto vertex : neighboursOfv)
		{
			if (adj.contains(vertex))
			{
				this->_cover.insert(vertex);
				removeVertex(vertex);
			}
		}
		return numNeighours;
	}

	void readGraph(string NameOfFile, string directory)
	{
		using namespace std;
		string line;
		vector<string> split;
		int i = 0;
		int _counter_vertices = 0;

		while (1)
		{
			line = Util::GetFileLine(directory + NameOfFile, i);
			if (line == "")
				break;
			split = Util::Split(line, "\t");

			for (int i = 1; i != split.size(); i++)
			{
				addNeighbour(Util::ToInt(split[i]));
			}
			_addRowToList(Util::ToInt(split[0]));
			_counter_vertices++;

			i++;
		}
		_calculerVertexMaxDegree();
		_readEdgesFromGraph();
		//Graph::currentMVCSize = adj.size();
		this->numVertices = _counter_vertices;
	}

	void readDimacs(string &NameOfFile)
	{
		std::fstream file;
		file.open(NameOfFile, ios::in);

		if (!file.is_open())
			throw "File not found";

		std::string line;

		while (std::getline(file, line))
		{
			if (line[0] == 'e')
			{
				auto split = Util::Split(line, " ");
				int u = std::stoi(split[1]);
				int v = std::stoi(split[2]);
				add_edge(u, v);
				numEdges++;
			}
		}
		numVertices = adj.size();
		_calculerVertexMaxDegree();
		_calculerVertexMinDegree();

		return;
	}

	void add_edge(int u, int v)
	{
		adj[u].insert(v);
		adj[v].insert(u);
	}

	void readEdges(string NameOfFile)
	{

		std::ifstream file(NameOfFile);

		if (!file.is_open())
		{
			throw std::runtime_error("Input file not found\n");
		}

		int u, v;
		int i = 0;
		while (!file.eof())
		{
			i++;
			file >> u >> v;
			adj[u].insert(v);
			adj[v].insert(u);
		}

		numEdges = i;
		numVertices = adj.size();

		/*4 testing*/
		double mean = (double)numEdges / (double)numVertices;
		//double prob = mean * 100 / (double)numVertices;

		double maxEdgesPossible = numVertices * (numVertices - 1) / 2;
		double maxEdgesPerNode = maxEdgesPossible / (double)numVertices;
		//double density = mean / maxEdgesPerNode;

		_calculerVertexMaxDegree();
		_calculerVertexMinDegree();
	}

	/*It explores the highest degree edges and choses whether
		the first one in the list or arbitrarily*/
	[[nodiscard]] int id_max(bool random = true)
	{
		return random ? _getRandomVertex(this->idsMax) : idsMax[0];
	}

	[[nodiscard]] int id_min(bool random = true)
	{
		return random ? _getRandomVertex(this->idsMin) : idsMin[0];
	}

	[[nodiscard]] int d_max()
	{
		return max;
	}

	[[nodiscard]] int d_min()
	{
		return min;
	}

	//Returns graph's size
	int size()
	{
		return this->adj.size();
	}

	//returns cover size

	int coverSize()
	{
		return this->_cover.size();
	}

	//BETA:...
	bool isCovered()
	{
		return numEdges == 0 ? 1 : 0;
	}

	//gets neighbours of v, Nv(v) = {w1,w2, ... ,wi}
	std::set<int16_t> &operator[](const int16_t v)
	{
		if (!adj.contains(v))
			throw "_VERTEX_NOT_FOUND";
		else
			return adj[v];
	}

	typedef std::map<int16_t, set<int16_t>>::iterator iterator;

	iterator begin() { return adj.begin(); }
	iterator end() { return adj.end(); }

	size_t preprocessing()
	{

		clean_graph(INT_MAX);
		auto _adj = this->adj;
		bool flag = true;
		flag = _rule4(_adj);
		this->adj = _adj;
		clean_graph(INT_MAX);

		_readEdgesFromGraph();
		_calculerVertexMaxDegree();
		_calculerVertexMinDegree();

		this->numVertices = this->adj.size();

		return adj.size();
	}

	int clean_graph()
	{
		int16_t added_to_cover = 0;
		bool flag = true;
		bool flag2 = true;
		while (flag2)
		{
			if (flag2)
			{
				flag = true;
				while (flag)
				{
					flag = _rule1(this->adj);
					flag = _rule2(this->adj, added_to_cover);
				}
			}

			flag2 = _rule3(this->adj, added_to_cover);
		}

		this->_zeroVertexDegree.clear();
		_updateVertexDegree();
		numVertices = adj.size();
		return added_to_cover;
	}

	int clean_graph(int16_t k)
	{
		int16_t added_to_cover = 0;
		bool flag = true;
		bool flag2 = true;
		while (flag2)
		{
			if (flag2)
			{
				flag = true;
				while (flag)
				{
					flag = _rule1(this->adj);
					flag = _rule2(this->adj, added_to_cover);
				}
			}

			flag2 = _rule3(this->adj, added_to_cover);
		}

		this->_zeroVertexDegree.clear();
		_updateVertexDegree();
		numVertices = adj.size();
		return added_to_cover;
	}

	int clean_graph2()
	{
		int16_t added_to_cover = 0;
		bool flag = true;

		while (flag)
		{
			flag = _rule1(this->adj);
			flag = _rule2(this->adj, added_to_cover);
		}

		this->_zeroVertexDegree.clear();
		_updateVertexDegree();
		return added_to_cover;
	}

	std::set<int16_t> cover()
	{
		return _cover;
	}

	std::set<int16_t> postProcessing()
	{
		//_cover.insert(-2);
		auto it = _cover.begin();
		set<int16_t> unfolded_vertices;
		/*If a vertex is negative, it means it was folded, then we look up
			the foldedVertices to unfold it*/
		while (it != _cover.end())
		{
			/*It finds folded vertices (u'), which are negatives,
				if (u') is included in the cover, then, vertices
				u and v must be present in the cover*/
			if (*it < 0)
			{
				unfolded_vertices.insert(foldedVertices[*it].v);
				unfolded_vertices.insert(foldedVertices[*it].w);
				foldedVertices.erase(*it);
				_cover.erase(*it);
				it = _cover.begin();
			}
			else
				it++;
		}

		/* ******************************************************** */

		/* if (u') was not included in the cover, then u must be
			present in the cover */
		auto i = foldedVertices.begin();
		while (i != foldedVertices.end())
		{
			unfolded_vertices.insert(i->second.u);
			foldedVertices.erase(i->first);
			i = foldedVertices.begin();
		}

		/* ******************************************************** */

		/*Build minimum vertex cover*/
		auto j = unfolded_vertices.begin();

		while (j != unfolded_vertices.end())
		{
			this->_cover.insert(*j);
			j++;
		}
		return _cover;
	}

	int max_k()
	{
		if (!adj.empty())
		{
			//double mxDegrees = list[idsMax[0]].size();
			//return floor((double)list.size() / (1.0 + (1.0 / (mxDegrees))));
			return ceil((double)adj.size() / (1.0 + (1.0 / (max))));
		}
		return 0;
	}

	int min_k()
	{
		if (!adj.empty())
		{
			//	double mxDegrees = list[idsMax[0]].size();
			//	double minDegrees = list[idsMin[0]].size();
			//return floor((double)list.size() / (1.0 + (mxDegrees / minDegrees)));
			return floor((double)adj.size() / (1.0 + ((double)max / (double)min)));
		}
		return 0;
	}

	int getNumEdges()
	{
		return this->numEdges;
	}

	void build_graph(int SIZE, int p)
	{

		build(SIZE, p);
	}

	void print_edges(std::ofstream &file)
	{
		auto it = adj.begin();

		/*Fix this to not printing duplicated edges*/

		while (!adj.empty())
		{
			auto it2 = (*it).second.begin();
			while (!(*it).second.empty())
			{
				file << (*it).first << "\t" << *it2;
				int v = (*it).first;
				int w = *it2;
				removeEdge(v, w);

				if (!(*it).second.empty())
					file << endl;

				it2 = (*it).second.begin();
			}
			removeZeroVertexDegree();
			if (!adj.empty())
				file << endl;

			it = adj.begin();
		}
	}

	int find_vi(std::vector<int> &vi)
	{
		auto adj_cpy = adj;
		size_t sum = 0;
		size_t MAX = max;
		size_t edges_p = numEdges;

		auto findMax = [&adj_cpy]()
		{
			int max = 0;

			for (auto &[key, val] : adj_cpy)
			{
				if (val.size() > max)
					max = val.size();
			}
			return max;
		};

		auto iterator = adj_cpy.begin();
		while (iterator != adj_cpy.end())
		{
			int key = iterator->first;
			auto &neigbours = iterator->second;
			if (neigbours.size() == MAX)
			{
				vi.push_back(key);
				sum += neigbours.size();

				edges_p -= neigbours.size();

				for (auto &neighbour : neigbours)
				{
					adj_cpy[neighbour].erase(key);
				}
				adj_cpy.erase(key);
				MAX = findMax();

				if (sum >= numEdges)
					break;

				iterator = adj_cpy.begin();
			}
			else
				iterator++;
		}

		return 0;
	}

	int DegLB()
	{
		if (adj.size() == 0)
			return 0;

		std::vector<int> vi;
		auto adj_cpy = adj;
		size_t sum = 0;

		size_t edges_p = numEdges;

		auto findMaxVertex = [&adj_cpy]()
		{
			int maxdeg_v = adj_cpy.begin()->second.size();

			for (auto &[key, val] : adj_cpy)
			{
				if (val.size() > adj_cpy[maxdeg_v].size())
					maxdeg_v = key;
			}
			return maxdeg_v;
		};

		auto iterator = adj_cpy.begin();
		while (true)
		{
			//todo : use priority queue to maintain max degree guy
			int max_v = findMaxVertex();

			vi.push_back(max_v);
			sum += adj[max_v].size(); //neigbours.size();

			edges_p -= adj_cpy[max_v].size();

			for (auto &neighbour : adj_cpy[max_v])
			{
				adj_cpy[neighbour].erase(max_v);
			}
			adj_cpy.erase(max_v);

			if (sum >= numEdges)
				break;
		}

		if (edges_p <= 0 || adj_cpy.size() == 0)
			return vi.size();

		int nextv = findMaxVertex();

		if (adj[nextv].size() == 0)
			return vi.size();

		return vi.size() + edges_p / adj[nextv].size();
	}

	int antiColoringLB()
	{
		if (adj.size() == 0)
			return 0;

		map<int, int> colors;
		int maxcol = 0;

		for (auto it = adj.begin(); it != adj.end(); ++it)
		{
			int v = it->first;
			set<int> taken;
			for (auto it2 = adj.begin(); it2 != adj.end(); ++it2)
			{
				int w = it2->first;
				if (w >= v)
					break;
				if (!adj[v].contains(w) && colors.contains(w))
				{
					taken.insert(colors[w]);
				}
			}
			for (int i = 0; i < adj.size(); ++i)
			{
				if (!taken.contains(i))
				{
					//cout<<"color "<<v<<" with "<<i<<endl;
					colors[v] = i;
					maxcol = std::max(maxcol, i);
					break;
				}
			}
		}
		int isub = maxcol + 1;
		return adj.size() - isub;
	}

	/*		TEMPORARY		*/

	std::vector<std::vector<int16_t>> ADJ_MATRIX()
	{

		int16_t N = adj.size();
		std::vector<std::vector<int16_t>> tmp(N, std::vector<int16_t>(N, 0));

		auto it = adj.begin();
		while (it != adj.end())
		{
			auto jt = it->second.begin();
			while (jt != it->second.end())
			{
				tmp[it->first][*jt] = 1;
				jt++;
			}
			it++;
		}
		return tmp;
	}

	std::vector<int16_t> DEGREE()
	{
		std::vector<int16_t> tmp;

		auto it = adj.cbegin();

		while (it != adj.cend())
		{
			tmp.push_back(it->second.size());
			it++;
		}
		return tmp;
	}

	//Graph(const Graph &) = default;
	//Graph(Graph &&) = default;
	//Graph &operator=(const Graph &) = default;
	//Graph &operator=(Graph &&) = default;
	//virtual ~Graph() = default;
#ifdef MPI_ENABLED
	/*
	template <class Archive>
	void serialize(Archive &ar)
	{
		ar(max,
		   min,
		   idsMax,
		   idsMin,
		   adj,
		   rows,
		   vertexDegree,
		   _zeroVertexDegree,
		   foldedVertices,
		   _cover,
		   numEdges,
		   numVertices);
	} */

	template <class Archive>
	void serialize(Archive &ar, const unsigned int version)
	{
		ar &max;
		ar &min;
		ar &idsMax;
		ar &idsMin;
		ar &adj;
		//ar &rows;
		ar &vertexDegree;
		//ar &_zeroVertexDegree;
		ar &foldedVertices;
		ar &_cover;
		ar &numEdges;
		ar &numVertices;
	}
#endif

	std::map<int16_t, std::set<int16_t>> adj; /*Adjacency list*/

private:
	int max;								 /*Highest degree within graph*/
	int min;								 /*Lowest degree within graph*/
	std::vector<int16_t> idsMax;			 /*Stores the positions of max degree vertices within the adjacency adj*/
	std::vector<int16_t> idsMin;			 /*same as above but for min degree*/
	std::set<int16_t> rows;					 /*Temporary variable to store*/
	std::map<int16_t, int16_t> vertexDegree; /*list of vertices with their corresponding
									number of edges*/
	std::set<int16_t> _zeroVertexDegree;	 /*List of vertices with zero degree*/

	std::map<int16_t, FoldedVertices> foldedVertices;
	std::set<int16_t> _cover;

	int numEdges;	 //number of edges
	int numVertices; //number of vertices
};

#endif