/****************************************************************************
* VCGLib                                                            o o     *
* Visual and Computer Graphics Library                            o     o   *
*                                                                _   O  _   *
* Copyright(C) 2004-2016                                           \/)\/    *
* Visual Computing Lab                                            /\/|      *
* ISTI - Italian National Research Council                           |      *
*                                                                    \      *
* All rights reserved.                                                      *
*                                                                           *
* 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 2 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 (http://www.gnu.org/licenses/gpl.txt)          *
* for more details.                                                         *
*                                                                           *
****************************************************************************/

#ifndef __RASTERIZED_OUTLINE2_PACKER_H__
#define __RASTERIZED_OUTLINE2_PACKER_H__

#include <vcg/space/rect_packer.h>
#include <vcg/complex/algorithms/outline_support.h>

namespace vcg
{


class RasterizedOutline2
{
private:
    //the grid is the "bounding grid" of the polygon, which is returned by the rasterization process
    //this is a vector of "bounding grids", there is one for each rasterization (different rotation or whatever)
    std::vector < std::vector< std::vector<int> > > grids;

    //points: the points which make the polygon
    std::vector<Point2f> points;

    //top: a vector containing the number of cells (for the i-th column starting from left) from the
    //FIRST NON-EMTPY cell at the bottom to the LAST NON-EMPTY CELL at the top (there is one top vector for each rasterization)
    std::vector< std::vector<int> > deltaY;

    //bottom: a vector containing the number of EMPTY cells found starting from the bottom
    //until the first NON-EMPTY cell is found (there is one bottom vector for each rasterization)
    std::vector< std::vector<int> > bottom;

    //right: a vector containing the number of cells (for the i-th row starting from bottom) from the
    //FIRST NON-EMTPY cell at the left to the LAST NON-EMPTY CELL at the right (there is one right vector for each rasterization)
    std::vector< std::vector<int> > deltaX;

    //left: a vector containing the number of EMPTY cells found starting from the left (at the i-th row starting from the bottom)
    //until the first NON-EMPTY cell is found (there is one left vector for each rasterization)
    std::vector< std::vector<int> > left;

    //the area, measured in cells, of the discrete representations of the polygons
    std::vector<int> discreteAreas;

public:
    RasterizedOutline2() { }
    int gridHeight(int i) { return grids.at(i).size(); }
    int gridWidth( int i) { return grids.at(i).at(0).size(); }

    std::vector<Point2f>&  getPoints()           { return points; }
    const std::vector<Point2f>&  getPointsConst() const{ return points; }
    std::vector< std::vector<int> >& getGrids(int rast_i)  { return grids[rast_i]; }

    //get top/bottom/left/right vectors of the i-th rasterization
    std::vector<int>& getDeltaY(int i) { return deltaY[i]; }
    std::vector<int>& getBottom(int i) { return bottom[i]; }
    std::vector<int>& getDeltaX(int i) { return deltaX[i]; }
    std::vector<int>& getLeft(int i) { return left[i]; }
    int& getDiscreteArea(int i) { return discreteAreas[i]; }
    void addPoint(Point2f& newpoint) { points.push_back(newpoint); }
    void setPoints(std::vector<Point2f>& newpoints) { points = newpoints; }

    //resets the state of the poly and resizes all the states vectors
    void resetState(int totalRasterizationsNum) {
        discreteAreas.clear();
        deltaY.clear();
        bottom.clear();
        deltaX.clear();
        left.clear();
        grids.clear();

        discreteAreas.resize(totalRasterizationsNum);
        deltaY.resize(totalRasterizationsNum);
        bottom.resize(totalRasterizationsNum);
        deltaX.resize(totalRasterizationsNum);
        left.resize(totalRasterizationsNum);
        grids.resize(totalRasterizationsNum);
    }

    void initFromGrid(int rast_i) {
        std::vector< std::vector<int> >& tetrisGrid = grids[rast_i];
        int gridWidth = tetrisGrid[0].size();
        int gridHeight = tetrisGrid.size();
        //compute bottom,
        //where bottom[i] = empty cells from the bottom in the column i
        for (int col = 0; col < gridWidth; col++) {
            int bottom_i = 0;
            for (int row = gridHeight - 1; row >= 0; row--) {
                if (tetrisGrid[row][col] == 0) {
                    bottom_i++;
                }
                else {
                    bottom[rast_i].push_back(bottom_i);
                    break;
                }
            }
        }
        if (bottom[rast_i].size() == 0) assert("ERROR: EMPTY BOTTOM VECTOR"==0);

        //compute top
        //IT ASSUMES THAT THERE IS AT LEAST ONE NON-0 ELEMENT (which should always be the case, even if the poly is just a point)
        //deltaY[i] = for the column i, it stores the number of cells which are between the bottom and the top side of the poly
        for (int col = 0; col < gridWidth; col++) {
            int deltay_i = gridHeight - bottom[rast_i][col];
            for (int row = 0; row < gridHeight; row++) {
                if (tetrisGrid[row][col] == 0) {
                    deltay_i--;
                }
                else {
                    break;
                }
            }
            deltaY[rast_i].push_back(deltay_i);
        }
        if (deltaY[rast_i].size() == 0) assert("ERROR: EMPTY deltaY VECTOR"==0);

        //same meaning as bottom, but for the left side
        //we want left/right sides vector to be ordered so that index 0 is at poly's bottom
        int left_i;
        for (int row = gridHeight-1; row >= 0; --row) {
            //for (int row = 0; row < gridHeight; ++row) {
            left_i = 0;
            for (int col = 0; col < gridWidth; col++) {
                if (tetrisGrid[row][col] == 0) ++left_i;
                else {
                    left[rast_i].push_back(left_i);
                    break;
                }
            }
        }
        if (left[rast_i].size() == 0) assert("ERROR: EMPTY leftSide VECTOR"==0);

        //we want left/right sides vector to be ordered so that index 0 is at poly's bottom
        int deltax_i;
        for (int row = gridHeight-1; row >= 0; --row) {
            //for (int row = 0; row < gridHeight; ++row) {
            deltax_i = gridWidth - left[rast_i][gridHeight - 1 - row];
            for (int col = gridWidth - 1; col >= 0; --col) {
                if (tetrisGrid[row][col] == 0) --deltax_i;
                else {
                    break;
                }
            }
            deltaX[rast_i].push_back(deltax_i);
        }
        if (deltaX[rast_i].size() == 0) assert("ERROR: EMPTY rightSide VECTOR"==0);

        //compute the discreteArea: IT IS THE AREA (measured in grid cells) BETWEEN THE TOP AND BOTTOM SIDES...
        int discreteArea = 0;
        for (size_t i = 0; i < deltaY[rast_i].size(); i++) {
            discreteArea += deltaY[rast_i][i];
        }
        discreteAreas[rast_i] = discreteArea;
    }


};

template <class ScalarType>
class ComparisonFunctor
{

public:
    std::vector<RasterizedOutline2> & v;
    inline ComparisonFunctor( std::vector<RasterizedOutline2> & nv ) : v(nv) { }

    inline bool operator() ( int a, int b )
    {
        float area1 = tri::OutlineUtil<ScalarType>::Outline2Area(v[a].getPoints());
        float area2 = tri::OutlineUtil<ScalarType>::Outline2Area(v[b].getPoints());

        return area1 > area2;
    }
};

template <class SCALAR_TYPE, class RASTERIZER_TYPE>
class RasterizedOutline2Packer
{
    typedef typename vcg::Box2<SCALAR_TYPE> Box2x;
    typedef typename vcg::Point2<SCALAR_TYPE> Point2x;
    typedef typename vcg::Similarity2<SCALAR_TYPE> Similarity2x;
public:
  class Parameters
  {
  public:
      //size of one cell of the grid (square cells at the moment)
      int cellSize;

      //the number of rasterizations to create for each polygon; It must be a multiple of 4.
      int rotationNum;

      //THE PACKING ALGO TO USE:
      //0 - BOTTOM PACKING: it just uses bottom horizon and computes cost using the num of empty cells between the bottom side of the poly and the bottom horizon
      //1 - BOTTOM PACKING WITH PENALTY: it uses both bottom and left horizons, and it makes so that polys are placed the closest possible to the left horizon
      //2 - CORNER PACKING: 1) tries to drop poly from right to left and computes cost relative to the left horizon
      //                    2) tries to drop poly from top to bottom and computes cost relative to the bottom horizon
      //                    3) chooses the X,Y which minimize the cost
      //                    NOTE: IN THIS ALGO THE COST HAVE TO INCLUDE THE PENALTY, OTHERWISE THE TWO STRATEGIES (dropping from right and from top)
      //                          WILL COMPETE CAUSING A LOW PACKING EFFICIENCY
      enum costFuncEnum {
          MinWastedSpace,
          LowestHorizon,
          MixedCost
      };
      costFuncEnum costFunction;
      bool doubleHorizon;

      ///default constructor
      Parameters()
      {
          costFunction = LowestHorizon;
          doubleHorizon=true;
          rotationNum = 16;
          cellSize = 8;
      }
  };


  //THE CLASS WHICH HANDLES THE PACKING AND THE UPDATED STATE OF THE PACKING ALGORITHMS
  class packingfield
  {

  private:
      //the bottomHorizon stores the length of the i-th row in the current solution
      std::vector<int> mLeftHorizon;

      //the bottomHorizon stores the height of the i-th column in the current solution
      std::vector<int> mBottomHorizon;

      //the size of the packing grid
      vcg::Point2i mSize;

      //packing parameters
      Parameters params;

  public:
      packingfield(vcg::Point2i size, const Parameters& par)
      {
          mBottomHorizon.resize(size.X(), 0);
          mLeftHorizon.resize(size.Y(), 0);
          params = par;
          mSize = Point2i(size.X(), size.Y());
      }

      std::vector<int>& bottomHorizon() { return mBottomHorizon; }
      std::vector<int>& leftHorizon() { return mLeftHorizon; }
      vcg::Point2i& size() { return mSize; }

      //returns the score relative to the left horizon of that poly in that particular position, taking into account the choosen algo
      int getCostX(RasterizedOutline2& poly, Point2i pos, int rast_i) {
          switch (params.costFunction) {
          case 0: return emptyCellBetweenPolyAndLeftHorizon(poly, pos, rast_i);
          case 1: return maxXofPoly(poly, pos, rast_i);
          case 2: return costXWithPenaltyOnY(poly, pos, rast_i);
          }
          return 0;
      }

      //returns the score relative to the bottom horizon of that poly in that particular position, taking into account the choosen algo
      int getCostY(RasterizedOutline2& poly, Point2i pos, int rast_i) {
          switch (params.costFunction) {
          case 0: return emptyCellBetweenPolyAndBottomHorizon(poly, pos, rast_i);
          case 1: return maxYofPoly(poly, pos, rast_i);
          case 2: return costYWithPenaltyOnX(poly, pos, rast_i);
          }
          return 0;
      }

      //given a poly and the column at which it is placed,
      //this returns the Y at which the wasted space is minimum
      //i.e. the Y at which the polygon touches the horizon
      int dropY(RasterizedOutline2& poly, int col, int rast_i) {
          int tmp = INT_MAX;
          int adjacentIndex = 0;
          std::vector<int>& bottom = poly.getBottom(rast_i);

          //look for for index of the column at which the poly touches the bottom horizon first
          for (size_t i = 0; i < bottom.size(); ++i) {
              int diff = bottom[i] - mBottomHorizon[col + i];
              if (diff < tmp) {
                  adjacentIndex = i;
                  tmp = diff;
              }
          }
          //return the lowest Y of the dropped poly
          return mBottomHorizon[col + adjacentIndex] - bottom[adjacentIndex];
      }

      //given a poly and the row at which it is placed,
      //this returns the X at which the wasted space is minimum
      //i.e. the X at which the polygon touches the left horizon
      int dropX(RasterizedOutline2& poly, int row, int rast_i) {
          int tmp = INT_MAX;
          int adjacentIndex = 0;
          std::vector<int>& left = poly.getLeft(rast_i);

          //look for for index of the column at which the poly touches the left horizon first
          for (size_t i = 0; i < left.size(); ++i) {
              int diff = left[i] - mLeftHorizon[row + i];
              if (diff < tmp) {
                  adjacentIndex = i;
                  tmp = diff;
              }
          }
          //and return the lowest X of the dropped poly
          return mLeftHorizon[row + adjacentIndex] - left[adjacentIndex];
      }

      int costYWithPenaltyOnX(RasterizedOutline2& poly, Point2i pos, int rast_i) {
          std::vector<int>& left = poly.getLeft(rast_i);

          //get the standard cost on X axis
          int score = emptyCellBetweenPolyAndBottomHorizon(poly, pos, rast_i);

          //apply a penalty if the poly is the poly is far from the left horizon
          //thus preferring poly which are closer to the left horizon
          for (size_t i = 0; i < left.size(); ++i) {
              //ASSUMPTION: if the poly is (partially/fully) under the left horizon,
              //then we will count this as a good thing (subtracting a quantity from the cost) but since we don't have
              //a grid holding the current state of the packing field, we don't know the position of the polygons at our left side,
              //so we ASSUME that there isn't any polygon between the poly we're considering and the Y axis of the packing field,
              //and count the number of cells between us and the RIGHT end the packing field
              //(NOTE: ^^^^^^^ this implies that the closer we are to the left horizon, the lower the cost will get)
              if (pos.X() + left[i] < mLeftHorizon[pos.Y() + i])
                  //number of cells between us and the RIGHT end the packing field
                  score -= mSize.X() - pos.X() - left[i];
              else         //the number of cells between the bottom side of the poly at the (posY+i)-th row and the value of the horizon in that row
                  score += pos.X() + left[i] - mLeftHorizon[pos.Y() + i];
          }
          return score;
      }

      //returns the number of empty cells between poly's bottom side and the bottom horizon
      int emptyCellBetweenPolyAndBottomHorizon(RasterizedOutline2& poly, Point2i pos, int rast_i)
      {
          std::vector<int>& bottom = poly.getBottom(rast_i);
          int score = 0;
          int min = INT_MAX;

          //count the number of empty cells between poly's bottom side and the bottom horizon
          for (size_t i = 0; i < bottom.size(); ++i) {
              int diff = bottom[i] - mBottomHorizon[pos.X() + i];
              score += diff;
              if (diff < min) min = diff;
          }
          score += (-min*bottom.size());

          return score;
      }

      int costXWithPenaltyOnY(RasterizedOutline2& poly, Point2i pos, int rast_i) {
          std::vector<int>& bottom = poly.getBottom(rast_i);

          //get the standard cost on X axis
          int score = emptyCellBetweenPolyAndLeftHorizon(poly, pos, rast_i);

          //apply a penalty if the poly is the poly is far from the bottom horizon
          //thus preferring poly which are closer to the bottom horizon
          for (size_t i = 0; i < bottom.size(); ++i) {
              //ASSUMPTION: if the poly is (partially/fully) under the bottom horizon,
              //then we will count this as a good thing (subtracting a quantity from the cost) but since we don't have
              //a grid holding the current state of the packing field, we don't know the position of the polygons beneath us,
              //so we ASSUME that there isn't any polygon between the poly we're considering and the X axis of the packing field,
              //and count the number of cells between us and the TOP end the packing field
              //(NOTE: ^^^^^^^ this implies that the closer we are to the bottom horizon, the lower the cost will get)
              if (pos.Y() + bottom[i] < mBottomHorizon[pos.X() + i])
                  //number of cells between us and the TOP side the packing field
                  score -= (mSize.Y() - pos.Y() - bottom[i]);
              else         //the number of cells between the left side of the poly at the (posX+i)-th column and the value of the horizon in that column
                  score += pos.X() + bottom[i] - mBottomHorizon[pos.X() + i];
          }
          return score;
      }

      int maxYofPoly(RasterizedOutline2& poly, Point2i pos, int rast_i)
      {
          return pos.Y() + poly.gridHeight(rast_i);
      }

      int maxXofPoly(RasterizedOutline2& poly, Point2i pos, int rast_i)
      {
          return pos.X() + poly.gridWidth(rast_i);
      }

      //returns the number of empty cells between poly's left side and the left horizon
      int emptyCellBetweenPolyAndLeftHorizon(RasterizedOutline2& poly, Point2i pos, int rast_i)
      {
          std::vector<int>& left = poly.getLeft(rast_i);

          int score = 0;
          int min = INT_MAX;

          //count the number of empty cells between poly's left side and the left horizon
          for (size_t i = 0; i < left.size(); ++i) {
              int diff = left[i] - mLeftHorizon[pos.Y() + i];
              score += diff;
              if (diff < min) min = diff;
          }
          score += (-min*left.size());

          return score;
      }

      //updates the horizons according to the chosen position
      void placePoly(RasterizedOutline2& poly, Point2i pos, int rast_i) {

          std::vector<int>& bottom = poly.getBottom(rast_i);
          std::vector<int>& deltaY = poly.getDeltaY(rast_i);
          std::vector<int>& left = poly.getLeft(rast_i);
          std::vector<int>& deltaX = poly.getDeltaX(rast_i);

          //update bottom horizon
          for (int i = 0; i < poly.gridWidth(rast_i); i++) {
              //tmpHor = the highest Y reached by the poly, RELATIVE TO the packing field coords system
              int tmpHor = pos.Y() + bottom[i] + deltaY[i];
              //only update the horizon if it's higher than this value
              if (tmpHor > mBottomHorizon[pos.X() + i]) mBottomHorizon[pos.X() + i] = tmpHor;
          }

          if (params.costFunction != Parameters::MixedCost
                  && !params.doubleHorizon) return;

          //update leftHorizon
          for (int i = 0; i < poly.gridHeight(rast_i); i++) {
              //tmpHor = the highest X reached by the poly, RELATIVE TO the packing field coords system
              int tmpHor = pos.X() + left[i] + deltaX[i];
              //only update the horizon if it's higher than this value
              if (tmpHor > mLeftHorizon[pos.Y() + i]) mLeftHorizon[pos.Y() + i] = tmpHor;
          }
      }
  };


    static bool Pack(std::vector< std::vector< Point2x>  > &polyPointsVec,
                     Point2i containerSize,
                     std::vector<Similarity2x> &trVec,
                     const Parameters &packingPar)
    {
      std::vector<Point2i> containerSizes(1,containerSize);
      std::vector<int> polyToContainer;
      return Pack(polyPointsVec,containerSizes,trVec,polyToContainer,packingPar);
    }

    static bool Pack(std::vector< std::vector< Point2x>  > &polyPointsVec,
                     const std::vector<Point2i> &containerSizes,
                     std::vector<Similarity2x> &trVec,
                     std::vector<int> &polyToContainer,
                     const Parameters &packingPar)
    {

      int containerNum=containerSizes.size();

        float gridArea = 0;
        //if containerSize isn't multiple of cell size, crop the grid (leaving containerSize as it is)
        for (int i = 0; i < containerNum; i++) {
            Point2i gridSize(containerSizes[i].X() / packingPar.cellSize,
                             containerSizes[i].Y() / packingPar.cellSize);

            gridArea += (gridSize.X()*packingPar.cellSize * gridSize.Y()*packingPar.cellSize);
        }

        float totalArea = 0;
        for (size_t j = 0; j < polyPointsVec.size(); j++) {
            float curArea = tri::OutlineUtil<SCALAR_TYPE>::Outline2Area(polyPointsVec[j]);
            if(curArea<0) tri::OutlineUtil<SCALAR_TYPE>::ReverseOutline2(polyPointsVec[j]);
            totalArea += fabs(curArea);
        }

        //we first set it to the "optimal" scale
        float optimalScale = sqrt(gridArea/ totalArea);
        float currScale = optimalScale;
        float latestFailScale = 0;

        bool ret = false;
        //we look for the first scale factor which makes the packing algo succeed
        //we will use this value in the bisection method afterwards
        ret = PolyPacking(polyPointsVec, containerSizes, trVec, polyToContainer, packingPar, currScale);
        while (!ret) {
            latestFailScale = currScale;
            currScale *= 0.60;
            ret = PolyPacking(polyPointsVec, containerSizes, trVec, polyToContainer, packingPar, currScale);
        }
        //if it managed to pack with the optimal scale (VERY unlikely), just leave
        if (currScale == optimalScale) return true;

        //BISECTION METHOD
        float latestSuccessScale = currScale;
        float tmpScale = (latestSuccessScale + latestFailScale) / 2;
        while ( (latestFailScale / latestSuccessScale) - 1 > 0.001
                || ((latestFailScale / latestSuccessScale) - 1 < 0.001 && !ret) ) {

            tmpScale = (latestSuccessScale + latestFailScale) / 2;
            ret = PolyPacking(polyPointsVec, containerSizes, trVec, polyToContainer, packingPar, tmpScale);
            if (ret) latestSuccessScale = tmpScale;
            else latestFailScale = tmpScale;
        }

        float finalArea = 0;
        //compute occupied area
        for (size_t j = 0; j < polyPointsVec.size(); j++) {
            std::vector<Point2f> oldPoints = polyPointsVec[j];
            for (size_t k = 0; k < oldPoints.size(); k++) {
                oldPoints[k].Scale(latestSuccessScale, latestSuccessScale);
            }
            finalArea +=  tri::OutlineUtil<SCALAR_TYPE>::Outline2Area(oldPoints);
        }
//        printf("PACKING EFFICIENCY: %f with scale %f\n", finalArea/gridArea, latestSuccessScale);
    }

    //tries to pack polygons using the given gridSize and scaleFactor
    //stores the result, i.e. the vector of similarities, in trVec
    static bool PolyPacking(std::vector< std::vector< Point2x>  > &outline2Vec,
                            const std::vector<Point2i> &containerSizes,
                            std::vector<Similarity2x> &trVec,
                            std::vector<int> &polyToContainer,
                            const Parameters &packingPar,
                            float scaleFactor)
    {
        int containerNum = containerSizes.size();

        polyToContainer.clear();
        polyToContainer.resize(outline2Vec.size());
        trVec.resize(outline2Vec.size());

        //create packing fields, one for each container
        std::vector<Point2i> gridSizes;
        std::vector<packingfield> packingFields;
        for (int i=0; i < containerNum; i++) {
            //if containerSize isn't multiple of cell size, crop the grid (leaving containerSize as it is)
            gridSizes.push_back(Point2i(containerSizes[i].X() / packingPar.cellSize,
                                        containerSizes[i].Y() / packingPar.cellSize));

            packingfield one(gridSizes[i], packingPar);
            packingFields.push_back(one);
        }

        //create the vector of polys, starting for the poly points we received as parameter
        std::vector<RasterizedOutline2> polyVec(outline2Vec.size());
        for(size_t i=0;i<polyVec.size();i++) {
            polyVec[i].setPoints(outline2Vec[i]);
        }

        // Build a permutation that holds the indexes of the polys ordered by their area
        std::vector<int> perm(polyVec.size());
        for(size_t i=0;i<polyVec.size();i++) perm[i] = i;
        sort(perm.begin(),perm.end(),ComparisonFunctor<float>(polyVec));

//        printf("BEGIN OF PACKING\n");

        // **** First Step: Rasterize all the polygons ****
        for (size_t i = 0; i < polyVec.size(); i++) {
            polyVec[i].resetState(packingPar.rotationNum);
            for (int rast_i = 0; rast_i < packingPar.rotationNum/4; rast_i++) {
                //create the rasterization (i.e. fills bottom/top/grids/internalWastedCells arrays)
                RASTERIZER_TYPE::rasterize(polyVec[i],scaleFactor, rast_i,packingPar.rotationNum,packingPar.cellSize);
            }
        }

        // **** Second Step: iterate on the polys, and try to find the best position ****
        for (size_t currPoly = 0; currPoly < polyVec.size(); currPoly++) {

            int i = perm[currPoly];
            int bestRastIndex = -1;
            int bestCost = INT_MAX;
            int bestPolyX = -1;
            int bestPolyY = -1;
            int bestContainer = -1; //the container where the poly fits best

            //try all the rasterizations and choose the best fitting one
            for (int rast_i = 0; rast_i < packingPar.rotationNum; rast_i++) {

                //try to fit the poly in all containers, in all valid positions
                for (int grid_i = 0; grid_i < containerNum; grid_i++) {
                    int maxCol = gridSizes[grid_i].X() - polyVec[i].gridWidth(rast_i);
                    int maxRow = gridSizes[grid_i].Y() - polyVec[i].gridHeight(rast_i);

                    //look for the best position, dropping from top
                    for (int col = 0; col < maxCol; col++) {
                        //get the Y at which the poly touches the horizontal horizon
                        int currPolyY = packingFields[grid_i].dropY(polyVec[i],col, rast_i);

                        if (currPolyY + polyVec[i].gridHeight(rast_i) > gridSizes[grid_i].Y()) {
                            //skip this column, as the poly would go outside the grid if placed here
                            continue;
                        }

                        int currCost = packingFields[grid_i].getCostX(polyVec[i], Point2i(col, currPolyY), rast_i) +
                                packingFields[grid_i].getCostY(polyVec[i], Point2i(col, currPolyY), rast_i);

                        //if this rasterization is better than what we found so far
                        if (currCost < bestCost) {
                            bestContainer = grid_i;
                            bestCost = currCost;
                            bestRastIndex = rast_i;
                            bestPolyX = col;
                            bestPolyY = currPolyY;
                        }
                    }

                    if (!packingPar.doubleHorizon) continue;

                    for (int row = 0; row < maxRow; row++) {
                        //get the Y at which the poly touches the horizontal horizon
                        int currPolyX = packingFields[grid_i].dropX(polyVec[i],row, rast_i);

                        if (currPolyX + polyVec[i].gridWidth(rast_i) > gridSizes[grid_i].X()) {
                            //skip this column, as the poly would go outside the grid if placed here
                            continue;
                        }

                        int currCost = packingFields[grid_i].getCostY(polyVec[i], Point2i(currPolyX, row), rast_i) +
                                packingFields[grid_i].getCostX(polyVec[i], Point2i(currPolyX, row), rast_i);

                        //if this rasterization fits better than those we tried so far
                        if (currCost < bestCost) {
                            bestContainer = grid_i;
                            bestCost = currCost;
                            bestRastIndex = rast_i;
                            bestPolyX = currPolyX;
                            bestPolyY = row;
                        }
                    }
                }
            }

            //if we couldn't find a valid position for the poly return false, as we couldn't pack with the current scaleFactor
            if (bestRastIndex == -1) {
//                printf("Items didn't fit using %f as scaleFactor\n", scaleFactor);
                return false;
            }

            //we found the best position for a given poly,
            //let's place it, so that the horizons are updated accordingly
            packingFields[bestContainer].placePoly(polyVec[i], Point2i(bestPolyX, bestPolyY), bestRastIndex);

            //create the rotated bb which we will use to set the similarity translation prop
            float angleRad = float(bestRastIndex)*(M_PI*2.0)/float(packingPar.rotationNum);
            Box2f bb;
            std::vector<Point2f> points = polyVec[i].getPoints();
            for(size_t i=0;i<points.size();++i) {
                Point2f pp=points[i];
                pp.Rotate(angleRad);
                bb.Add(pp);
            }

            //associate the poly to the container where it fitted best
            polyToContainer[i] = bestContainer;

            //now we have bestPolyX/bestRastIndex
            //we have to update the similarities vector accordingly!
            float polyXInImgCoords = bestPolyX*packingPar.cellSize;
            float scaledBBWidth = bb.DimX()*scaleFactor;
            float polyWidthInImgCoords = polyVec[i].gridWidth(bestRastIndex)*packingPar.cellSize;
            float offsetX = (polyWidthInImgCoords - ceil(scaledBBWidth))/2.0;
            float scaledBBMinX = bb.min.X()*scaleFactor;

            //note: bestPolyY is 0 if the poly is at the bottom of the grid
            float imgHeight = containerSizes[bestContainer].Y();
            float polyYInImgCoords = bestPolyY*packingPar.cellSize;
            float polyHeightInImgCoords = polyVec[i].gridHeight(bestRastIndex)*packingPar.cellSize;
            float topPolyYInImgCoords = polyYInImgCoords + polyHeightInImgCoords;
            float scaledBBHeight = bb.DimY()*scaleFactor;
            float offsetY = (polyHeightInImgCoords - ceil(scaledBBHeight))/2.0;
            float scaledBBMinY = bb.min.Y()*scaleFactor;
            trVec[i].tra = Point2f(polyXInImgCoords - scaledBBMinX + offsetX,
                                   imgHeight - topPolyYInImgCoords - scaledBBMinY + offsetY);
            trVec[i].rotRad = angleRad;
            trVec[i].sca = scaleFactor;
        }

        //sort polyToContainer and trVec so that we have them ordered for dumping
//        printf("SUCCESSFULLY PACKED with scaleFactor %f\n", scaleFactor);
        return true;
    }

    static void printVector(std::vector<int>& vec) {
        for (size_t i = 0; i < vec.size(); i++) {
            printf("%d", vec[i]);
        }
        printf("\n");
    }
}; // end class



} // end namespace vcg

#endif // NEW_POLYPACKER_H
