/*************************************************************************
 *    CompuCell - A software framework for multimodel simulations of     *
 * biocomplexity problems Copyright (C) 2003 University of Notre Dame,   *
 *                             Indiana                                   *
 *                                                                       *
 * This program is free software; IF YOU AGREE TO CITE USE OF CompuCell  *
 *  IN ALL RELATED RESEARCH PUBLICATIONS according to the terms of the   *
 *  CompuCell GNU General Public License RIDER 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 for more details.                  *
 *                                                                       *
 *  You should have received a copy of the GNU General Public License    *
 *     along with this program; if not, write to the Free Software       *
 *      Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.        *
 *************************************************************************/


#include "Boundary.h"
#include "BoundaryFactory.h"
#include "BoundaryStrategy.h"
#include "AlgorithmFactory.h"
#include "Algorithm.h"
#include <src/CompuCell3D/Field3D/Dim3D.h>
#include <src/CompuCell3D/Field3D/Point3D.h>
#include <src/CompuCell3D/Field3D/Neighbor.h>
#include <src/CompuCell3D/Field3D/NeighborFinder.h>

using namespace std;
using namespace CompuCell3D;

// Singleton
BoundaryStrategy* BoundaryStrategy::singleton;


// Default constructor
BoundaryStrategy::BoundaryStrategy(){
   strategy_x = BoundaryFactory::createBoundary(BoundaryFactory::no_flux);
   strategy_y = BoundaryFactory::createBoundary(BoundaryFactory::no_flux);
   strategy_z = BoundaryFactory::createBoundary(BoundaryFactory::no_flux);
   algorithm = AlgorithmFactory::createAlgorithm(AlgorithmFactory::Default,0,0,"None");
   regular = true; 

}


// Constructor
BoundaryStrategy::BoundaryStrategy(string boundary_x, string boundary_y,
                   string boundary_z, string alg, int index, int size, string
		   inputfile){

   strategy_x = BoundaryFactory::createBoundary(boundary_x);
   strategy_y = BoundaryFactory::createBoundary(boundary_y);
   strategy_z = BoundaryFactory::createBoundary(boundary_z);
   algorithm = AlgorithmFactory::createAlgorithm(alg, index, size, inputfile);
   regular = true;
}

// Destructor
BoundaryStrategy::~BoundaryStrategy() {

    delete strategy_x;
    delete strategy_y;
    delete strategy_z;

}

/*
 * Check to see if the given point lies inside the field dimensions
 *
 * @param pt Point3D
 *
 * @return bool 
 */
bool BoundaryStrategy::isValid(const Point3D &pt) const {
    
    // check to see if the point lies in the dimensions before applying the
    // shape algorithm

 if (0 <= pt.x && pt.x < dim.x &&
                   0 <= pt.y && pt.y < dim.y &&
                   0 <= pt.z && pt.z < dim.z ) {
           
     return algorithm->inGrid(pt);
 } 

     return false;
}

/*
 * Check to see if the given coordinate  lies inside the 
 * max value for that axis
 *
 * @param coordinate for a given axis
 * @param max_value for a that axis 
 *
 * @return bool
 */ 
bool BoundaryStrategy::isValid(const int coordinate, const int max_value) {

    return (0 <= coordinate && coordinate < max_value); 
}

/*
 * Set dimensions
 *
 * @param dim Dim3D
 */
void BoundaryStrategy::setDim(const Dim3D theDim) {

    dim = theDim;
    algorithm->setDim(theDim);

}

/*
 * Set current step
 *
 *  @param int currentStep
 */ 
 void BoundaryStrategy::setCurrentStep(const int theCurrentStep) {

   currentStep = theCurrentStep;
   algorithm->setCurrentStep(theCurrentStep);
     
 }

/*
 * Set Irregular
 *
 */
 void BoundaryStrategy::setIrregular() {
  
     regular = false;
     
 }


/*
 * Retrieves a neighbor for a given point after applying
 * boundary conditions as configured
 *
 * @param pt Point3D
 * @param token int
 * @param distance int
 * @param checkBounds bool
 *
 * @return Point3D Valid Neighbor
 */
Point3D BoundaryStrategy::getNeighbor(const Point3D& pt, unsigned int& token,
                                   double& distance, bool checkBounds) {

     Neighbor n;
     Point3D p;
     int x;
     int y;
     int z;
     bool x_bool;
     bool y_bool;
     bool z_bool;

     while(true) {

      // Get a neighbor from the NeighborFinder 
      n = NeighborFinder::getInstance()->getNeighbor(token);
      x = (pt + n.pt).x;
      y = (pt + n.pt).y;
      z = (pt + n.pt).z;

      token++;

      if(!checkBounds || isValid(pt + n.pt)) {
          
         // Valid Neighbor
          break;

      } else {
          
          if (regular) {
              
          // For each coordinate, if it is not valid, apply condition
          x_bool = (isValid(x,dim.x) ? true : strategy_x->applyCondition(x, dim.x));
          y_bool = (isValid(y,dim.y) ? true : strategy_y->applyCondition(y, dim.y));
          z_bool = (isValid(z,dim.z) ? true : strategy_z->applyCondition(z, dim.z));
          
          // If all the coordinates of the neighbor are valid then return the
          // neighbor
            if(x_bool && y_bool && z_bool) {
              
              break; 

             }
            
          } 
          
        }
      

      }
   

     distance = n.distance;
     p.x = x;
     p.y = y;
     p.z = z;
     
     return p;
    
}

               
int BoundaryStrategy::getNumPixels(int x, int y, int z) const  {

    return algorithm->getNumPixels(x, y, z);

}

