/////////////////////////////////////////////////////////////
//                                                         //
// 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 "TriBox.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>

TriBox::TriBox()
{}

double TriBox::m_random(double imin,double imax)const
{
  return imin+((imax-imin)*((double)(rand())/(double)(RAND_MAX)));
}

TriBox::TriBox(const Vector3& pmin,const Vector3& pmax,bool inverted):m_pmin(pmin),m_pmax(pmax)
{
  m_inverted=inverted;
}

/*!
  add a plane to the box

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

pair<Vector3,Vector3> TriBox::getBoundingBox()
{
  return make_pair(m_pmin,m_pmax); 
}

Vector3 TriBox::getAPoint(int)const
{
  double px,py,pz;

  pz=m_random(m_pmin.z(),m_pmax.z());
  py=m_random(m_pmin.y(),m_pmax.y());
  double dy=m_pmax.y()-m_pmin.y();
  double y=py-m_pmin.y();
  double ry=m_inverted ? y/dy : 1.0-(y/dy);
  double dx=(m_pmax.x()-m_pmin.x())/2.0;
  double xmin=(m_pmin.x()+m_pmax.x())/2.0-dx*ry;
  double xmax=(m_pmin.x()+m_pmax.x())/2.0+dx*ry;
  px=m_random(xmin,xmax);

  return Vector3(px,py,pz);
}

Plane TriBox::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);
}

const map<double,const AGeometricObject*> TriBox::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;
}


bool TriBox::isIn(const Vector3& V) const
{
  bool res; 

  res=(V.z()<m_pmax.z()) && (V.z()>m_pmin.z());
  if((V.y()<m_pmax.y()) && (V.y()>m_pmin.y())){
    double dy=m_pmax.y()-m_pmin.y();
    double y=V.y()-m_pmin.y();
    double ry=m_inverted ? y/dy : 1.0-(y/dy);
    double dx=(m_pmax.x()-m_pmin.x())/2.0;
    double xmin=(m_pmin.x()+m_pmax.x())/2.0-dx*ry;
    double xmax=(m_pmin.x()+m_pmax.x())/2.0+dx*ry;
    //    std::cout << "y: " << V.Y() << " dy: " << dy << " ry: " << ry << " dx: " << dx << " xmin: " << xmin << "xmax: " << xmax << std::endl; 
    res=res && (V.x()<xmax) && (V.x()>xmin);
  } else {
    res=false;
  }

  return res;
}

bool TriBox::isIn(const Sphere& S)
{
  double r=S.Radius();
  Vector3 p=S.Center();
  bool res=isIn(p); 

  vector<Plane>::iterator iter=m_planes.begin();
  
  //  std::cout << "p: " << p<< " r: " << r;
  double dist=2*r;
  while((iter!=m_planes.end()) && (dist>r)){
    dist=iter->getDist(p);
    //    std::cout << "  dist: " << dist ;
    iter++;
  }
  //  std::cout << std::endl;

  return res && (dist>r);  
}

/*!
  Check if sphere is fully outside volume

  \param S the sphere
  \warning DUMMY IMPLEMENTATION
*/
bool TriBox::isFullyOutside(const Sphere& S)
{
  return true;
}

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

  return ost;
}
