// Copyright 2009-2010 Green Code LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <algorithm>
#include <cmath>
#include <iostream>

#include <boost/foreach.hpp>
#include <boost/make_shared.hpp>

#include "tpsdemo/linalg3d-double.h"

#include "DisjointRegions.h"
#include "Geometry3D.h"
#include "Grid.h"
#include "IPointVector.h"
#include "LineIndent.h"
#include "ProgressBar.h"
#include "RasterSurface.h"

namespace mcc
{

  DisjointRegions::DisjointRegions()
    : iterationState_(RegionIteration_Done)  {
  }

  //---------------------------------------------------------------------------

  const unsigned int desiredPtsPerRegion = 12;  // Default in ArcGIS Spline

  //---------------------------------------------------------------------------

  // Represents a point that's in a neighboring region.
  struct NeighborPoint
  {
    const IPoint * point;
    double distance;

    NeighborPoint(const IPoint *             neighborPt,
                  const CoordinateInterval & xInterval,
                  const CoordinateInterval & yInterval)
      : point(neighborPt)
    {
      // Compute distance from region specified by x and y intervals.
      double xDistance = distanceToInterval(point->x(), xInterval);
      double yDistance = distanceToInterval(point->y(), yInterval);
      distance = std::max(xDistance, yDistance);
    }

    double distanceToInterval(Coordinate                 value,
                              const CoordinateInterval & interval)
    {
      if (value < interval.lowerBound())
        return interval.lowerBound() - value;
      else if (interval.upperBound() < value)
        return value - interval.upperBound();
      else
        return 0.0;
    }

    bool operator<(const NeighborPoint & rhs) const
    {
      return distance < rhs.distance;
    }
  };

  //---------------------------------------------------------------------------

  // Functor class for adding a neighbor point to a region's point list.
  class AppendPoint
  {
    public:
      AppendPoint(std::vector<const IPoint *> & points)
        : points_(points)
      {
      }

      void operator()(const NeighborPoint & neighbor) const
      {
        points_.push_back(neighbor.point);
      }

    private:
      std::vector<const IPoint *> & points_;
  };

  //---------------------------------------------------------------------------

  // Location of a cell's neighbor relative to the cell itself.
  struct RelativeLocation
  {
    int rowOffset;
    int columnOffset;

    RelativeLocation(int rOffset,
                     int cOffset)
      : rowOffset(rOffset),
        columnOffset(cOffset)
    {
    }

    RelativeLocation & operator+=(const RelativeLocation & relLoc)
    {
      rowOffset += relLoc.rowOffset;
      columnOffset += relLoc.columnOffset;
      return *this;
    }
  };

  //---------------------------------------------------------------------------

  // Get the neighboring region to a particular region (specified by its cell).
  // If the relative location of the neighbor is outside the grid of regions,
  // 0 is returned.

  const InterpolationRegion * getNeighbor(Grid<InterpolationRegion> &  regions,
                                          const Cell &                 cell,
                                          const RelativeLocation       neighborLocation)
  {
    long row = cell.row() + neighborLocation.rowOffset;
    if (row < 0 || row >= (long) regions.rows())
      return 0;
    long column = cell.column() + neighborLocation.columnOffset;
    if (column < 0 || column >= (long) regions.columns())
      return 0;
    return &(regions((unsigned int) row, (unsigned int) column));
  }

  //---------------------------------------------------------------------------

  // Get points from the regions in the outermost ring of a region's
  // neighborhood.  The ring is traversed in clock-wise order:
  //
  //   +---+---+---+---+---+
  //   | P | A | B | C | D |
  //   +---+---+---+---+---+
  //   | O | 8 | 1 | 2 | E |
  //   +---+---+---+---+---+
  //   | N | 7 | * | 3 | F |
  //   +---+---+---+---+---+
  //   | M | 6 | 5 | 4 | G |
  //   +---+---+---+---+---+
  //   | L | K | J | I | H |
  //   +---+---+---+---+---+
  //
  //  For the region marked "*" in the diagram above, if its neighborhood is
  //  3x3, then the eight regions marked 1 to 8 are traversed.  If the
  //  neighborhood is 5x5, then the regions marked "A" to "P" are traversed.
  //
  //  The points in the traversed neighbors are appended to the "neighborPts"
  //  parameter.
  void getPointsFromOuterRing(Grid<InterpolationRegion> &  regions,
                              const Cell &                 cell,
                              int                          neighborhoodSize,
                              std::vector<NeighborPoint> & neighborPts)
  {
    CoordinateInterval xInterval = regions.getXInterval(cell.column()); // = cell.xInterval();
    CoordinateInterval yInterval = regions.getYInterval(cell.row());    // = cell.yInterval();

    RelativeLocation traverseRingAlongEdges[] = {
      RelativeLocation( 0,       right(1) ),  // top edge, left to right
      RelativeLocation( down(1), 0        ),  // right edge, top to bottom
      RelativeLocation( 0,       left(1)  ),  // bottom edge, right to left
      RelativeLocation( up(1),   0        ),  // left edge, bottom to top
    };

    // The upper left cell in current neighborhood ring
    int neighborhoodRadius = (neighborhoodSize - 1) / 2;
    RelativeLocation neighborLocation(up(neighborhoodRadius), left(neighborhoodRadius));

    int nCellsPerEdge = neighborhoodSize - 1;

    BOOST_FOREACH(const RelativeLocation & locOfNextNeighborAlongEdge, traverseRingAlongEdges) {
      for (int i = 0; i < nCellsPerEdge; ++i) {
        neighborLocation += locOfNextNeighborAlongEdge;
        const InterpolationRegion * neighbor = getNeighbor(regions, cell, neighborLocation);
        if (neighbor) {
          BOOST_FOREACH(const IPoint * point, neighbor->pts) {
            neighborPts.push_back(NeighborPoint(point, xInterval, yInterval));
          }
        }
      }
    }
  }

  //---------------------------------------------------------------------------

  int DisjointRegions::subdivide(const IPointVector &  points,
                                 PointSelector         pointSelector,
                                 const RasterSurface & raster)
  {
    // Subdivide the raster's area into non-overlapping regions.  The goal is
    // to have approximately the same # of points in each region.

    int desiredNumRegions = points.count() / desiredPtsPerRegion;
      // Rounding down because one less region means possibly more pts per
      // region.

    double rasterHeight = raster.rows() * raster.cellSize();
    double rasterWidth = raster.columns() * raster.cellSize();
    double rasterArea = rasterHeight * rasterWidth;
    double desiredRegionArea = rasterArea / desiredNumRegions;

    // The size of a region if it was the optimal shape = square
    double desiredRegionSize = std::sqrt(desiredRegionArea);

    // But the desired region size is not guaranteed to divide both the height
    // and width of the raster evenly.  So stretch the regions' heights and
    // widths until they evenly divide the raster's height and width,
    // respectively.  Stretching ensures that regions do not lose points.  The
    // resulting region shape is likely not to be square.
    int nRows = int(std::floor(rasterHeight / desiredRegionSize));
	if (nRows > int(raster.rows())){
		nRows = int(raster.rows());
	}
    double regionHeight = rasterHeight / nRows;

    int nColumns = int(std::floor(rasterWidth / desiredRegionSize));
	if (nColumns > int(raster.columns())){
		nColumns = int(raster.columns());
	}
    double regionWidth = rasterWidth / nColumns;

    // Create the 2-d arry of InterpolationRegion (nRows, nColumns)
    regions_ = boost::make_shared< Grid<InterpolationRegion> >(nRows, nColumns, raster.lowerLeft(),
                                                               Coordinate(regionHeight),
                                                               Coordinate(regionWidth));

    LineIndent indent("  ");

    // Sort points into the regions
    BOOST_FOREACH(const IPoint & point, points) {
      Cell cell = regions_->getCell(point.x(), point.y());
      if ((*pointSelector)(point))
        (*regions_)[cell].pts.push_back(& point);
      else
        (*regions_)[cell].nPtsNotSelected++;
    }

    // Determine the cell block for each region.
    int regionRow = regions_->topRow();
    Coordinate regionRow_minY = regions_->getYInterval(regionRow).lowerBound();
    BOOST_FOREACH(unsigned int rasterRow, raster.topToBottom()) {
      // If first (top) row, then scan across cell columns computing widths of
      // cell blocks.
      if (rasterRow == raster.topRow()) {
          const unsigned int regionTopRow = regions_->topRow();
          unsigned int regionColumn = regions_->leftColumn();
          InterpolationRegion * currentRegion = &( (*regions_)(regionTopRow, regionColumn) );
          Coordinate currentRegion_maxX = regions_->getXInterval(regionColumn).upperBound();
          BOOST_FOREACH(unsigned int rasterColumn, raster.leftToRight()) {
            Cell cell = raster.getCell(raster.topRow(), rasterColumn);
            if (rasterColumn == raster.leftColumn()) {
              currentRegion->cellBlock = CellBlock(cell /* upperLeft */);  // cell block height and width = 1
            }
            else if (cell.x() < currentRegion_maxX) {
              // The cell is in the current region, so increment the cell
              // block's width.
              currentRegion->cellBlock.width += 1;
            } else {
              // We've moved right into a new region.
              regionColumn += right(1);
              currentRegion = &( (*regions_)(regionTopRow, regionColumn) );
              currentRegion->cellBlock = CellBlock(cell /* upperLeft */);  // cell block height and width = 1
              currentRegion_maxX = regions_->getXInterval(regionColumn).upperBound();
            }
          }
      } else {
        // Current raster row is not the top row.  So two possibilities:
        //   A) the centers of the current row's cells are in the current
        //      region row, or
        //   B) the current row's cells in the next lower region row
        Cell leftMostCell = raster.getCell(rasterRow, raster.leftColumn());
        if (leftMostCell.y() > regionRow_minY) {
          // Case (A) - still in current region row, so go through the regions
          // on the current region row, and increment their cell block heights.
          BOOST_FOREACH(unsigned int regionColumn, regions_->leftToRight()) {
            (*regions_)(regionRow, regionColumn).cellBlock.height += 1;
          }
        } else {
          // Case (B) - moved down into new region row, so scan across region
          // columns, copying the cell block widths from the regions in the
          // top region row into their corresponding regions of the current
          // region row.
          regionRow += down(1);
          regionRow_minY = regions_->getYInterval(regionRow).lowerBound();
          BOOST_FOREACH(unsigned int regionColumn, regions_->leftToRight()) {
            const InterpolationRegion & correspondingRegionInTopRow = (*regions_)(regions_->topRow(), regionColumn);
            int upperLeftCell_column = correspondingRegionInTopRow.cellBlock.upperLeftCell.column();

            CellBlock & currentRegion_cellBlock = (*regions_)(regionRow, regionColumn).cellBlock;
            currentRegion_cellBlock.upperLeftCell = raster.getCell(rasterRow, upperLeftCell_column);
            currentRegion_cellBlock.height = 1;
            currentRegion_cellBlock.width = correspondingRegionInTopRow.cellBlock.width;
          }
        }  // else Case (B)
      }  // else current raster row not top row
    }  // for each raster row

    iterationState_ = RegionIteration_Initialized;
    raster_ = &(raster);

    return nRows * nColumns;
  }

  const Cell* DisjointRegions::getNextCell()
  {
    switch (iterationState_) {
      case RegionIteration_Initialized :
        // Return the upper left region
        currentRegionRow_ = regions_->topRow();
        currentRegionColumn_ = regions_->leftColumn();
        iterationState_ = RegionIteration_InProgress;
        break;

      case RegionIteration_InProgress :
        // Advance to next cell in row major order
        if (currentRegionColumn_ != regions_->rightColumn())
          currentRegionColumn_ += right(1);
        else {
          // Advance to next row
          if (currentRegionRow_ != regions_->bottomRow()) {
            currentRegionRow_ += down(1);
            currentRegionColumn_ = regions_->leftColumn();
          } else {
            iterationState_ = RegionIteration_Done;
            return 0;
          }
        }
        break;

      default :
        assert(iterationState_ == RegionIteration_Done);
        return 0;
    }

    return new Cell(regions_->getCell(currentRegionRow_, currentRegionColumn_));

  }

  const InterpolationRegion * DisjointRegions::getRegionForCell(const Cell *cell)
  {
    unsigned int row = cell->row();
    unsigned int column = cell->column();

    return &( (*regions_)(row, column) );

  }

    //---------------------------------------------------------------------------

  void addNeighborPointsToRegionWithCell(Grid<InterpolationRegion> &  regions,
		  const Cell & cell, std::vector<const IPoint *> &points, int nPoints,
    int &indexNextAvailableNeighbor, std::vector<NeighborPoint> &neighborPts,
    int &neighborhoodSize, int &nPointsLeftInOuterRing)
  {
    while (nPoints > 0) {
      while (nPointsLeftInOuterRing == 0) {
        // Expand the neighborhood, and get points from the new outer ring.
        neighborPts.clear();
        neighborhoodSize += 2;  // first 3x3, then 5x5, 7x7, ...
        getPointsFromOuterRing(regions, cell, neighborhoodSize, neighborPts);
        nPointsLeftInOuterRing = neighborPts.size();
        std::sort(neighborPts.begin(), neighborPts.begin());
        indexNextAvailableNeighbor = 0;
      }

      int nPtsToAdd = (nPointsLeftInOuterRing < nPoints) ? nPointsLeftInOuterRing : nPoints;
      std::for_each(neighborPts.begin() + indexNextAvailableNeighbor,
                    neighborPts.begin() + indexNextAvailableNeighbor + nPtsToAdd,
                    AppendPoint(points));
      indexNextAvailableNeighbor += nPtsToAdd;
      nPointsLeftInOuterRing -= nPtsToAdd;
      nPoints -= nPtsToAdd;
    }
  }

  void DisjointRegions::getPointsAndCellsForCell(const Cell *cell, int nExtraPoints,
    std::vector<const IPoint *> &points, std::vector<Cell> &cells)
  {
    const InterpolationRegion *region = getRegionForCell(cell);
    points = region->pts;
    int neighborhoodSize = 1;
    std::vector<NeighborPoint> neighborPts;
    neighborPts.clear();
    int nPointsLeftInOuterRing = 0;
    int indexNextAvailableNeighbor = 0;
    unsigned int nSelectedPts = points.size();

    while (nSelectedPts < desiredPtsPerRegion) {

      addNeighborPointsToRegionWithCell(*regions_, *cell, points, desiredPtsPerRegion - nSelectedPts,
        indexNextAvailableNeighbor, neighborPts, neighborhoodSize, nPointsLeftInOuterRing);
      nSelectedPts = points.size();

    }

    if(nExtraPoints > 0) {
      addNeighborPointsToRegionWithCell(*regions_, *cell, points, nExtraPoints,indexNextAvailableNeighbor,
        neighborPts, neighborhoodSize, nPointsLeftInOuterRing);
    }

    cells.clear();
    const CellBlock & cellBlock = region->cellBlock;
    const Cell & upperLeftCell = cellBlock.upperLeftCell;
    unsigned int blockTop    = upperLeftCell.row();
    unsigned int blockBottom = blockTop + down(cellBlock.height - 1);
    unsigned int blockLeft   = upperLeftCell.column();
    unsigned int blockRight  = blockLeft + right(cellBlock.width - 1);

    BOOST_FOREACH(unsigned int row, Sequence<unsigned int>(blockTop, blockBottom)) {
      BOOST_FOREACH(unsigned int column, Sequence<unsigned int>(blockLeft, blockRight)) {
        cells.push_back(raster_->getCell(row, column));
      }
    }
  }

}


// Copyright 2014-2015 SDL plc
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//     http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/** \file

    semirings used for Arc for Hypergraph.
*/

#ifndef HYP__HYPERGRAPH_WEIGHT_HPP
#define HYP__HYPERGRAPH_WEIGHT_HPP
#pragma once

/**
   Weight: semiring (zero, one, plus, times, less,==).

   note: some more expensive semirings' zero and one will not be usable until
   main() (static globals). singleton lazy-init pattern not worth the overhead.
*/

#include <sdl/Hypergraph/Exception.hpp>
#include <sdl/Hypergraph/Types.hpp>
#include <sdl/Hypergraph/WeightBase.hpp>
#include <sdl/Util/Constants.hpp>
#include <sdl/Util/Hash.hpp>
#include <sdl/Util/LogHelper.hpp>
#include <sdl/Util/LogMath.hpp>
#include <sdl/Util/Math.hpp>
#include <sdl/LexicalCast.hpp>
#include <boost/math/special_functions/log1p.hpp>

#if HAVE_OPENFST
// OpenFst weight compatability - needed for ToReplaceFst
#define DEFINE_OPENFST_COMPAT_FUNCTIONS(name)            \
  static std::string const& Type() {                     \
    static std::string const r(#name);                   \
    return r;                                            \
  }                                                      \
  static const Self One() { return Self::one(); }        \
  static const Self Zero() { return Self::zero(); }      \
  void Write(std::ostream& o) const { o << *this; }      \
  static inline std::size_t Properties() { return 0x3; } \
  typedef Self ReverseWeight;                            \
  Self& Reverse() { return *this; }                      \
  Self const& Reverse() const { return *this; }
#else
#define DEFINE_OPENFST_COMPAT_FUNCTIONS(name)
#endif

#define SDL_DEFINE_FLOATWT_CMP(c, cmp)                \
  template <class T>                                  \
  bool operator cmp(c<T> const& w1, c<T> const& w2) { \
    return w1.value_ cmp w2.value_;                   \
  }

#define SDL_DEFINE_FLOATWT_CMPS(c) \
  SDL_DEFINE_FLOATWT_CMP(c, ==)    \
  SDL_DEFINE_FLOATWT_CMP(c, >=)    \
  SDL_DEFINE_FLOATWT_CMP(c, <=)    \
  SDL_DEFINE_FLOATWT_CMP(c, !=)    \
  SDL_DEFINE_FLOATWT_CMP(c, <)     \
  SDL_DEFINE_FLOATWT_CMP(c, >)

// TODO: #if HAVE_OPENFST and use constants appropriate for particular semiring - this is just to compile
// HgFsmDraw

// TODO: OpenFst Divide. use sdl::Util::logPlus?

namespace sdl {
namespace Hypergraph {

/**
   Simple wrapper weight around a float value. Base class for
   Viterbi weight, Log weight, etc.
 */
template <class T>
class FloatWeightTpl : public WeightBase {
 protected:
  typedef typename Util::OtherFloat<T>::type DoubleT;

 public:
  typedef T FloatT;

  static inline constexpr FloatT kOneValue() { return 0; }
  // TODO: C++11 constexpr
  static inline constexpr FloatT kZeroValue() { return std::numeric_limits<T>::infinity(); }

  typedef void HasIsZero;
  bool isZero() const { return value_ == std::numeric_limits<T>::infinity(); }
  friend inline void setZero(FloatWeightTpl& x) { x.value_ = std::numeric_limits<T>::infinity(); }

  typedef void HasIsOne;
  bool isOne() const { return !value_; }
  friend inline void setOne(FloatWeightTpl& x) { x.value_ = 0; }

  /// uninitialized - we want users to explicitly init to zero or one as
  /// appropriate e.g. vector<ViterbiWeight>(N, ViterbiWeight::zero())
  constexpr FloatWeightTpl() = default;  // uninitialized but an explicit weight() init would then 0-init?
  constexpr FloatWeightTpl(T v) : value_(v) {}
  constexpr FloatWeightTpl(DoubleT v) : value_((T)v) {}
  constexpr FloatWeightTpl(int v) : value_((T)v) {}
  constexpr FloatWeightTpl(std::size_t v) : value_((T)v) {}

  FloatWeightTpl(FloatWeightTpl const& o) = default;
  FloatWeightTpl& operator=(FloatWeightTpl const& o) = default;
  FloatWeightTpl(FloatWeightTpl&& o) = default;
  FloatWeightTpl& operator=(FloatWeightTpl&& o) = default;

  T& value() { return value_; }

  T getValue() const { return value_; }

  void set(std::string const& s) { value_ = sdl::lexical_cast<T>(s); }

  T value_;
};

SDL_DEFINE_FLOATWT_CMPS(FloatWeightTpl)

template <class T>
std::istream& operator>>(std::istream& i, FloatWeightTpl<T>& x) {
  return i >> x.value();
}
template <class T>
std::ostream& operator<<(std::ostream& o, FloatWeightTpl<T> const& x) {
  return o << x.value_;
}

template <class T>
inline std::size_t hashWeight(FloatWeightTpl<T> const& w) {
  return Util::hashFloat(w.value_);
}

template <class T>
inline bool approxEqual(FloatWeightTpl<T> const& w1, FloatWeightTpl<T> const& w2,
                        T epsilon = FloatConstants<T>::epsilon) {
  return Util::floatEqual(w1.value_, w2.value_, epsilon);
}

template <class T>
inline bool approxGreaterOrEqual(FloatWeightTpl<T> const& w1, FloatWeightTpl<T> const& w2,
                                 T epsilon = FloatConstants<T>::epsilon) {
  return Util::approxGreaterOrEqual(w1.value_, w2.value_, epsilon);
}

template <class T>
inline bool approxLessOrEqual(FloatWeightTpl<T> const& w1, FloatWeightTpl<T> const& w2,
                              T epsilon = FloatConstants<T>::epsilon) {
  return Util::approxLessOrEqual(w1.value_, w2.value_, epsilon);
}

template <class T>
inline bool definitelyGreater(FloatWeightTpl<T> const& w1, FloatWeightTpl<T> const& w2,
                              T epsilon = FloatConstants<T>::epsilon) {
  return Util::definitelyGreater(w1.value_, w2.value_, epsilon);
}

template <class T>
inline bool definitelyLess(FloatWeightTpl<T> const& w1, FloatWeightTpl<T> const& w2,
                           T epsilon = FloatConstants<T>::epsilon) {
  return Util::definitelyLess(w1.value_, w2.value_, epsilon);
}

// all 3 of approx equal, greater, less may be true simultaneously (if same eps, then approxEqual <=>
// approxGreater and approxLess

class BooleanWeight : public FloatWeightTpl<bool> {
 public:
  typedef bool FloatT;

  BooleanWeight() : FloatWeightTpl<bool>() {}
  static inline constexpr bool kOneValue() { return 1; }
  static inline constexpr bool kZeroValue() { return 0; }

  // explicit only because somebody was wrongly activating this implicitly (they should use safe_bool idiom?)
  explicit constexpr BooleanWeight(bool v) : FloatWeightTpl<bool>(v) {}

  typedef BooleanWeight Self;
  DEFINE_OPENFST_COMPAT_FUNCTIONS(Boolean)

  typedef void HasIsOne;
  bool isOne() const { return value_ == true; }
  friend inline void setOne(BooleanWeight& x) { x.value_ = true; }
  static inline constexpr BooleanWeight one() { return BooleanWeight(true); }

  typedef void HasIsZero;
  bool isZero() const { return value_ == false; }
  friend inline void setZero(BooleanWeight& x) { x.value_ = false; }
  static inline constexpr BooleanWeight zero() { return BooleanWeight(false); }
};

template <class T>
class ViterbiWeightTpl : public FloatWeightTpl<T> {
  typedef ViterbiWeightTpl<T> Self;
  typedef FloatWeightTpl<T> Base;

 public:
  typedef void HasIsZero;
  bool isZero() const { return this->value_ == std::numeric_limits<T>::infinity(); }
  typedef void HasIsOne;
  bool isOne() const { return !this->value_; }

  typedef T FloatT;

  constexpr ViterbiWeightTpl() : Base() {}

  constexpr ViterbiWeightTpl(T v) : Base(v) {}
  constexpr ViterbiWeightTpl(int v) : Base(v) {}
  constexpr ViterbiWeightTpl(std::size_t v) : Base(v) {}
  constexpr ViterbiWeightTpl(typename Base::DoubleT v) : Base(v) {}

  ViterbiWeightTpl(ViterbiWeightTpl const& o) = default;
  ViterbiWeightTpl& operator=(ViterbiWeightTpl const& o) = default;
  ViterbiWeightTpl(ViterbiWeightTpl&& o) = default;
  ViterbiWeightTpl& operator=(ViterbiWeightTpl&& o) = default;

  Self& operator=(Base const& other) {
    this->value_ = other.value_;
    return *this;
  }

  static inline constexpr ViterbiWeightTpl<T> one() { return ViterbiWeightTpl<T>(0.0f); }

  static inline constexpr ViterbiWeightTpl<T> zero() {
    return ViterbiWeightTpl<T>(std::numeric_limits<T>::infinity());
  }

  void plusBy(Self const& b) {
    if (b.value_ < this->value_) this->value_ = b.value_;
  }
  typedef void HasPlusBy;

  void timesBy(Self const& b) { this->value_ += b.value_; }
  typedef void HasTimesBy;

  DEFINE_OPENFST_COMPAT_FUNCTIONS(Viterbi)
};

template <class T>
ViterbiWeightTpl<T> plus(ViterbiWeightTpl<T> const& w1, ViterbiWeightTpl<T> const& w2) {
  return w1.value_ < w2.value_ ? w1 : w2;
}

template <class T>
ViterbiWeightTpl<T> minus(ViterbiWeightTpl<T> const& w1, ViterbiWeightTpl<T> const& w2) {
  SDL_THROW_LOG(Hypergraph, UnimplementedException, "Viterbi minus is not supported");
  return w1;  // make compiler happy
}

template <class T>
inline ViterbiWeightTpl<T> times(ViterbiWeightTpl<T> const& w1, ViterbiWeightTpl<T> const& w2) {
  if (w1 == ViterbiWeightTpl<T>::zero() || w2 == ViterbiWeightTpl<T>::zero())
    return ViterbiWeightTpl<T>::zero();
  return ViterbiWeightTpl<T>(w1.value_ + w2.value_);
}

template <class T>
inline bool less(ViterbiWeightTpl<T> const& w1, ViterbiWeightTpl<T> const& w2) {
  return w1.value_ < w2.value_;
}

template <class T>
inline ViterbiWeightTpl<T> divide(ViterbiWeightTpl<T> const& w1, ViterbiWeightTpl<T> const& w2) {
  if (w1 == ViterbiWeightTpl<T>::zero() || w2 == ViterbiWeightTpl<T>::zero())
    // Technically can't divide by 0. but practically ok.
    return ViterbiWeightTpl<T>::zero();
  return ViterbiWeightTpl<T>(w1.value_ - w2.value_);
}

template <class T>
inline ViterbiWeightTpl<T> invert(ViterbiWeightTpl<T> const& w) {
  return ViterbiWeightTpl<T>(-w.value_);
}

template <class T>
inline ViterbiWeightTpl<T> pow(ViterbiWeightTpl<T> const& w, T k) {
  return ViterbiWeightTpl<T>(k * w.value_);
}

template <class T>
class LogWeightTpl : public FloatWeightTpl<T> {

  typedef FloatWeightTpl<T> Base;
  typedef LogWeightTpl Self;

 public:
  typedef void HasIsZero;
  bool isZero() const { return this->value_ == std::numeric_limits<T>::infinity(); }

  typedef void HasIsOne;
  bool isOne() const { return !this->value_; }

  typedef T FloatT;

  constexpr LogWeightTpl() : Base() {}

  constexpr LogWeightTpl(T v) : Base(v) {}
  constexpr LogWeightTpl(typename Base::DoubleT v) : Base(v) {}
  constexpr LogWeightTpl(int v) : Base(v) {}
  constexpr LogWeightTpl(std::size_t v) : Base(v) {}
  LogWeightTpl(LogWeightTpl const& o) = default;
  LogWeightTpl& operator=(LogWeightTpl const& o) = default;
  LogWeightTpl(LogWeightTpl&& o) = default;
  LogWeightTpl& operator=(LogWeightTpl&& o) = default;

  static inline constexpr LogWeightTpl<T> one() { return LogWeightTpl<T>(0.0f); }

  static inline constexpr LogWeightTpl<T> zero() {
    return LogWeightTpl<T>(std::numeric_limits<T>::infinity());
  }

  Self& operator=(Base const& other) {
    this->value_ = other.value_;
    return *this;
  }

  void timesBy(LogWeightTpl const& b) { this->value_ += b.value_; }
  typedef void HasTimesBy;

  DEFINE_OPENFST_COMPAT_FUNCTIONS(Log)
};

template <class T>
LogWeightTpl<T> plus(LogWeightTpl<T> const& w1, LogWeightTpl<T> const& w2) {
  return {Util::neglogPlus(w1.value_, w2.value_)};
}

template <class T>
LogWeightTpl<T> minus(LogWeightTpl<T> const& w1, LogWeightTpl<T> const& w2) {
  return {Util::neglogMinus(w1.value_, w2.value_)};
}

template <class T>
inline LogWeightTpl<T> times(LogWeightTpl<T> const& w1, LogWeightTpl<T> const& w2) {
  if (w1 == LogWeightTpl<T>::zero() || w2 == LogWeightTpl<T>::zero()) return LogWeightTpl<T>::zero();
  return LogWeightTpl<T>(w1.value_ + w2.value_);
}

template <class T>
inline bool less(LogWeightTpl<T> const& w1, LogWeightTpl<T> const& w2) {
  return w1.value_ < w2.value_;
}

template <class T>
inline LogWeightTpl<T> divide(LogWeightTpl<T> const& w1, LogWeightTpl<T> const& w2) {
  if (w1 == LogWeightTpl<T>::zero() || w2 == LogWeightTpl<T>::zero()) {
    // Technically can't divide by zero. but practically ok.
    return LogWeightTpl<T>::zero();
  }
  return LogWeightTpl<T>(w1.value_ - w2.value_);
}

template <class T>
inline LogWeightTpl<T> invert(LogWeightTpl<T> const& w) {
  return LogWeightTpl<T>(-w.value_);
}

template <class T>
inline LogWeightTpl<T> pow(LogWeightTpl<T> const& w, T k) {
  return LogWeightTpl<T>(k * w.value_);
}

typedef LogWeightTpl<float> LogWeight;
typedef ViterbiWeightTpl<float> ViterbiWeight;

inline BooleanWeight plus(BooleanWeight const& w1, BooleanWeight const& w2) {
  return BooleanWeight(w1.value_ || w2.value_);
}

inline BooleanWeight times(BooleanWeight const& w1, BooleanWeight const& w2) {
  return BooleanWeight(w1.value_ && w2.value_);
}

inline bool less(BooleanWeight const& w1, BooleanWeight const& w2) {
  return w1.value_ < w2.value_;
}

inline BooleanWeight divide(BooleanWeight const& w1, BooleanWeight const& w2) {
  SDL_THROW_LOG(Hypergraph, UnimplementedException, "Boolean divide is not supported");
  return w1;  // make compiler happy
}

inline BooleanWeight invert(BooleanWeight const& w1, BooleanWeight const& w2) {
  SDL_THROW_LOG(Hypergraph, UnimplementedException, "Boolean invert is not supported");
  return w1;  // make compiler happy
}

inline BooleanWeight pow(BooleanWeight const& w, bool p) {
  return p ? w : BooleanWeight(true);
}

template <class Weight>
char const* weightName(Weight*) {
  return "Weight";
}

template <class Weight>
char const* weightName() {
  return weightName((Weight*)0);
}

template <class T>
inline char const* weightName(ViterbiWeightTpl<T>*) {
  return "Viterbi";
}

template <class T>
inline char const* weightName(LogWeightTpl<T>*) {
  return "Log";
}

inline char const* weightName(BooleanWeight*) {
  return "Boolean";
}

}}

#endif


/*
 Copyright (C) 2019 Quaternion Risk Management Ltd
 All rights reserved.

 This file is part of ORE, a free-software/open-source library
 for transparent pricing and risk analysis - http://opensourcerisk.org

 ORE is free software: you can redistribute it and/or modify it
 under the terms of the Modified BSD License.  You should have received a
 copy of the license along with this program.
 The license is also available online at <http://opensourcerisk.org>

 This program is distributed on the basis that it will form a useful
 contribution to risk analytics and model standardisation, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
*/

#include <qle/termstructures/eqcommoptionsurfacestripper.hpp>
#include <boost/make_shared.hpp>
#include <ql/instruments/impliedvolatility.hpp>
#include <ql/math/solver1d.hpp>
#include <ql/pricingengines/vanilla/analyticeuropeanengine.hpp>
#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
#include <qle/pricingengines/baroneadesiwhaleyengine.hpp>
#include <qle/termstructures/blackvariancesurfacesparse.hpp>
#include <qle/termstructures/pricetermstructureadapter.hpp>

using std::function;
using std::map;
using std::pair;
using std::set;
using std::vector;
using namespace QuantLib;

namespace {

// Utility method to create the list of options to be used at an expiry date for stripping.
function<bool(Real,Real)> comp = [](Real a, Real b) { return !close(a, b) && a < b; };

map<Real, Option::Type, decltype(comp)> createStrikes(Real forward, const vector<Real>& cStrikes,
    const vector<Real>& pStrikes, bool preferOutOfTheMoney) {

    // Firstly create the restricted vector of call and put strikes.
    vector<Real> rcStks;
    copy_if(cStrikes.begin(), cStrikes.end(), back_inserter(rcStks),[forward, preferOutOfTheMoney](Real stk) {
        return (preferOutOfTheMoney && stk >= forward) || (!preferOutOfTheMoney && stk <= forward); });
    vector<Real> rpStks;
    copy_if(pStrikes.begin(), pStrikes.end(), back_inserter(rpStks), [forward, preferOutOfTheMoney](Real stk) {
        return (preferOutOfTheMoney && stk <= forward) || (!preferOutOfTheMoney && stk >= forward); });

    // Create the empty map.
    map<Real, Option::Type, decltype(comp)> res(comp);

    // If both restricted vectors are empty, return an empty map
    if (rcStks.empty() && rpStks.empty())
        return res;

    // At least one of the restricted strike vectors are non empty so populate the map.
    if (!rcStks.empty() && !rpStks.empty()) {
        // Most common case hopefully. Use both sets of strikes.
        // Could have the fwd strike in both restricted sets from the logic above. Favour Call here via overwrite.
        for (Real stk : rpStks)
            res[stk] = Option::Put;
        for (Real stk : rcStks)
            res[stk] = Option::Call;
    } else if (rpStks.empty()) {
        // If restricted put strikes are empty, use all the call strikes
        for (Real stk : cStrikes)
            res[stk] = Option::Call;
    } else if (rcStks.empty()) {
        // If restricted call strikes are empty, use all the put strikes
        for (Real stk : pStrikes)
            res[stk] = Option::Put;
    }

    return res;
}

}

namespace QuantExt {

OptionSurfaceStripper::OptionSurfaceStripper(
    const boost::shared_ptr<OptionInterpolatorBase>& callSurface,
    const boost::shared_ptr<OptionInterpolatorBase>& putSurface,
    const Calendar& calendar,
    const DayCounter& dayCounter,
    Exercise::Type type,
    bool lowerStrikeConstExtrap,
    bool upperStrikeConstExtrap,
    bool timeFlatExtrapolation,
    bool preferOutOfTheMoney,
    Solver1DOptions solverOptions)
    : callSurface_(callSurface),
      putSurface_(putSurface),
      calendar_(calendar),
      dayCounter_(dayCounter),
      type_(type),
      lowerStrikeConstExtrap_(lowerStrikeConstExtrap),
      upperStrikeConstExtrap_(upperStrikeConstExtrap),
      timeFlatExtrapolation_(timeFlatExtrapolation),
      preferOutOfTheMoney_(preferOutOfTheMoney),
      solverOptions_(solverOptions),
      havePrices_(boost::dynamic_pointer_cast<OptionPriceSurface>(callSurface_)) {

    QL_REQUIRE(callSurface_->referenceDate() == putSurface_->referenceDate(),
        "Mismatch between Call and Put reference dates in OptionSurfaceStripper");

    registerWith(Settings::instance().evaluationDate());

    // Set up that is only needed if we have price based surfaces and we are stripping volatilities.
    if (havePrices_) {

        // Check that there is also a put price surface
        QL_REQUIRE(boost::dynamic_pointer_cast<OptionPriceSurface>(putSurface_),
            "OptionSurfaceStripper: call price surface provided but no put price surface.");

        setUpSolver();
    }
}

OptionSurfaceStripper::PriceError::PriceError(const VanillaOption& option, SimpleQuote& volatility, Real targetPrice)
    : option_(option), volatility_(volatility), targetPrice_(targetPrice) {}

Real OptionSurfaceStripper::PriceError::PriceError::operator()(Volatility x) const {

    volatility_.setValue(x);

    // Barone Adesi Whaley fails for very small variance, so wrap in a try catch
    Real npv;
    try {
        npv = option_.NPV();
    } catch (...) {
        npv = 0.0;
    }

    return npv - targetPrice_;
}

void OptionSurfaceStripper::performCalculations() const {

    // Create a set of all expiries
    auto tmp = callSurface_->expiries();
    set<Date> allExpiries(tmp.begin(), tmp.end());
    tmp = putSurface_->expiries();
    allExpiries.insert(tmp.begin(), tmp.end());

    boost::shared_ptr<BlackVarianceSurfaceSparse> callVolSurface;
    boost::shared_ptr<BlackVarianceSurfaceSparse> putVolSurface;

    // Switch based on whether surface is direct volatilities or prices to be stripped.
    boost::shared_ptr<PricingEngine> engine;
    boost::shared_ptr<SimpleQuote> volQuote = boost::make_shared<SimpleQuote>(0.1);
    if (havePrices_) {

        // a black scholes process
        boost::shared_ptr<GeneralizedBlackScholesProcess> gbsp = process(volQuote);

        // hard code the engines here
        if (type_ == Exercise::American) {
            engine = boost::make_shared<QuantExt::BaroneAdesiWhaleyApproximationEngine>(gbsp);
        } else if (type_ == Exercise::European) {
            engine = boost::make_shared<QuantExt::AnalyticEuropeanEngine>(gbsp);
        } else {
            QL_FAIL("Unsupported exercise type for option stripping");
        }

    } else {
        // we have variance surfaces, explicitly cast so we can look up vol later
        callVolSurface = boost::dynamic_pointer_cast<BlackVarianceSurfaceSparse>(callSurface_);
        putVolSurface = boost::dynamic_pointer_cast<BlackVarianceSurfaceSparse>(putSurface_);
    }

    // Need to populate these below to feed to BlackVarianceSurfaceSparse
    vector<Real> volStrikes;
    vector<Real> volData;
    vector<Date> volExpiries;

    // Loop over each expiry
    for (const Date& expiry : allExpiries) {

        // Get the forward price at expiry
        Real fwd = forward(expiry);

        // Get the call and put strikes at the expiry date. Each may be empty.
        vector<Real> callStrikes = strikes(expiry, true);
        vector<Real> putStrikes = strikes(expiry, false);

        // We want a set of prices both sides of ATM forward
        // If preferOutOfTheMoney_ is false, we take calls where strike < atm and puts where strike > atm.
        // If preferOutOfTheMoney_ is true, we take calls where strike > atm and puts where strike < atm.
        auto relevantStrikes = createStrikes(fwd, callStrikes, putStrikes, preferOutOfTheMoney_);
        for (const auto& kv : relevantStrikes) {
            if (havePrices_) {
                // Only use the volatility if the root finding was successful
                Real v = implyVol(expiry, kv.first, kv.second, engine, *volQuote);
                if (v != Null<Real>()) {
                    volExpiries.push_back(expiry);
                    volStrikes.push_back(kv.first);
                    volData.push_back(v);
                }
            } else {
                volExpiries.push_back(expiry);
                volStrikes.push_back(kv.first);
                Real v = kv.second == Option::Call ? callVolSurface->blackVol(expiry, kv.first) :
                    putVolSurface->blackVol(expiry, kv.first);
                volData.push_back(v);
            }
        }
    }

    // Populate the variance surface.
    volSurface_ = boost::make_shared<BlackVarianceSurfaceSparse>(
        callSurface_->referenceDate(), calendar_, volExpiries, volStrikes, volData, dayCounter_,
        lowerStrikeConstExtrap_, upperStrikeConstExtrap_, timeFlatExtrapolation_);
}

vector<Real> OptionSurfaceStripper::strikes(const Date& expiry, bool isCall) const {

    const boost::shared_ptr<OptionInterpolatorBase>& surface = isCall ? callSurface_ : putSurface_;
    auto expiries = surface->expiries();
    auto it = find(expiries.begin(), expiries.end(), expiry);

    if (it != expiries.end()) {
        return surface->strikes().at(distance(expiries.begin(), it));
    } else {
        return {};
    }

}

Real OptionSurfaceStripper::implyVol(Date expiry, Real strike, Option::Type type,
    boost::shared_ptr<PricingEngine> engine, SimpleQuote& volQuote) const {

    // Create the option instrument used in the solver.
    boost::shared_ptr<StrikedTypePayoff> payoff = boost::make_shared<PlainVanillaPayoff>(type, strike);
    boost::shared_ptr<Exercise> exercise;
    if (type_ == Exercise::American) {
        exercise = boost::make_shared<AmericanExercise>(expiry);
    } else if (type_ == Exercise::European) {
        exercise = boost::make_shared<EuropeanExercise>(expiry);
    } else {
        QL_FAIL("OptionSurfaceStripper: unsupported exercise type for option stripping.");
    }
    VanillaOption option(payoff, exercise);
    option.setPricingEngine(engine);

    // Get the target price from the surface.
    Real targetPrice = type == Option::Call ? callSurface_->getValue(expiry, strike)
        : putSurface_->getValue(expiry, strike);

    // Attempt to calculate the implied volatility.
    Real vol = Null<Real>();
    try {
        PriceError f(option, volQuote, targetPrice);
        vol = solver_(f);
    } catch (const Error&) {
    }

    return vol;
}

void OptionSurfaceStripper::setUpSolver() {

    // Check that enough solver options have been provided.
    const Real& guess = solverOptions_.initialGuess;
    QL_REQUIRE(guess != Null<Real>(), "OptionSurfaceStripper: need a valid initial " <<
        "guess for a price based surface.");

    const Real& accuracy = solverOptions_.accuracy;
    QL_REQUIRE(accuracy != Null<Real>(), "OptionSurfaceStripper: need a valid accuracy " <<
        "for a price based surface.");

    // Set maximum evaluations if provided.
    if (solverOptions_.maxEvaluations != Null<Size>())
        brent_.setMaxEvaluations(solverOptions_.maxEvaluations);

    // Check and set the lower bound and upper bound
    if (solverOptions_.lowerBound != Null<Real>() && solverOptions_.upperBound != Null<Real>()) {
        QL_REQUIRE(solverOptions_.lowerBound < solverOptions_.upperBound, "OptionSurfaceStripper: lowerBound (" <<
            solverOptions_.lowerBound << ") should be less than upperBound (" << solverOptions_.upperBound << ")");
    }

    if (solverOptions_.lowerBound != Null<Real>())
        brent_.setLowerBound(solverOptions_.lowerBound);
    if (solverOptions_.upperBound != Null<Real>())
        brent_.setUpperBound(solverOptions_.upperBound);

    // Choose a min/max or step solver based on parameters provided, favouring the min/max based version.
    const Real& min = solverOptions_.minMax.first;
    const Real& max = solverOptions_.minMax.second;
    const Real& step = solverOptions_.step;
    using std::placeholders::_1;
    if (min != Null<Real>() && max != Null<Real>()) {
        typedef Real (Brent::* MinMaxSolver)(const PriceError&, Real, Real, Real, Real) const;
        solver_ = std::bind(static_cast<MinMaxSolver>(&Brent::solve), &brent_, _1, accuracy, guess, min, max);
    } else if (step != Null<Real>()) {
        typedef Real(Brent::* StepSolver)(const PriceError&, Real, Real, Real) const;
        solver_ = std::bind(static_cast<StepSolver>(&Brent::solve), &brent_, _1, accuracy, guess, step);
    } else {
        QL_FAIL("OptionSurfaceStripper: need a valid step size or (min, max) pair for a price based surface.");
    }

}

boost::shared_ptr<BlackVolTermStructure> OptionSurfaceStripper::volSurface() {
    calculate();
    return volSurface_;
}

EquityOptionSurfaceStripper::EquityOptionSurfaceStripper(
    const Handle<EquityIndex>& equityIndex,
    const boost::shared_ptr<OptionInterpolatorBase>& callSurface,
    const boost::shared_ptr<OptionInterpolatorBase>& putSurface,
    const Calendar& calendar,
    const DayCounter& dayCounter,
    Exercise::Type type,
    bool lowerStrikeConstExtrap,
    bool upperStrikeConstExtrap,
    bool timeFlatExtrapolation,
    bool preferOutOfTheMoney,
    Solver1DOptions solverOptions)
    : OptionSurfaceStripper(callSurface, putSurface, calendar, dayCounter, type, lowerStrikeConstExtrap,
        upperStrikeConstExtrap, timeFlatExtrapolation, preferOutOfTheMoney, solverOptions), equityIndex_(equityIndex) {
    registerWith(equityIndex_);
}

boost::shared_ptr<GeneralizedBlackScholesProcess> EquityOptionSurfaceStripper::process(
    const boost::shared_ptr<QuantLib::SimpleQuote>& volatilityQuote) const {

    Handle<BlackVolTermStructure> vts(boost::make_shared<BlackConstantVol>(
        callSurface_->referenceDate(), calendar_, Handle<Quote>(volatilityQuote), dayCounter_));

    return boost::make_shared<BlackScholesMertonProcess>(equityIndex_->equitySpot(),
        equityIndex_->equityDividendCurve(), equityIndex_->equityForecastCurve(), vts);
}

Real EquityOptionSurfaceStripper::forward(const Date& date) const {
    return equityIndex_->forecastFixing(date);
}

CommodityOptionSurfaceStripper::CommodityOptionSurfaceStripper(
    const Handle<PriceTermStructure>& priceCurve,
    const Handle<YieldTermStructure>& discountCurve,
    const boost::shared_ptr<OptionInterpolatorBase>& callSurface,
    const boost::shared_ptr<OptionInterpolatorBase>& putSurface,
    const Calendar& calendar,
    const DayCounter& dayCounter,
    Exercise::Type type,
    bool lowerStrikeConstExtrap,
    bool upperStrikeConstExtrap,
    bool timeFlatExtrapolation,
    bool preferOutOfTheMoney,
    Solver1DOptions solverOptions)
    : OptionSurfaceStripper(callSurface, putSurface, calendar, dayCounter, type, lowerStrikeConstExtrap,
        upperStrikeConstExtrap, timeFlatExtrapolation, preferOutOfTheMoney, solverOptions),
        priceCurve_(priceCurve), discountCurve_(discountCurve) {
    registerWith(priceCurve_);
    registerWith(discountCurve_);
}

boost::shared_ptr<GeneralizedBlackScholesProcess> CommodityOptionSurfaceStripper::process(
    const boost::shared_ptr<QuantLib::SimpleQuote>& volatilityQuote) const {

    QL_REQUIRE(!priceCurve_.empty(), "CommodityOptionSurfaceStripper: price curve is empty");
    QL_REQUIRE(!discountCurve_.empty(), "CommodityOptionSurfaceStripper: discount curve is empty");

    // Volatility term structure for the process
    Handle<BlackVolTermStructure> vts(boost::make_shared<BlackConstantVol>(
        callSurface_->referenceDate(), calendar_, Handle<Quote>(volatilityQuote), dayCounter_));

    // Generate "spot" and "yield" curve for the process.
    Handle<Quote> spot(boost::make_shared<DerivedPriceQuote>(priceCurve_));
    Handle<YieldTermStructure> yield(boost::make_shared<PriceTermStructureAdapter>(*priceCurve_, *discountCurve_));
    yield->enableExtrapolation();

    return boost::make_shared<QuantLib::GeneralizedBlackScholesProcess>(spot, yield, discountCurve_, vts);
}

Real CommodityOptionSurfaceStripper::forward(const Date& date) const {
    QL_REQUIRE(!priceCurve_.empty(), "CommodityOptionSurfaceStripper: price curve is empty");
    return priceCurve_->price(date);
}

}


#include <algorithm>
#include <boost/optional.hpp>
#include <climits>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <functional>
#include <iomanip>
#include <iostream>
#include <limits>
#include <list>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <string>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

#define REP(i, n) for(int i = 0, i##_MACRO = (n); i < i##_MACRO; i++)
#define RANGE(i, a, b) for(int i = (a), i##_MACRO = (b); i < i##_MACRO; i++)
#define EACH(e, a) for(auto&& e : a)
#define ALL(a) std::begin(a), std::end(a)
#define RALL(a) std::rbegin(a), std::rend(a)
#define FILL(a, n) memset((a), n, sizeof(a))
#define FILLZ(a) FILL(a, 0)
#define INT(x) (static_cast<int>(x))
#define PRECISION(x) std::fixed << std::setprecision(x)

using namespace std;

using ll = long long;
using VI = std::vector<int>;
using VI2D = std::vector<vector<int>>;
using VLL = std::vector<long long>;
using VLL2D = std::vector<vector<long long>>;

constexpr int INF = 2e9;
constexpr double EPS = 1e-10;
constexpr double PI = acos(-1.0);

constexpr int dx[] = {-1, 0, 1, 0};
constexpr int dy[] = {0, -1, 0, 1};

template <typename T>
constexpr int sign(T x) {
	return x < 0 ? -1 : x > 0 ? 1 : 0;
}

template <>
constexpr int sign(double x) {
	return x < -EPS ? -1 : x > EPS ? 1 : 0;
}

template <typename T, typename U>
constexpr void chmax(T& m, U x) {
	m = max(m, x);
}

template <typename T, typename U>
constexpr void chmin(T& m, U x) {
	m = min(m, x);
}

template <typename T>
constexpr T square(T x) {
	return x * x;
}

template <long long MOD = 1000000007>
class ModInt {
	public:
	long long n;

	constexpr ModInt() : n(0) {}
	constexpr ModInt(long long n) : n(n < 0 ? n + MOD : n % MOD) {}

	constexpr long long get() const { return this->n; }
	constexpr long long get_mod() const { return MOD; }

	constexpr ModInt inv() const { return pow<ModInt<MOD>>(*this, MOD - 2); }

	constexpr ModInt& operator=(const long long rhs) {
		return *this = ModInt(rhs);
	}
	constexpr ModInt& operator+=(const ModInt rhs) {
		return *this = ModInt(this->n + rhs.n);
	}
	constexpr ModInt& operator-=(const ModInt rhs) {
		return *this = ModInt(this->n - rhs.n);
	}
	constexpr ModInt& operator*=(const ModInt rhs) {
		return *this = ModInt(this->n * rhs.n);
	}
	constexpr ModInt& operator/=(const ModInt rhs) {
		return *this *= rhs.inv();
	}
	constexpr bool operator==(const ModInt rhs) const {
		return this->n == rhs.n;
	}
};

template <long long MOD>
constexpr ModInt<MOD> operator+(const ModInt<MOD>& lhs,
								const ModInt<MOD>& rhs) {
	return ModInt<MOD>(lhs) += rhs;
}
template <long long MOD>
constexpr ModInt<MOD> operator+(const ModInt<MOD>& lhs, const long long& rhs) {
	return ModInt<MOD>(lhs) += rhs;
}
template <long long MOD>
constexpr ModInt<MOD> operator+(const long long& lhs, const ModInt<MOD>& rhs) {
	return ModInt<MOD>(lhs) += rhs;
}

template <long long MOD>
constexpr ModInt<MOD> operator-(const ModInt<MOD>& lhs,
								const ModInt<MOD>& rhs) {
	return ModInt<MOD>(lhs) -= rhs;
}
template <long long MOD>
constexpr ModInt<MOD> operator-(const ModInt<MOD>& lhs, const long long& rhs) {
	return ModInt<MOD>(lhs) -= rhs;
}
template <long long MOD>
constexpr ModInt<MOD> operator-(const long long& lhs, const ModInt<MOD>& rhs) {
	return ModInt<MOD>(lhs) -= rhs;
}

template <long long MOD>
constexpr ModInt<MOD> operator*(const ModInt<MOD>& lhs,
								const ModInt<MOD>& rhs) {
	return ModInt<MOD>(lhs) *= rhs;
}
template <long long MOD>
constexpr ModInt<MOD> operator*(const ModInt<MOD>& lhs, const long long& rhs) {
	return ModInt<MOD>(lhs) *= rhs;
}
template <long long MOD>
constexpr ModInt<MOD> operator*(const long long& lhs, const ModInt<MOD>& rhs) {
	return ModInt<MOD>(lhs) *= rhs;
}

template <long long MOD>
constexpr ModInt<MOD> operator/(const ModInt<MOD>& lhs,
								const ModInt<MOD>& rhs) {
	return ModInt<MOD>(lhs) /= rhs;
}
template <long long MOD>
constexpr ModInt<MOD> operator/(const ModInt<MOD>& lhs, const long long& rhs) {
	return ModInt<MOD>(lhs) /= rhs;
}
template <long long MOD>
constexpr ModInt<MOD> operator/(const long long& lhs, const ModInt<MOD>& rhs) {
	return ModInt<MOD>(lhs) /= rhs;
}

template <long long MOD>
std::ostream& operator<<(std::ostream& os, const ModInt<MOD>& x) {
	return os << x.n;
}

template <long long MOD>
std::istream& operator>>(std::istream& is, const ModInt<MOD>& x) {
	return is >> x.n;
}

template <typename T>
class Counter {
	using iterator = typename std::unordered_map<T, std::size_t>::iterator;
	using const_iterator =
		typename std::unordered_map<T, std::size_t>::const_iterator;

	std::unordered_map<T, std::size_t> m;

	public:
	template <typename U>
	Counter(U& v) {
		for(T& e : v) {
			this->m[e]++;
		}
	}

	std::vector<T> keys() const {
		std::vector<T> v(this->size());
		int i = 0;
		for(const std::pair<T, std::size_t>& e : this->m) {
			v[i++] = e.first;
		}
		return v;
	}

	std::vector<size_t> values() const {
		std::vector<size_t> v(this->size());
		int i = 0;
		for(const std::pair<T, std::size_t>& e : this->m) {
			v[i++] = e.second;
		}
		return v;
	}

	std::size_t size() const { return this->m.size(); }
	iterator begin() { return this->m.begin(); }
	iterator end() { return this->m.end(); }
	const_iterator cbegin() const { return this->m.cbegin(); }
	const_iterator cend() const { return this->m.cend(); }

	const std::size_t& operator[](T a) const& { return this->m[a]; }
	std::size_t& operator[](T a) & { return this->m[a]; }
	std::size_t operator[](T a) const&& { return std::move(this->m[a]); }
};

int main() {
	int n;
	string s;
	cin >> n;
	cin >> s;

	Counter<char> count(s);
	ModInt<> result = 1;
	EACH(e, count.values()) { result *= e + 1; }

	cout << result - 1 << endl;
	return 0;
}


/*! \file fm2dir.hpp
    \brief Templated class which computes the Fast Marching Directional (FM2Directional).

    It uses as a main container the nDGridMap class. The nDGridMap type T
    has to be an FMDirectionalCell or something inherited from it.

    The leafsize of the grid map is ignored since it has to be > = 1 and that
    depends on the units employed.

    The type of the heap introduced is very important for the behaviour of the
    algorithm. The following heaps are provided:

    - FMDaryHeap wrap for the Boost D_ary heap (generalization of binary heaps).
      Set by default if no other heap is specified. The arity has been set to 2
      (binary heap) since it has been tested to be the more efficient in this algorithm.
    - FMFibHeap wrap for the Boost Fibonacci heap.
    - FMPriorityQueue wrap to the std::PriorityQueue class. This heap implies the implementation
      of the Simplified FMM (SFMM) method, done automatically because of the FMPriorityQueue::increase implementation.

    Copyright (C) 2014 Javier V. Gomez and Jose Pardeiro
    www.javiervgomez.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with this program. If not, see <http://www.gnu.org/licenses/>.*/

#ifndef FM2DIR_H_
#define FM2DIR_H_

#include <iostream>
#include <cmath>
#include <algorithm>
#include <numeric>
#include <fstream>
#include <array>
#include <limits>

#include <boost/math/constants/constants.hpp>

#include "../fmm/fmdata/fmdirectionalcell.h"
#include "../fm2/fm2.hpp"
#include "../gradientdescent/gradientdescent.hpp"

#define PI boost::math::constants::pi<double>()

// TODO: include suppoert to other solvers (GMM, FIM, UFMM). It requires a better way of setting parameters.

template < class grid_t, class heap_t = FMDaryHeap<FMCell> >  class FM2Dir : public FM2<grid_t, heap_t> {

    typedef std::vector< std::array< double, grid_t::getNDims() > > path_t;
    typedef FM2<grid_t, heap_t > FM2Base;

    public:

    FM2Dir
    (const std::string& name = "FM2Dir") : FM2Base(name) { }

        /**
         * Sets the input grid in which operations will be performed.
         *
         * @param g input grid map.
         */
        virtual void setEnvironment
        (grid_t * g) {
            grid_ = g;
            narrow_band_.setMaxSize(grid_->size());
            ndims_ = grid_->getNDims();
        }

        /**
         * Sets the initial points by the indices in the nDGridMap and
         * computes the initialization of the Fast Marching Square calling
         * the init() function.
         *
         * @param initial_point contains the index of the initial point of the query.
         *
         * @param fmm2_sources contains the indices of the initial points corresponding to all black cells.
         *
         * @param goal_idx contains the index of the goal point.
         *
         * @see init()
         */

        virtual void setInitialAndGoalPoints
        (const std::vector <int> & initial_point, const std::vector <int> & fmm2_sources, const int goal_idx) {
            initial_point_ = initial_point;
            fmm2_sources_ = fmm2_sources;
            goal_idx_ = goal_idx;
        }

        /**
         * Sets the initial points by the indices in the nDGridMap and
         * computes the initialization of the Fast Marching Method calling
         * the init() function.
         *
         * @param init_points contains the indices of the init points.
         *
         * @param save_velocity selects if the velocity profile must be saved
         *
         * @see init()
         */
        virtual void setInitialPoints
        (const std::vector<int> & init_points, const bool save_velocity = false) {
            init_points_ = init_points;
            for (const int &i: init_points) {
            grid_->getCell(i).setArrivalTime(0);
            grid_->getCell(i).setDirectionalTime(0);
            grid_->getCell(i).setState(FMState::FROZEN);
            }

            if (init_points.size() > 1)
                init();
            else
                init(save_velocity, true);
        }

        /**
        * Internal function although it is set to public so it can be accessed if desired.
        *
        * Computes the Fast Marching Method initialization from the initial points given. Programmed following the paper:
          A. Valero, J.V. Gómez, S. Garrido and L. Moreno, The Path to Efficiency: Fast Marching Method for Safer,
          More Efficient Mobile Robot Trajectories, IEEE Robotics and Automation Magazine, Vol. 20, No. 4, 2013.
        *
        * @param save_velocity selects if the velocity profile must be saved
        *
        * @param directional selects if directional heuristic must be applied
        *
        * @see setInitialPoints()
        */
        virtual void init
        (const bool save_velocity = false, const bool directional = false) {
            // TODO: neighbors computed twice for every cell. We can save time here.
            // TODO: check if the previous steps have been done (loading grid map and setting initial points.)
            int j = 0;
            int n_neighs = 0;
            for (int &i: init_points_) { // For each initial point
            n_neighs = grid_->getNeighbors(i, neighbors);
                for (int s = 0; s < n_neighs; ++s){  // For each neighbor
                    j = neighbors[s];
                    if ((grid_->getCell(j).getState() ==  FMState::FROZEN) || grid_->getCell(j).isOccupied() || grid_->getCell(j).getVelocity() ==  0) // If Frozen or obstacle
                        continue;
                    else {
                        double new_arrival_time = solveEikonal(j);
                        double dir_time = 0;

                        if (directional ==  true)
                            dir_time = new_arrival_time;

                        if (grid_->getCell(j).getState() ==  FMState::NARROW) { // Updating narrow band if necessary.
                            if (new_arrival_time < grid_->getCell(j).getArrivalTime()) {
                                grid_->getCell(j).setArrivalTime(new_arrival_time);

                            if (save_velocity)
                                velocity_map_[j] = vel;

                            if (directional)
                                grid_->getCell(j).setDirectionalTime(dir_time);
                            narrow_band_.increase( &(grid_->getCell(j))  ) ;
                            }
                        }
                        else {
                            grid_->getCell(j).setState(FMState::NARROW);
                            grid_->getCell(j).setArrivalTime(new_arrival_time);

                            if (save_velocity)
                                velocity_map_[j] = vel;

                            if (directional ==  true)
                                grid_->getCell(j).setDirectionalTime(dir_time);
                            narrow_band_.push( &(grid_->getCell(j)) );
                        } // neighbors open.
                    } // neighbors not frozen.
                } // For each neighbor.
            } // For each initial point.
        } // init()

        //IMPORTANT NOTE: Assuming inc(1) = inc(y)  = ... =  leafsize_
        // Possible improvement: If we include the neighbors in the cells information
        // this could be (most probably) speeded up.
        // This implementation is focused to be used with any number of dimensions.

        /**
        * Solves the Eikonal equation for a given cell using the heuristic criteria of the FM2 Directional.
        * This function is generalized to any number of dimensions.
        *
        * @param idx index of the cell to be evaluated.
        *
        * @param idx_source index of the source cell of the wave. If this value is -1 the heuristic is not applied
        *
        * @return the distance (or time of arrival) value.
        */
        virtual double solveEikonal
        (const int & idx, const int & idx_source = -1) {
            // TODO: Here neighbors are computed and then in the computeFM. There should be a way to avoid computing
            // neighbors twice.

            int a = grid_t::getNDims(); // a parameter of the Eikonal equation.

            double updatedT;
            sumT = 0;
            sumTT = 0;
            double minTInDim = 0;

            vel = 0;

            if (idx_source == -1) {
                for (int dim = 0; dim < grid_t::getNDims(); ++dim) {
                    minTInDim = grid_->getMinValueInDim(idx, dim);
                    if (!isinf(minTInDim)) {
                        Tvalues[dim] = minTInDim;
                        sumT +=  Tvalues[dim];
                        TTvalues[dim] = Tvalues[dim]*Tvalues[dim];
                        sumTT +=  TTvalues[dim];
                    }
                    else {
                        Tvalues[dim] = 0;
                        TTvalues[dim] = 0;
                        a -= 1 ;
                    }
                }

                vel = grid_->getCell(idx).getVelocity();
            }
            else {

                for (int dim = 0; dim < grid_t::getNDims(); ++dim) {
                    minTInDim = getMinValueInDimDirectional(idx, dim);
                    if (!isinf(minTInDim)) {
                        Tvalues[dim] = minTInDim;
                        sumT +=  Tvalues[dim];
                        TTvalues[dim] = Tvalues[dim]*Tvalues[dim];
                        sumTT +=  TTvalues[dim];
                    }
                    else {
                        Tvalues[dim] = 0;
                        TTvalues[dim] = 0;
                        a -= 1 ;
                    }
                }

                if (grid_->getCell(idx).getVelocity() < grid_->getCell(idx_source).getVelocity() && grid_->getCell(idx).getVelocity() > 0.05)
                    vel = 1;
                else
                    vel = grid_->getCell(idx).getVelocity();
            }

            double b = -2*sumT;
            double c = sumTT - grid_->getLeafSize() * grid_->getLeafSize()/(vel * vel); // leafsize not taken into account here.
            double quad_term = b*b - 4*a*c;
            if (quad_term < 0) {
                double minT = *(std::min_element(Tvalues.begin(), Tvalues.end()));
                updatedT = 1/(vel * vel) + minT; // leafsize not taken into account here.
            }
            else
                updatedT = (-b + sqrt(quad_term))/(2*a);

            return updatedT;
        }

        /**
         * Main Fast Marching Function. It requires to call first the setInitialPoints() function.
         *
         * @param stop selects if the wave has to stop when it arrives to the goal point
         *
         * @param directional selects if directional heuristic must be applied
         *
         * @see setInitialPoints()
         */

        virtual void computeFM
        (const bool stop = true, const bool directional = false) {
            // TODO: check if the previous steps have been done (initialization).
            int j =  0;
            int n_neighs = 0;
            bool stopWavePropagation = 0;

            while (narrow_band_.size() > 0 && stopWavePropagation ==  0) {
                int idxMin = narrow_band_.popMinIdx();
                n_neighs = grid_->getNeighbors(idxMin, neighbors);
                grid_->getCell(idxMin).setState(FMState::FROZEN);

                for (int s = 0; s < n_neighs; ++s) {
                    j = neighbors[s];
                    if ((grid_->getCell(j).getState() ==  FMState::FROZEN) || grid_->getCell(j).isOccupied()) // If Frozen or obstacle
                        continue;
                    else {
                        double new_arrival_time = solveEikonal(j);
                        double dir_time = 0;

                        if (directional ==  true)
                            dir_time = solveEikonal(j, idxMin);

                        if (grid_->getCell(j).getState() ==  FMState::NARROW) { // Updating narrow band if necessary.
                            if (new_arrival_time < grid_->getCell(j).getArrivalTime()) {
                                grid_->getCell(j).setArrivalTime(new_arrival_time);
                                narrow_band_.increase( &(grid_->getCell(j)) );
                                velocity_map_[j] = vel;
                            }

                            if (directional)
                                if (dir_time < grid_->getCell(j).getDirectionalTime())
                                    grid_->getCell(j).setDirectionalTime(dir_time);
                        }
                        else {
                            grid_->getCell(j).setState(FMState::NARROW);
                            grid_->getCell(j).setArrivalTime(new_arrival_time);
                            velocity_map_[j] = vel;

                            if (directional)
                                grid_->getCell(j).setDirectionalTime(dir_time);
                            narrow_band_.push( &(grid_->getCell(j)) );
                        } // neighbors open.
                    } // neighbors not frozen.
                    if (idxMin ==  initial_point_[0] && stop)
                        stopWavePropagation = 1;
                } // For each neighbor.
            } // while narrow band not empty
        }

        /**
         * Main Fast Marching Square Directional Function with velocity saturation. It requires to call first the setInitialPoints() function.
         *
         * @param maxDistance saturation distance (relative, where 1 means maximum distance). If this value is -1 (default) the velocities map is not saturated.
         *
         * @see setInitialPoints()
         */
        virtual void computeFM2Directional
        (const float maxDistance = -1) {
            maxDistance_ = maxDistance;

            velocity_map_.resize(grid_->size());
            if (maxDistance_ != -1)
                computeVelocitiesMap(true);
            else
                computeVelocitiesMap();

            // According to the theoretical basis the wave is expanded from the goal point to the initial point.
            std::vector <int> wave_init;
            wave_init.push_back(goal_idx_);
            setInitialPoints(wave_init, true);
            computeFM(true, true);
        }

        /**
         * Computes the path from the given index to a minimum (the one
         * gradient descent choses) and returns the velocity. According to
         * the theoretical basis the wave is expanded from the goal point
         * to the initial point. For these reasons the gradient must to be
         * applied from the initial point.
         *
         * No checks are done (points in the borders, points in obstacles...).
         *
         * The included scripts will parse the saved path.
         *
         * @param path the resulting path (output).
         *
         * @param velocity the resulting path (output).
         */
        virtual void computePath
        (path_t * p, std::vector <double> * path_velocity) {
            path_t* path_ = p;
            constexpr int ndims = grid_t::getNDims();

            GradientDescent< nDGridMap<FMDirectionalCell, ndims> > grad;
            grad.apply_directional(*grid_,initial_point_[0],*path_, velocity_map_, *path_velocity);
        }

        /**
        * For a cell with index idx, obtains the minimum value of the neigbours in dimension dim looking the
        * DirectionalTime value.
        *
        * @param idx index of the cell accessed.
        *
        * @param dim dimension in which the minimum is examinated (0: x, 1: y, 2: z, etc).
        *
        * @return the corresponding minimum value.
        * */
        double getMinValueInDimDirectional
        (const int idx, const int dim) {
            // n_neighs = 0; // How many neighbors obtained in that dimension.
            constexpr int ndims = grid_->getNDims();
            std::array<int, ndims> n;
            int n_neighs = grid_->getNumberNeighborsInDim(idx,n,dim);

            if (n_neighs ==  1)
                return grid_->getCell(n[0]).getDirectionalTime();
            else
                return (grid_->getCell(n[0]).getDirectionalTime()<grid_->getCell(n[1]).getDirectionalTime()) ? grid_->getCell(n[0]).getDirectionalTime() : grid_->getCell(n[1]).getDirectionalTime();
        }

    private:

        /**
         * Computes the velocities map of the FM2 algorithm.
         *
         * @param saturate select if the potential is saturated according to maxDistance_ .
         */

        void computeVelocitiesMap
        (bool saturate = false) {
            setInitialPoints(fmm2_sources_);
            computeFM(false);

            //Rescaling and saturating to relative velocities: [0-1]
            double maxValue = grid_->getMaxValue();
            double maxVelocity = 0;

            if (saturate)
                maxVelocity = maxDistance_ / grid_->getLeafSize();

            for (int i = 0; i < grid_->size(); i++) {
                double velocity = grid_->getCell(i).getValue() / maxValue;

                if (saturate)
                    if (velocity < maxVelocity)
                        grid_->getCell(i).setVelocity(velocity / maxVelocity);
                    else
                        grid_->getCell(i).setVelocity(1);
                else
                    grid_->getCell(i).setVelocity(velocity);

              // Restarting grid values for second wave expasion.
              grid_->getCell(i).setValue(std::numeric_limits<double>::infinity());
              grid_->getCell(i).setDirectionalTime(std::numeric_limits<double>::infinity());
              grid_->getCell(i).setState(FMState::OPEN);
            }
        }

    protected:
        using FMM<grid_t, heap_t>::grid_;
        using FMM<grid_t, heap_t>::neighbors;
        using FMM<grid_t, heap_t>::init_points_;
        using FMM<grid_t, heap_t>::Tvalues;
        using FMM<grid_t, heap_t>::TTvalues;
        using FMM<grid_t, heap_t>::narrow_band_;

    private:
        double sumT; /*!< Auxiliar value wich computes T1+T2+T3... Useful for generalizing the Eikonal solver. */
        double sumTT; /*!< Auxiliar value wich computes T1^2+T2^2+T3^2... Useful for generalizing the Eikonal solver. */
        double sumDistance; /*!< Auxiliar value wich computes euclidean distance between narrow band and goal point. Useful for generalizing the Eikonal solver with euristic. */

        double vel; /*!< Auxiliar value wich contains the velocity of the cell used on the Eikonal solver. */
        double maxDistance_; /*!< Distance value to saturate the first potential. */

        int goal_idx_; /*!< Goal point for the Fast Marching Square Star. */
        std::vector<int> fmm2_sources_;	/*!< Wave propagation sources for the Fast Marching Square Star. */
        std::vector<int> initial_point_;	/*!< Initial point for the Fast Marching Square Star. */

        std::array<int, grid_t::getNDims()-1> d_;
        std::array<int, grid_t::getNDims()> dimsize_;
        std::vector<double> velocity_map_; /*!< Auxiliar vector which contains the final velocity map of the environment. */

        int ndims_;
        double angle_wave, angle_velocity;
        double diff_angle;
};

#endif /* FM2DIR_H_*/


#ifndef GUNEROTRANSACTIONSENDCIRCUIT_H_
#define GUNEROTRANSACTIONSENDCIRCUIT_H_

#include <deque>
#include <boost/optional.hpp>
#include <boost/static_assert.hpp>
#include <libff/common/utils.hpp>
#include <libsnark/common/default_types/r1cs_gg_ppzksnark_pp.hpp>
#include <libsnark/common/default_types/r1cs_gg_ppzksnark_pp.hpp>
#include <libsnark/relations/constraint_satisfaction_problems/r1cs/examples/r1cs_examples.hpp>
#include <libsnark/zk_proof_systems/ppzksnark/r1cs_gg_ppzksnark/r1cs_gg_ppzksnark.hpp>
#include <libsnark/zk_proof_systems/ppzksnark/r1cs_ppzksnark/r1cs_ppzksnark.hpp>
#include <libsnark/zk_proof_systems/ppzksnark/uscs_ppzksnark/uscs_ppzksnark.hpp>
#include <libff/algebra/fields/field_utils.hpp>
#include <libff/algebra/scalar_multiplication/multiexp.hpp>
#include <libsnark/common/default_types/r1cs_ppzksnark_pp.hpp>
#include <libsnark/zk_proof_systems/ppzksnark/r1cs_ppzksnark/r1cs_ppzksnark.hpp>
#include <libff/algebra/curves/edwards/edwards_pp.hpp>
#include <libff/algebra/curves/mnt/mnt4/mnt4_pp.hpp>
#include <libff/algebra/curves/mnt/mnt6/mnt6_pp.hpp>
#include <libff/common/utils.hpp>

#include <libsnark/gadgetlib1/gadgets/merkle_tree/merkle_tree_check_read_gadget.hpp>
#include <libsnark/gadgetlib1/gadgets/merkle_tree/merkle_tree_check_update_gadget.hpp>

#include "gunerotransactionsend_gadget.hpp"
#include "GuneroProof.hpp"

using namespace libsnark;

namespace gunero {

class GuneroTransactionSendWitness{
public:
    uint256 W;
    uint256 T;
    uint256 V_S;
    uint256 V_R;
    uint256 L_P;

    GuneroTransactionSendWitness() {}
    GuneroTransactionSendWitness(
        const uint256& pW,
        const uint256& pT,
        const uint256& pV_S,
        const uint256& pV_R,
        const uint256& pL_P
    ) : W(pW),
        T(pT),
        V_S(pV_S),
        V_R(pV_R),
        L_P(pL_P)
    {
    }
    ~GuneroTransactionSendWitness() {}

    ADD_SERIALIZE_METHODS;

    template <typename Stream, typename Operation>
    inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
        READWRITE(W);
        READWRITE(T);
        READWRITE(V_S);
        READWRITE(V_R);
        READWRITE(L_P);
    }

    friend std::ostream& operator<<(std::ostream &out, const GuneroTransactionSendWitness &witness)
    {
        ::Serialize(out, witness, 1, 1);

        return out;
    }

    friend std::istream& operator>>(std::istream &in, GuneroTransactionSendWitness &witness)
    {
        ::Unserialize(in, witness, 1, 1);

        return in;
    }
};

///// TRANSACTION SEND PROOF /////
// With this proof, we are validating that the sender of the token is accepting that this token's ownership should
// be transferred to the new transaction hash given. The "account view hash" validates that this proof is consistent
// with the others generated, and also serves as an additional precaution for others using this proof to validate an
// unauthorized release of the token to a party not covered in the transaction.
// Public Parameters:
// Current Authorization Root Hash (W)
// Token UID (T)
// Sender Account View Hash (V_S)
// Receiver Account View Hash (V_R)
// Previous Transaction Hash (L_P)

// Private Parameters:
// Sender Private Key (s_S)
// Sender Account View Randomizer (r_S)
// Receiver Account View Randomizer (r_R)
// Previous Sender Account Address (A_PS)
// Previous Authorization Root Hash (W_P)
// alt: Receiver Proof Public Key (P_proof_R)

//1) Obtain A_S from s_S through EDCSA operations
//1 alt) Obtain P_proof_S from s_S through PRF operations
//2) Validate V_S == hash(A_S, hash(W, r_S)) (View Hash is consistent for Sender)
//2 alt) Validate V_S == hash(P_proof_S, hash(W, r_S)) (View Hash is consistent for Sender)
//3) Validate V_R == hash(A_R, hash(W, r_R)) (View Hash is consistent for Receiver)
//3 alt) Validate V_R == hash(P_proof_R, hash(W, r_R)) (View Hash is consistent for Receiver)
//4) Validate L_P == hash(A_PS, hash(s_S, hash(T, W_P))) (The send proof is valid, sender owns token)
template<typename FieldT, typename BaseT, typename HashT>
class GuneroTransactionSendCircuit
{
public:
    GuneroTransactionSendCircuit()
    {}
    ~GuneroTransactionSendCircuit() {}

    void generate(
        const std::string& r1csPath,
        const std::string& pkPath,
        const std::string& vkPath
    ) {
        protoboard<FieldT> pb;
        gunerotransactionsend_gadget<FieldT, BaseT, HashT> gunero(pb);

        gunero.generate_r1cs_constraints(r1csPath, pkPath, vkPath);
    }

    bool prove(
        const uint256& pW,
        const uint256& pT,
        const uint256& pV_S,
        const uint256& pV_R,
        const uint256& pL_P,
        const uint252& ps_S,
        const uint256& pr_S,
        const uint256& pr_R,
        const uint160& pA_PS,
        const uint256& pW_P,
        const uint256& pP_proof_R,
        const r1cs_ppzksnark_proving_key<BaseT>& pk,
        const r1cs_ppzksnark_verification_key<BaseT>& vk,
        GuneroProof& proof
    )
    {
#ifdef DEBUG
        libff::print_header("Gunero witness (proof)");
#endif

        {
            r1cs_primary_input<FieldT> primary_input;
            r1cs_auxiliary_input<FieldT> aux_input;
            {
                protoboard<FieldT> pb;
                {
#ifdef DEBUG
                    libff::print_header("Gunero gunerotransactionsend_gadget.load_r1cs_constraints()");
#endif

                    gunerotransactionsend_gadget<FieldT, BaseT, HashT> gunero(pb);

                    gunero.generate_r1cs_witness(
                        pW,
                        pT,
                        pV_S,
                        pV_R,
                        pL_P,
                        ps_S,
                        pr_S,
                        pr_R,
                        pA_PS,
                        pW_P,
                        pP_proof_R
                    );

#ifdef DEBUG
                    printf("\n"); libff::print_indent(); libff::print_mem("after gunerotransactionsend_gadget.load_r1cs_constraints()"); libff::print_time("after gunerotransactionsend_gadget.load_r1cs_constraints()");
#endif
                }

                // The constraint system must be satisfied or there is an unimplemented
                // or incorrect sanity check above. Or the constraint system is broken!
                assert(pb.is_satisfied());

                // TODO: These are copies, which is not strictly necessary.
                primary_input = pb.primary_input();
                aux_input = pb.auxiliary_input();

                // Swap A and B if it's beneficial (less arithmetic in G2)
                // In our circuit, we already know that it's beneficial
                // to swap, but it takes so little time to perform this
                // estimate that it doesn't matter if we check every time.
                // pb.constraint_system.swap_AB_if_beneficial();

                //Test witness_map()
                {
                    r1cs_primary_input<FieldT> primary_input_test = gunerotransactionsend_gadget<FieldT, BaseT, HashT>::witness_map(
                        pW,
                        pT,
                        pV_S,
                        pV_R,
                        pL_P
                    );
                    assert(primary_input == primary_input_test);
                }
            }

            r1cs_ppzksnark_proof<BaseT> r1cs_proof = r1cs_ppzksnark_prover<BaseT>(
                pk,
                primary_input,
                aux_input
            );

            proof = GuneroProof(r1cs_proof);

#ifdef DEBUG
            printf("\n"); libff::print_indent(); libff::print_mem("after witness (proof)"); libff::print_time("after witness (proof)");
#endif
        }

        //Verify
        {
            r1cs_primary_input<FieldT> primary_input = gunerotransactionsend_gadget<FieldT, BaseT, HashT>::witness_map(
                pW,
                pT,
                pV_S,
                pV_R,
                pL_P
            );

            return r1cs_ppzksnark_verifier_strong_IC<BaseT>(vk, primary_input, proof.to_libsnark_proof<r1cs_ppzksnark_proof<BaseT>>());
        }
    }

    bool verify(
        const uint256& pW,
        const uint256& pT,
        const uint256& pV_S,
        const uint256& pV_R,
        const uint256& pL_P,
        const GuneroProof& proof,
        const r1cs_ppzksnark_verification_key<BaseT>& vk,
        const r1cs_ppzksnark_processed_verification_key<BaseT>& vk_precomp
        )
    {
        try
        {
            r1cs_primary_input<FieldT> primary_input = gunerotransactionsend_gadget<FieldT, BaseT, HashT>::witness_map(
                pW,
                pT,
                pV_S,
                pV_R,
                pL_P
            );

            r1cs_ppzksnark_proof<BaseT> r1cs_proof = proof.to_libsnark_proof<r1cs_ppzksnark_proof<BaseT>>();

            ProofVerifier<BaseT> verifierEnabled = ProofVerifier<BaseT>::Strict();

            bool verified = verifierEnabled.check(
                vk,
                vk_precomp,
                primary_input,
                r1cs_proof
            );

            if (verified)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        catch (...)
        {
            return false;
        }
    }
};

} // end namespace `gunero`

#endif /* GUNEROTRANSACTIONSENDCIRCUIT_H_ */


/* 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 "exact_toffoli_synthesis.hpp"

#include <array>
#include <iostream>

#include <boost/format.hpp>

#include <core/utils/timer.hpp>
#include <classical/abc/abc_api.hpp>
#include <reversible/functions/add_gates.hpp>
#include <reversible/utils/truth_table_helpers.hpp>

#include <sat/bsat/satSolver.h>

namespace cirkit
{

/******************************************************************************
 * Types                                                                      *
 ******************************************************************************/

class exact_toffoli_synthesis_manager
{
public:
  exact_toffoli_synthesis_manager( const binary_truth_table& spec, const properties::ptr& settings );

  void run( circuit& circ );

private:
  /* indexes:
   *   i    : target, line
   *   j, k : controls
   *   l    : gate
   *   t    : truth table row
   */
  int var_offset( int l ) const;
  int sim_var( int l, int i, int t ) const;
  int not_var( int l, int i ) const;
  int cnot_var( int l, int i, int j ) const;
  int tof_var( int l, int i, int j, int k ) const;

  int equal_other_clauses( int g, int l, int t, int i );
  int not_clauses( int l, int i, int t );
  int cnot_clauses( int l, int i, int j, int t );
  int tof_clauses( int l, int i, int j, int k, int t );
  int gate_clauses( int l );
  std::vector<int> spec_assumptions() const;
  void create_circuit( circuit& circ ) const;

  int symmetry_breaking_ordering( int l );
  int symmetry_breaking_not_same( int l );

  template<typename ...Lits>
  int add_clause( Lits... literals )
  {
    std::array<int, sizeof...( Lits )> cls = {{ literals... }};
    int * p = &cls.front();
    return abc::sat_solver_addclause( solver.get(), p, p + sizeof...( Lits ) );
  }

  int make_lit( int var, int c ) const;

  /* range helpers */
  template<typename Fn>
  void for_each_not( Fn&& f )
  {
    for ( auto i = 0u; i < n; ++i )
    {
      f( i );
    }
  }

  template<typename Fn>
  void for_each_cnot( Fn&& f )
  {
    for ( auto i = 0u; i < n; ++i )
    {
      for ( auto j = 1u; j < n; ++j )
      {
        f( i, j );
      }
    }
  }

  template<typename Fn>
  void for_each_tof( Fn&& f )
  {
    for ( auto i = 0u; i < n; ++i )
    {
      for ( auto j = 2u; j < n; ++j )
      {
        for ( auto k = 1u; k < j; ++k )
        {
          f( i, j, k );
        }
      }
    }
  }

  void debug_vars();

private:
  const binary_truth_table& spec;
  unsigned                  n, r;
  int                       num_vars_per_gate;
  int                       sim_offset;

  std::vector<boost::dynamic_bitset<>> spec_vec;

  /* solver */
  std::unique_ptr<abc::sat_solver, void(*)(abc::sat_solver*)> solver;

  /* settings */
  bool verbose = false;
};

/******************************************************************************
 * Private functions                                                          *
 ******************************************************************************/

exact_toffoli_synthesis_manager::exact_toffoli_synthesis_manager( const binary_truth_table& spec, const properties::ptr& settings )
  : spec( spec ),
    n( spec.num_inputs() ),
    solver( abc::sat_solver_new(), abc::sat_solver_delete ),
    verbose( get( settings, "verbose", verbose ) )
{
  /* we have:
     - 2^n             sim vars
     - n               NOT vars
     - n(n-1)          CNOT vars
     - n(n-1)(n-2) / 2 TOF vars
  */
  num_vars_per_gate = n * ( 1 << n ) + n + n * ( n - 1 ) + n * ( ( n - 1 ) * ( n - 2 ) ) / 2;
  sim_offset = n * ( 1 << n );

  spec_vec = truth_table_to_bitset_vector( spec );
}

void exact_toffoli_synthesis_manager::run( circuit& circ )
{
  r = 0;
  auto nvars = 0;
  abc::sat_solver_restart( solver.get() );

  while ( true )
  {
    ++r;
    nvars += num_vars_per_gate;

    std::cout << "[i] try to find optimum circuit with " << r << " gates" << std::endl;

    abc::sat_solver_setnvars( solver.get(), nvars );

    gate_clauses( r - 1 );

    if ( r > 1 )
    {
      symmetry_breaking_ordering( r - 1 );
      symmetry_breaking_not_same( r - 1 );
    }
    auto assump = spec_assumptions();
    int * p = &assump.front();

    const auto result = abc::sat_solver_solve( solver.get(), p, p + assump.size(), 0, 0, 0, 0 );

    if ( result == 1 ) /* SAT */
    {
      std::cout << "[i] found circuit with " << r << " gates" << std::endl;
      create_circuit( circ );
      return;
    }
  }

  assert( false );
}

inline int exact_toffoli_synthesis_manager::var_offset( int l ) const
{
  assert( l < static_cast<int>( r ) );

  return l * num_vars_per_gate;
}

inline int exact_toffoli_synthesis_manager::sim_var( int l, int i, int t ) const
{
  assert( l < static_cast<int>( r ) );
  assert( i < static_cast<int>( n ) );
  assert( t < static_cast<int>( 1u << n ) );

  return var_offset( l ) + i * ( 1 << n ) + t;
}

inline int exact_toffoli_synthesis_manager::not_var( int l, int i ) const
{
  assert( l < static_cast<int>( r ) );
  assert( i < static_cast<int>( n ) );

  return var_offset( l ) + sim_offset + i;
}

inline int exact_toffoli_synthesis_manager::cnot_var( int l, int i, int j ) const
{
  assert( l < static_cast<int>( r ) );
  assert( i < static_cast<int>( n ) );
  assert( j > 0 && j < static_cast<int>( n ) );

  return var_offset( l ) + sim_offset + n + i * ( n - 1 ) + j - 1;
}

inline int exact_toffoli_synthesis_manager::tof_var( int l, int i, int j, int k ) const
{
  assert( l < static_cast<int>( r ) );
  assert( i < static_cast<int>( n ) );
  assert( j > 1 && j < static_cast<int>( n ) );
  assert( k > 0 && k < static_cast<int>( j ) );

  auto offset = var_offset( l ) + sim_offset + n + n * ( n - 1 );
  offset += i * ( ( n - 2 ) * ( n - 1 ) ) / 2; /* offset based on target */
  offset += ( ( j - 2 ) * ( j - 1 ) ) / 2; /* right j entry */

  return offset + k - 1;
}

inline int exact_toffoli_synthesis_manager::equal_other_clauses( int g, int l, int t, int i )
{
  for ( auto ii = 0u; ii < n; ++ii )
  {
    if ( static_cast<int>( ii ) == i ) continue;

    if ( l == 0 )
    {
      const auto bit = ( t >> ii ) & 1;
      add_clause( make_lit( g, 1 ), make_lit( sim_var( l, ii, t ), 1 - bit ) );
    }
    else
    {
      add_clause( make_lit( g, 1 ), make_lit( sim_var( l - 1, ii, t ), 0 ), make_lit( sim_var( l, ii, t ), 1 ) );
      add_clause( make_lit( g, 1 ), make_lit( sim_var( l - 1, ii, t ), 1 ), make_lit( sim_var( l, ii, t ), 0 ) );
    }
  }

  return 1;
}

int exact_toffoli_synthesis_manager::not_clauses( int l, int i, int t )
{
  const auto g = not_var( l, i );
  equal_other_clauses( g, l, t, i );

  if ( l == 0 )
  {
    const auto bit = ( t >> i ) & 1;
    add_clause( make_lit( g, 1 ), make_lit( sim_var( l, i, t ), bit ) );
  }
  else
  {
    add_clause( make_lit( g, 1 ), make_lit( sim_var( l - 1, i, t ), 0 ), make_lit( sim_var( l, i, t ), 0 ) );
    add_clause( make_lit( g, 1 ), make_lit( sim_var( l - 1, i, t ), 1 ), make_lit( sim_var( l, i, t ), 1 ) );
  }

  return 1;
}

int exact_toffoli_synthesis_manager::cnot_clauses( int l, int i, int j, int t )
{
  const auto g = cnot_var( l, i, j );
  equal_other_clauses( g, l, t, i );

  const auto c1 = ( i + j ) % n;

  if ( l == 0 )
  {
    const auto tbit = ( t >> i ) & 1;
    const auto cbit = ( t >> c1 ) & 1;
    add_clause( make_lit( g, 1 ), make_lit( sim_var( l, i, t ), tbit == cbit ) );
  }
  else
  {
    add_clause( make_lit( g, 1 ), make_lit( sim_var( l - 1, i, t ), 0 ), make_lit( sim_var( l - 1, c1, t ), 0 ), make_lit( sim_var( l, i, t ), 1 ) );
    add_clause( make_lit( g, 1 ), make_lit( sim_var( l - 1, i, t ), 0 ), make_lit( sim_var( l - 1, c1, t ), 1 ), make_lit( sim_var( l, i, t ), 0 ) );
    add_clause( make_lit( g, 1 ), make_lit( sim_var( l - 1, i, t ), 1 ), make_lit( sim_var( l - 1, c1, t ), 0 ), make_lit( sim_var( l, i, t ), 0 ) );
    add_clause( make_lit( g, 1 ), make_lit( sim_var( l - 1, i, t ), 1 ), make_lit( sim_var( l - 1, c1, t ), 1 ), make_lit( sim_var( l, i, t ), 1 ) );
  }

  return 1;
}

int exact_toffoli_synthesis_manager::tof_clauses( int l, int i, int j, int k, int t )
{
  const auto g = tof_var( l, i, j, k );
  equal_other_clauses( g, l, t, i );

  const auto c1 = ( i + j ) % n;
  const auto c2 = ( i + k ) % n;

  if ( l == 0 )
  {
    const auto tbit = ( t >> i ) & 1;
    const auto cbit1 = ( t >> c1 ) & 1;
    const auto cbit2 = ( t >> c2 ) & 1;
    add_clause( make_lit( g, 1 ), make_lit( sim_var( l, i, t ), tbit == ( cbit1 && cbit2 ) ) );
  }
  else
  {
    add_clause( make_lit( g, 1 ), make_lit( sim_var( l - 1, i, t ), 1 ), make_lit( sim_var( l - 1, c1, t ), 0 ), make_lit( sim_var( l, i, t ), 0 ) );
    add_clause( make_lit( g, 1 ), make_lit( sim_var( l - 1, i, t ), 0 ), make_lit( sim_var( l - 1, c1, t ), 0 ), make_lit( sim_var( l, i, t ), 1 ) );
    add_clause( make_lit( g, 1 ), make_lit( sim_var( l - 1, i, t ), 1 ), make_lit( sim_var( l - 1, c2, t ), 0 ), make_lit( sim_var( l, i, t ), 0 ) );
    add_clause( make_lit( g, 1 ), make_lit( sim_var( l - 1, i, t ), 0 ), make_lit( sim_var( l - 1, c2, t ), 0 ), make_lit( sim_var( l, i, t ), 1 ) );
    add_clause( make_lit( g, 1 ), make_lit( sim_var( l - 1, i, t ), 1 ), make_lit( sim_var( l - 1, c1, t ), 1 ), make_lit( sim_var( l - 1, c2, t ), 1 ), make_lit( sim_var( l, i, t ), 1 ) );
    add_clause( make_lit( g, 1 ), make_lit( sim_var( l - 1, i, t ), 0 ), make_lit( sim_var( l - 1, c1, t ), 1 ), make_lit( sim_var( l - 1, c2, t ), 1 ), make_lit( sim_var( l, i, t ), 0 ) );
  }

  return 1;
}

int exact_toffoli_synthesis_manager::symmetry_breaking_ordering( int l )
{
  /* no CNOT( i, j ) before NOT( i ) */
  for_each_cnot( [this, l]( int i, int j ) {
      add_clause( make_lit( not_var( l, i ), 1 ), make_lit( cnot_var( l - 1, i, j ), 1 ) );
    } );
  /* no TOF( i, j, k ) before NOT( i ) */
  for_each_tof( [this, l]( int i, int j, int k ) {
      add_clause( make_lit( not_var( l, i ), 1 ), make_lit( tof_var( l - 1, i, j, k ), 1 ) );
    } );
  /* order CNOTs with same target */
  for_each_cnot( [this, l]( int i, int j ) {
      for ( auto jj = 1; jj < j; ++jj )
      {
        add_clause( make_lit( cnot_var( l, i, j ), 1 ), make_lit( cnot_var( l - 1, i, jj ), 1 ) );
      }
    } );
  /* order of TOFs with same target */
  for_each_tof( [this, l]( int i, int j, int k ) {
      for ( auto jj = 2; jj < j; ++jj )
      {
        for ( auto kk = 1; kk < jj; ++kk )
        {
          add_clause( make_lit( tof_var( l, i, j, k ), 1 ), make_lit( tof_var( l - 1, i, jj, kk ), 1 ) );
        }
      }
      for ( auto kk = 1; kk < j; ++kk )
      {
        add_clause( make_lit( tof_var( l, i, j, k ), 1 ), make_lit( tof_var( l - 1, i, j, kk ), 1 ) );
      }
    } );
  /* no CNOT( i, j ) before NOT( i' ) where i' not in {i, j} */
  for_each_cnot( [this, l]( int i, int j ) {
      for ( auto ii = 0u; ii < n; ++ii )
      {
        if ( static_cast<int>( ii ) == i || ii == ( ( i + j ) % n ) ) { continue; }
        add_clause( make_lit( not_var( l, ii ), 1 ), make_lit( cnot_var( l - 1, i, j ), 1 ) );
      }
    } );
  /* no TOF( i, j, k ) before NOT( i' ) where i' not in {i, j, k} */
  for_each_tof( [this, l]( int i, int j, int k ) {
      for ( auto ii = 0u; ii < n; ++ii )
      {
        if ( static_cast<int>( ii ) == i || ii == ( ( i + j ) % n ) || ii == ( ( i + k ) % n ) ) { continue; }
        add_clause( make_lit( not_var( l, ii ), 1 ), make_lit( tof_var( l - 1, i, j, k ), 1 ) );
      }
    } );

  return 1;
}

int exact_toffoli_synthesis_manager::symmetry_breaking_not_same( int l )
{
  for_each_not( [this, l]( int i ) {
      add_clause( make_lit( not_var( l, i ), 1 ), make_lit( not_var( l - 1, i ), 1 ) );
    } );
  for_each_cnot( [this, l]( int i, int j ) {
      add_clause( make_lit( cnot_var( l, i, j ), 1 ), make_lit( cnot_var( l - 1, i, j ), 1 ) );
    } );
  for_each_tof( [this, l]( int i, int j, int k ) {
      add_clause( make_lit( tof_var( l, i, j, k ), 1 ), make_lit( tof_var( l - 1, i, j, k ), 1 ) );
    } );

  return 1;
}

void exact_toffoli_synthesis_manager::debug_vars()
{
  for ( auto l = 0u; l < r; ++l )
  {
    for ( auto i = 0u; i < n; ++i )
    {
      for ( auto t = 0u; t < ( 1u << n ); ++t )
      {
        std::cout << boost::format( "%6d sim_var( %d, %d, %d )" ) % sim_var( l, i, t ) % l % i % t << std::endl;
      }
    }

    for ( auto i = 0u; i < n; ++i )
    {
      std::cout << boost::format( "%6d not_var( %d, %d )" ) % not_var( l, i ) % l % i << std::endl;
    }

    for ( auto i = 0u; i < n; ++i )
    {
      for ( auto j = 1u; j < n; ++j )
      {
        std::cout << boost::format( "%6d cnot_var( %d, %d, %d )" ) % cnot_var( l, i, j ) % l % i % j << std::endl;
      }
    }

    for ( auto i = 0u; i < n; ++i )
    {
      for ( auto j = 2u; j < n; ++j )
      {
        for ( auto k = 1u; k < j; ++k )
        {
          std::cout << boost::format( "%6d tof_var( %d, %d, %d, %d )" ) % tof_var( l, i, j, k ) % l % i % j % k << std::endl;
        }
      }
    }
  }
}

int exact_toffoli_synthesis_manager::gate_clauses( int l )
{
  for ( auto t = 0u; t < ( 1u << n ); ++t )
  {
    for_each_not( [this, l, t]( int i ) { not_clauses( l, i, t ); } );
    for_each_cnot( [this, l, t]( int i, int j ) { cnot_clauses( l, i, j, t ); } );
    for_each_tof( [this, l, t]( int i, int j, int k ) { tof_clauses( l, i, j, k, t ); } );
  }

  /* at least one gate */
  std::vector<int> gates;

  for ( auto i = 0u; i < n; ++i )
  {
    gates.push_back( make_lit( not_var( l, i ), 0 ) );

    for ( auto j = 1u; j < n; ++j )
    {
      gates.push_back( make_lit( cnot_var( l, i, j ), 0 ) );

      for ( auto k = 1u; k < j; ++k )
      {
        gates.push_back( make_lit( tof_var( l, i, j, k ), 0 ) );
      }
    }
  }

  int * p = &gates.front();
  abc::sat_solver_addclause( solver.get(), p, p + gates.size() );

  return 1;
}

std::vector<int> exact_toffoli_synthesis_manager::spec_assumptions() const
{
  std::vector<int> assumptions;
  assumptions.reserve(  sim_offset );

  for ( auto i = 0u; i < n; ++i )
  {
    for ( auto t = 0u; t < ( 1u << n ); ++t )
    {
      assumptions.push_back( make_lit( sim_var( r - 1, i, t ), spec_vec[t][i] ? 0 : 1 ) );
    }
  }

  return assumptions;
}

void exact_toffoli_synthesis_manager::create_circuit( circuit& circ ) const
{
  circ.set_lines( n );

  for ( auto l = 0u; l < r; ++l )
  {
    auto added = false;

    for ( auto i = 0u; i < n; ++i )
    {
      if ( abc::sat_solver_var_value( solver.get(), not_var( l, i ) ) )
      {
        if ( !added )
        {
          added = true;
          append_not( circ, n - i - 1 );
        }
        else { std::cout << "[w] alternative gate found" << std::endl; }
      }

      for ( auto j = 1u; j < n; ++j )
      {
        if ( abc::sat_solver_var_value( solver.get(), cnot_var( l, i, j ) ) )
        {
          if ( !added )
          {
            added = true;
            append_cnot( circ, n - ( ( i + j ) % n ) - 1, n - i - 1 );
          }
          else { std::cout << "[w] alternative gate found" << std::endl; }
        }

        for ( auto k = 1u; k < j; ++k )
        {
          if ( abc::sat_solver_var_value( solver.get(), tof_var( l, i, j, k ) ) )
          {
            if ( !added )
            {
              added = true;
              append_toffoli( circ )( n - ( ( i + k ) % n ) - 1, n - ( ( i + j ) % n ) - 1 )( n - i - 1 );
            }
            else { std::cout << "[w] alternative gate found" << std::endl; }
          }
        }
      }
    }
  }
}

inline int exact_toffoli_synthesis_manager::make_lit( int var, int c ) const
{
  return abc::Abc_Var2Lit( var, c );
}

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

bool exact_toffoli_synthesis( circuit& circ, const binary_truth_table& spec, const properties::ptr& settings, const properties::ptr& statistics )
{
  properties_timer t( statistics );

  exact_toffoli_synthesis_manager mgr( spec, settings );

  mgr.run( circ );

  return true;
}

}

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