/*
 * Project: MoleCuilder
 * Description: creates and alters molecular systems
 * Copyright (C)  2010-2012 University of Bonn. All rights reserved.
 * Copyright (C)  2013 Frederik Heber. All rights reserved.
 * 
 *
 *   This file is part of MoleCuilder.
 *
 *    MoleCuilder 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.
 *
 *    MoleCuilder 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 MoleCuilder.  If not, see .
 */
/*
 * Box.cpp
 *
 *  Created on: Jun 30, 2010
 *      Author: crueger
 */
// include config.h
#ifdef HAVE_CONFIG_H
#include 
#endif
#include "CodePatterns/MemDebug.hpp"
#include "Box.hpp"
#include 
#include 
#include 
#include 
#include "CodePatterns/Assert.hpp"
#include "CodePatterns/Log.hpp"
#include "CodePatterns/Observer/Channels.hpp"
#include "CodePatterns/Observer/Notification.hpp"
#include "CodePatterns/Verbose.hpp"
#include "Helpers/defs.hpp"
#include "LinearAlgebra/RealSpaceMatrix.hpp"
#include "LinearAlgebra/Vector.hpp"
#include "LinearAlgebra/Plane.hpp"
#include "Shapes/BaseShapes.hpp"
#include "Shapes/ShapeOps.hpp"
Box::Box() :
    Observable("Box"),
    M(new RealSpaceMatrix()),
    Minv(new RealSpaceMatrix())
{
  internal_list.reserve(pow(3,3));
  coords.reserve(NDIM);
  index.reserve(NDIM);
  // observable stuff
  Channels *OurChannel = new Channels;
  NotificationChannels.insert( std::make_pair(static_cast(this), OurChannel) );
  // add instance for each notification type
  for (size_t type = 0; type < NotificationType_MAX; ++type)
    OurChannel->addChannel(type);
  M->setIdentity();
  Minv->setIdentity();
}
Box::Box(const Box& src) :
    Observable("Box"),
    conditions(src.conditions),
    M(new RealSpaceMatrix(*src.M)),
    Minv(new RealSpaceMatrix(*src.Minv))
{
  internal_list.reserve(pow(3,3));
  coords.reserve(NDIM);
  index.reserve(NDIM);
  // observable stuff
  Channels *OurChannel = new Channels;
  NotificationChannels.insert( std::make_pair(static_cast(this), OurChannel) );
  // add instance for each notification type
  for (size_t type = 0; type < NotificationType_MAX; ++type)
    OurChannel->addChannel(type);
}
Box::Box(RealSpaceMatrix _M) :
    Observable("Box"),
    M(new RealSpaceMatrix(_M)),
    Minv(new RealSpaceMatrix())
{
  internal_list.reserve(pow(3,3));
  coords.reserve(NDIM);
  index.reserve(NDIM);
  // observable stuff
  Channels *OurChannel = new Channels;
  NotificationChannels.insert( std::make_pair(static_cast(this), OurChannel) );
  // add instance for each notification type
  for (size_t type = 0; type < NotificationType_MAX; ++type)
    OurChannel->addChannel(type);
  ASSERT(M->determinant()!=0,"Matrix in Box construction was not invertible");
  *Minv = M->invert();
}
Box::~Box()
{
  delete M;
  delete Minv;
}
const RealSpaceMatrix &Box::getM() const{
  return *M;
}
const RealSpaceMatrix &Box::getMinv() const{
  return *Minv;
}
void Box::setM(RealSpaceMatrix _M){
  ASSERT(_M.determinant()!=0,"Matrix in Box construction was not invertible");
  OBSERVE;
  if (_M != *M)
    NOTIFY(MatrixChanged);
  *M    =_M;
  *Minv = M->invert();
}
Vector Box::translateIn(const Vector &point) const{
  return (*M) * point;
}
Vector Box::translateOut(const Vector &point) const{
  return (*Minv) * point;
}
Vector Box::enforceBoundaryConditions(const Vector &point) const{
  Vector helper = translateOut(point);
  for(int i=NDIM;i--;){
    switch(conditions[i]){
    case BoundaryConditions::Wrap:
      helper.at(i)=fmod(helper.at(i),1);
      helper.at(i)+=(helper.at(i)>=0)?0:1;
      break;
    case BoundaryConditions::Bounce:
      {
        // there probably is a better way to handle this...
        // all the fabs and fmod modf probably makes it very slow
        double intpart,fracpart;
        fracpart = modf(fabs(helper.at(i)),&intpart);
        helper.at(i) = fabs(fracpart-fmod(intpart,2));
      }
      break;
    case BoundaryConditions::Ignore:
      break;
    default:
      ASSERT(0,"No default case for this");
      break;
    }
  }
  return translateIn(helper);
}
bool Box::isInside(const Vector &point) const
{
  bool result = true;
  Vector tester = translateOut(point);
  for(int i=0;i= -MYEPSILON) &&
               ((tester[i] - 1.) < MYEPSILON)));
  return result;
}
bool Box::isValid(const Vector &point) const
{
  bool result = true;
  Vector tester = translateOut(point);
  for(int i=0;i= -MYEPSILON) &&
               ((tester[i] - 1.) < MYEPSILON)));
  return result;
}
VECTORSET(std::vector) Box::explode(const Vector &point,int n) const{
  ASSERT(isInside(point),"Exploded point not inside Box");
  internal_explode(point, n);
  VECTORSET(std::vector) res(internal_list);
  return res;
}
void Box::internal_explode(const Vector &point,int n) const{
//  internal_list.clear();
  size_t list_index = 0;
  Vector translater = translateOut(point);
  Vector mask; // contains the ignored coordinates
  // count the number of coordinates we need to do
  int dims = 0; // number of dimensions that are not ignored
  coords.clear();
  index.clear();
  for(int i=0;i x
            // 1 -> 2-x
            // 2 -> 2+x
            // 3 -> 4-x
            // 4 -> 4+x
            // the first number is the next bigger even number (n+n%2)
            // the next number is the value with alternating sign (x-2*(n%2)*x)
            // the negative numbers produce the same sequence reversed and shifted
            int n = abs(index[i]) + ((index[i]<0)?-1:0);
            int sign = (index[i]<0)?-1:+1;
            int even = n%2;
            helper[coords[i]]=n+even+translater[coords[i]]-2*even*translater[coords[i]];
            helper[coords[i]]*=sign;
          }
          break;
        case BoundaryConditions::Ignore:
          ASSERT(0,"Ignored coordinate handled in generation loop");
          break;
        default:
          ASSERT(0,"No default case for this switch-case");
          break;
      }
    }
    // add back all ignored coordinates (not handled in above loop)
    helper+=mask;
    ASSERT(list_index < internal_list.size(),
        "Box::internal_explode() - we have estimated the number of vectors wrong: "
        +toString(list_index) +" >= "+toString(internal_list.size())+".");
    internal_list[list_index++] = translateIn(helper);
    // set the new indexes
    int pos=0;
    ++index[pos];
    while(index[pos]>n){
      index[pos++]=-n;
      if(pos>=dims) { // it's trying to increase one beyond array... all vectors generated
        done = true;
        break;
      }
      ++index[pos];
    }
  }
}
VECTORSET(std::vector) Box::explode(const Vector &point) const{
  ASSERT(isInside(point),"Exploded point not inside Box");
  return explode(point,1);
}
const Vector Box::periodicDistanceVector(const Vector &point1,const Vector &point2) const{
  Vector helper1(enforceBoundaryConditions(point1));
  Vector helper2(enforceBoundaryConditions(point2));
  internal_explode(helper1,1);
  const Vector res = internal_list.minDistance(helper2);
  return res;
}
double Box::periodicDistanceSquared(const Vector &point1,const Vector &point2) const{
  const Vector res = periodicDistanceVector(point1, point2);
  return res.NormSquared();
}
double Box::periodicDistance(const Vector &point1,const Vector &point2) const{
  double res = sqrt(periodicDistanceSquared(point1,point2));
  return res;
}
double Box::DistanceToBoundary(const Vector &point) const
{
  std::map DistanceSet;
  std::vector > Boundaries = getBoundingPlanes();
  for (int i=0;ifirst;
}
Shape Box::getShape() const{
  return transform(Cuboid(Vector(0,0,0),Vector(1,1,1)),(*M));
}
const std::string Box::getConditionNames() const
{
  std::stringstream outputstream;
  outputstream << conditions;
  return outputstream.str();
}
const BoundaryConditions::Conditions_t & Box::getConditions() const
{
  return conditions.get();
}
const BoundaryConditions::BoundaryCondition_t Box::getCondition(size_t i) const
{
  return conditions.get(i);
}
void Box::setCondition(size_t i, const BoundaryConditions::BoundaryCondition_t _condition)
{
  OBSERVE;
  if (conditions.get(i) != _condition)
    NOTIFY(BoundaryConditionsChanged);
  conditions.set(i, _condition);
}
void Box::setConditions(const BoundaryConditions::Conditions_t & _conditions)
{
  OBSERVE;
  if (conditions.get() != _conditions)
  NOTIFY(BoundaryConditionsChanged);
  conditions.set(_conditions);
}
void Box::setConditions(const std::string & _conditions)
{
  OBSERVE;
  NOTIFY(BoundaryConditionsChanged);
  std::stringstream inputstream(_conditions);
  inputstream >> conditions;
}
void Box::setConditions(const std::vector< std::string >& _conditions)
{
  OBSERVE;
  NOTIFY(BoundaryConditionsChanged);
  conditions.set(_conditions);
}
const std::vector >  Box::getBoundingPlanes() const
{
  std::vector > res;
  for(int i=0;i0 && endpoint[1]>0 && endpoint[2]>0,"Vector does not define a full cuboid");
  M->setIdentity();
  M->diagonal()=endpoint;
  Vector &dinv = Minv->diagonal();
  for(int i=NDIM;i--;)
    dinv[i]=1/endpoint[i];
}
Box &Box::operator=(const Box &src)
{
  if(&src!=this){
    OBSERVE;
    // new matrix
    NOTIFY(MatrixChanged);
    delete M;
    delete Minv;
    M    = new RealSpaceMatrix(*src.M);
    Minv = new RealSpaceMatrix(*src.Minv);
    // new boundary conditions
    NOTIFY(BoundaryConditionsChanged);
    conditions = src.conditions;
  }
  return *this;
}
Box &Box::operator=(const RealSpaceMatrix &mat)
{
  OBSERVE;
  NOTIFY(MatrixChanged);
  setM(mat);
  return *this;
}
std::ostream & operator << (std::ostream& ost, const Box &m)
{
  ost << m.getM();
  return ost;
}