/* Copyright (c) 2001 Dave Tweed <tweed@cs.bris.ac.uk>  *
 * GNU General Public Licence                           */

#include "tile.h"
#include <algorithm>
#include <stack>
//#include <stdio.h>

/* 
 * changed<05.02.2003> by Rudolf Polzer: including namespace std (C++)
 */

using namespace std;

/***************************************************************************
 *this file implements a quick and nasty routine for attempting to find
 *a compact placement of rectangles within a larger rectangular area,
 *essentially by trying a pruned-search using the following algorithm:
 * o sort the rectangles so they are placed in order of decreasing size
 * o initialise a `frontier' of possible places for the `top-left' corner
 *   of the next rectangle with (0,0)
 * o for the current rectangle R_n to be placed:
 * +  try to place R_n at an untried point on the frontier
 * + IF R_n doesn't overlap with any other rectangle currently placed
 *   THEN add its bottom left corner and top right corner to the frontier
 *        and try to place R_n+1 using the same technique
 *   ELSE IF the frontier is exhausted without a successful configuration
 *   THEN try a different position for R_n-1
 *Since this is exponential in the number of rectangles to place, a limit is
 *set on the amount of `work cycles' that will be spent trying to place the
 *rectangles before the best solution found so far is returned
 *(Clearly there are problems where a tiling is possible but it won't be
 *found by this algorithm but it works well for most real situations)
 **************************************************************************/

const int MAX_WORK_CYCLES_ACCEPTABLE=10000;

/**************************************************************************
 *frontier is a list of easily computed positions where placing the
 *next rectangle could be good
 *************************************************************************/
struct FrontierRec {
  int x,y;
  int currentlyUsed;
  void set(int sX,int sY,int sCurrentlyUsed){
    x=sX;
    y=sY;
    currentlyUsed=sCurrentlyUsed;
  }
};

/**************************************************************************
 *make the code slightly simpler; of course most C++ compilers will
 *produce abysmyal code for this case even under high optimisation. Ho hum.
 *************************************************************************/
//typedef pair<int,int> PairVals;
struct PairVals {
  int first;
  int second;
};

inline
int
rectFitsOnScreen(int maxX,int maxY,const RectRec &r)
{
  return r.x+r.sizeX < maxX  &&  r.y+r.sizeY < maxY;
}

inline
bool
rectanglesOverlap(const RectRec &r1,const RectRec &r2)
{
  //compute the distance between centres along each axis and see if they are
  //both less than the 1/2 (sum of sizes rectangles dim) for each axis
  //I _think_ this is the computationally best test for overlapping-ness
  return 2*abs(r1.x+r1.sizeX/2-(r2.x+r2.sizeX/2)) < (r1.sizeX+r2.sizeX)
    && 2*abs(r1.y+r1.sizeY/2-(r2.y+r2.sizeY/2)) < (r1.sizeY+r2.sizeY);
}

/**************************************************************************
 *return true if we've either given a correct solution or exceeded our
 *work cycles limit. this was originally written recursively but changed to
 *be recursive, even though this obscures what's going on slightly
 *************************************************************************/
static
void
addRectToTiling(int maxX,int maxY,int noItemsOnFrontP,FrontierRec* front,
                int noRectangles,RectRec *rects,int curRect,
                RectRec *bestPlacement,PairVals *maxDimens,int &lowestArea,
                int maxWorkCycles)
{
  int workCyclesLeft=maxWorkCycles;
  int noItemsOnFront=noItemsOnFrontP;
  int curPosOnFront=0;
  stack<pair<int,int> > theStack;
  do{
    if(!front[curPosOnFront].currentlyUsed){
      //only count this as working cycle if we do reasonable amount of work
      --workCyclesLeft;
      //place rectangle at current frontier position & disable it
      rects[curRect].setPosn(front[curPosOnFront].x,front[curPosOnFront].y);
      front[curPosOnFront].currentlyUsed=1;
      /*check latest placement is consistent (using a simple O(n) alg)*/
      int consistentSoFar=rectFitsOnScreen(maxX,maxY,rects[curRect]);
      int i;
      for(i=curRect-1;/*consistentSoFar &&*/ i>=0;--i){
        consistentSoFar &= !rectanglesOverlap(rects[i],rects[curRect]);
      }
      if(consistentSoFar){
        maxDimens[curRect+1].first=max(maxDimens[curRect].first,
                                       rects[curRect].x+rects[curRect].sizeX);
        maxDimens[curRect+1].second=max(maxDimens[curRect].second,
                                        rects[curRect].y+rects[curRect].sizeY);
        if(curRect==noRectangles-1){
          //if we've got this far, then we must have a valid configuration,
          //so record it if it's better than the current best
          int bboxArea=maxDimens[curRect+1].first*maxDimens[curRect+1].second;
          if(bboxArea < lowestArea){
            lowestArea=bboxArea;
            //is there any advantage to using a bitwise copy?
            copy(rects,rects+noRectangles,bestPlacement);
          }
        }else{
          theStack.push(make_pair(curPosOnFront,noItemsOnFront));
          //expand frontier to accomodate the points from this rectangle
          front[noItemsOnFront++]
            .set(rects[curRect].x+rects[curRect].sizeX+1,rects[curRect].y,0);
          front[noItemsOnFront++]
            .set(rects[curRect].x,rects[curRect].y+rects[curRect].sizeY+1,0);
          ++curRect;
          curPosOnFront=0;
        }
      }
    }//ELSE we try the next position for the current rectangle
    ++curPosOnFront;
    while(curPosOnFront>=noItemsOnFront){
      if(theStack.empty()){
        break;
      }
#if 0
      assert(!theStack.empty());
#endif
      curPosOnFront=theStack.top().first+1;
      noItemsOnFront=theStack.top().second;
      //NOW BEFORE ANYTHING ELSE, remember to reactivate posn on frontier
      front[curPosOnFront].currentlyUsed=0;
      --curRect;
      theStack.pop();
    }
  }while(workCyclesLeft>0 && !theStack.empty());
  //fprintf(stderr,
  //        workCyclesLeft>0 ? "finished ok\n" : "ran out of cycles\n");
}

/*return false if we failed to tile the region*/
bool
tileArea(int maxX,int maxY,int noRectangles,RectRec *rects)
{
  //ok, first we do a simple check whether a tiling is possible
  int areaOfConstituents=0;
  int i;
  for(i=0;i<noRectangles;++i){
    areaOfConstituents+=rects[i].sizeX*rects[i].sizeY;
  }
  if(noRectangles<=1 || areaOfConstituents>maxX*maxY){
    return false;
  }
  //order the rectangles by decreasing area (using STL sort for convenience)
  sort(rects,rects+noRectangles);
  RectRec *bestPlacement=new RectRec[5*noRectangles];
  int lowestBBoxArea=maxX*maxY+1;
  FrontierRec *front=new FrontierRec[5*2*noRectangles+2];
  front[0].set(0,0,0);
  PairVals *maxDimens=new PairVals[5*noRectangles+1];
  maxDimens[0].first = 0;//=make_pair(0,0);
  maxDimens[0].second = 0;
  addRectToTiling(maxX,maxY,1,front,noRectangles,rects,0,bestPlacement,
                  maxDimens,lowestBBoxArea,MAX_WORK_CYCLES_ACCEPTABLE);
  delete[] maxDimens;
  delete[] front;
  if(lowestBBoxArea<maxX*maxY+1){
    copy(bestPlacement,bestPlacement+noRectangles,rects);
  }
  delete[] bestPlacement;
  return lowestBBoxArea<maxX*maxY+1;
}


syntax highlighted by Code2HTML, v. 0.9.1