/////////////////////////////////////////////////////////////
//                                                         //
// Copyright (c) 2007-2017 by The University of Queensland //
// Centre for Geoscience Computing                         //
// http://earth.uq.edu.au/centre-geoscience-computing      //
//                                                         //
// Primary Business: Brisbane, Queensland, Australia       //
// Licensed under the Open Software License version 3.0    //
// http://www.apache.org/licenses/LICENSE-2.0              //
//                                                         //
/////////////////////////////////////////////////////////////

#include "BoxWithPlanes3D.h"


//-- system includes --
#include <cstdlib>

using std::rand;

// --- STL includes ---
#include <utility>
#include <map>

using std::make_pair;
using std::map;

//--- IO includes ---
#include <iostream>


BoxWithPlanes3D::BoxWithPlanes3D()
{
}

/*!
  helper function, return random value between min & max
*/
double BoxWithPlanes3D::m_random(double imin,double imax) const
{
  return imin+((imax-imin)*((double)(rand())/(double)(RAND_MAX)));
}


/*!
  Construct box 

  \param pmin minimum point of bounding box
  \param pmax maximum point of bounding box
*/
BoxWithPlanes3D::BoxWithPlanes3D(const Vector3& pmin,const Vector3& pmax): m_pmin(pmin),m_pmax(pmax)
{}

/*!
  add a plane to the box

  \param P the plane
*/
void BoxWithPlanes3D::addPlane(const Plane& P)
{
  m_planes.push_back(P);
}

/*!
  get bounding box
*/
pair<Vector3,Vector3> BoxWithPlanes3D::getBoundingBox()
{
  return make_pair(m_pmin,m_pmax); 
}
  

/*!
  get point inside the box. The Argument is ignored
*/
Vector3 BoxWithPlanes3D::getAPoint(int) const
{
  double px,py,pz;
  
  px=m_random(m_pmin.x(),m_pmax.x());
  py=m_random(m_pmin.y(),m_pmax.y());
  pz=m_random(m_pmin.z(),m_pmax.z());

  return Vector3(px,py,pz);
}
  
/*!
  Return closest plane to a given point. Assumes that the list of Planes is not empty.
  
  \param p the point
  \warning check non-empty list of lines (via hasPlane()) before invoking
*/
Plane BoxWithPlanes3D::getClosestPlane(const Vector3& p)
{
  vector<Plane>::iterator PL=m_planes.begin();
  double dist=PL->getDist(p);

  for(vector<Plane>::iterator iter=m_planes.begin();
      iter!=m_planes.end();
      iter++){
    double ndist=iter->getDist(p);
    if(ndist<dist){
      PL=iter;
      dist=ndist;
    }
  }

  return (*PL);
}

/*!
  get the n closes planes to a given point

  \param p the point
  \param nmax the max. nr. of planes
*/
const map<double,const AGeometricObject*> BoxWithPlanes3D::getClosestObjects(const Vector3& p,int nmax) const
{
  map<double,const AGeometricObject*> res;

  for(vector<Plane>::const_iterator iter=m_planes.begin();
      iter!=m_planes.end();
      iter++){
    double ndist=iter->getDist(p);
    res.insert(make_pair(ndist,&(*iter)));
  }

  return res;
}
  
/*!
  check if point is inside

  \param p the point
*/
bool BoxWithPlanes3D::isIn(const Vector3& p) const
{
  return ((p.x()>m_pmin.x()) && (p.x()<m_pmax.x()) && 
	  (p.y()>m_pmin.y()) && (p.y()<m_pmax.y()) &&
	  (p.z()>m_pmin.z()) && (p.z()<m_pmax.z()));
}
  
/*!
  check if a Sphere is inside and doesn't intersect any planes

  \param S the Sphere
*/
bool BoxWithPlanes3D::isIn(const Sphere& S)
{
  double r=S.Radius();
  Vector3 p=S.Center();

  //  std::cout << "rad: " << r << "  pos: " << p << std::endl;

  bool inside=(p.X()>m_pmin.X()+r) && (p.X()<m_pmax.X()-r) && 
    (p.Y()>m_pmin.Y()+r) && (p.Y()<m_pmax.Y()-r) &&
    (p.Z()>m_pmin.Z()+r) && (p.Z()<m_pmax.Z()-r);
  
  vector<Plane>::iterator iter=m_planes.begin();
  
  double dist=2*r;
  while((iter!=m_planes.end()) && (dist>r)){
    dist=iter->getDist(p);
    iter++;
  }

  return inside && (dist>r);  
}

/*
  check if a Sphere is fully outside the box
 \param S the Sphere
*/
bool BoxWithPlanes3D::isFullyOutside(const Sphere& S)
{
    double r=S.Radius();
  Vector3 p=S.Center();

  //  std::cout << "rad: " << r << "  pos: " << p << std::endl;

  bool outside=
    (p.X()<m_pmin.X()-r) || (p.X()>m_pmax.X()+r) || 
    (p.Y()<m_pmin.Y()-r) || (p.Y()>m_pmax.Y()+r) ||
    (p.Z()<m_pmin.Z()-r) || (p.Z()>m_pmax.Z()+r);

  return outside;

}

ostream& operator<< (ostream& ost, const BoxWithPlanes3D& L)
{
  ost << L.m_pmin << " to " << L.m_pmax;

  return ost;
}

