#include <mystdlib.h>
#include <core/register_archive.hpp>

#include <linalg.hpp>
#include <csg.hpp>

namespace netgen
{

Parallelogram3d :: Parallelogram3d (Point<3> ap1, Point<3> ap2, Point<3> ap3)
{
  p1 = ap1;
  p2 = ap2;
  p3 = ap3;

  CalcData();
}

Parallelogram3d ::~Parallelogram3d ()
{
  ;
}

void Parallelogram3d :: SetPoints (Point<3> ap1, 
				   Point<3> ap2, 
				   Point<3> ap3)
{
  p1 = ap1;
  p2 = ap2;
  p3 = ap3;

  CalcData();
}

void Parallelogram3d :: CalcData()
{
  v12 = p2 - p1;
  v13 = p3 - p1;
  p4 = p2 + v13;

  n = Cross (v12, v13);
  n.Normalize();
}

int Parallelogram3d :: 
IsIdentic (const Surface & s2, int & inv, double eps) const
{
  int id = 
    (fabs (s2.CalcFunctionValue (p1)) <= eps) &&
    (fabs (s2.CalcFunctionValue (p2)) <= eps) &&
    (fabs (s2.CalcFunctionValue (p3)) <= eps);

  if (id)
    {
      Vec<3> n2;
      n2 = s2.GetNormalVector(p1);
      inv = (n * n2) < 0;
    }
  return id;
}


double Parallelogram3d :: CalcFunctionValue (const Point<3> & point) const
{
  return n * (point - p1);
}

void Parallelogram3d :: CalcGradient (const Point<3> & /* point */, 
				      Vec<3> & grad) const
{
  grad = n;
}

void Parallelogram3d :: CalcHesse (const Point<3> & /* point */, Mat<3> & hesse) const
{
  hesse = 0;
}

double Parallelogram3d :: HesseNorm () const
{
  return 0;
}

Point<3> Parallelogram3d :: GetSurfacePoint () const
{
  return p1;
}

void Parallelogram3d :: Print (ostream & str) const
{
  str << "Parallelogram3d " << p1 << " - " << p2 << " - " << p3 << endl;
}

  
void Parallelogram3d :: 
GetTriangleApproximation (TriangleApproximation & tas, 
			  const Box<3> & /* bbox */, 
			  double /* facets */) const
{
  tas.AddPoint (p1);
  tas.AddPoint (p2);
  tas.AddPoint (p3);
  tas.AddPoint (p4);
  tas.AddTriangle (TATriangle (0, 0, 1, 2));
  tas.AddTriangle (TATriangle (0, 2, 1, 3));
}










Brick :: Brick (Point<3> ap1, Point<3> ap2, 
		Point<3> ap3, Point<3> ap4)
{
  faces.SetSize (6);
  surfaceids.SetSize (6);
  surfaceactive.SetSize(6);

  p1 = ap1; p2 = ap2;
  p3 = ap3; p4 = ap4;

  for (int i = 0; i < 6; i++)
    {
      faces[i] = new Plane (Point<3>(0,0,0), Vec<3> (0,0,1));
      surfaceactive[i] = 1;
    }

  CalcData();
}

Brick :: ~Brick ()
{
  for (int i = 0; i < 6; i++)
    delete faces[i];
}

Primitive * Brick :: CreateDefault ()
{
  return new Brick (Point<3> (0,0,0),
		    Point<3> (1,0,0),
		    Point<3> (0,1,0),
		    Point<3> (0,0,1));
}



Primitive * Brick :: Copy () const
{
  return new Brick (p1, p2, p3, p4);
}

void  Brick :: Transform (Transformation<3> & trans)
{
  trans.Transform (p1);
  trans.Transform (p2);
  trans.Transform (p3);
  trans.Transform (p4);

  CalcData();
}









INSOLID_TYPE Brick :: BoxInSolid (const BoxSphere<3> & box) const
{
  /*
  int i;
  double maxval;
  for (i = 1; i <= 6; i++)
    {
      double val = faces.Get(i)->CalcFunctionValue (box.Center());
      if (i == 1 || val > maxval)
	maxval = val;
    }
  
  if (maxval > box.Diam()) return IS_OUTSIDE;
  if (maxval < -box.Diam()) return IS_INSIDE;
  return DOES_INTERSECT;
  */

  bool inside = 1;
  bool outside = 0;

  Point<3> p[8];
  for (int j = 0; j < 8; j++)
    p[j] = box.GetPointNr(j);

  for (int i = 0; i < 6; i++)
    {
      bool outsidei = 1;
      for (int j = 0; j < 8; j++)
	{
	  // Point<3> p = box.GetPointNr (j);
	  double val = faces[i]->Plane::CalcFunctionValue (p[j]);

	  if (val > 0)  inside = 0;
	  if (val < 0)  outsidei = 0;
	}
      if (outsidei) outside = 1;
    }

  if (outside) return IS_OUTSIDE;
  if (inside) return IS_INSIDE;
  return DOES_INTERSECT;
}

INSOLID_TYPE Brick :: PointInSolid (const Point<3> & p,
			   double eps) const
{
  double maxval = faces[0] -> Plane::CalcFunctionValue (p);
  for (int i = 1; i < 6; i++)
    {
      double val = faces[i] -> Plane::CalcFunctionValue (p);
      if (val > maxval) maxval = val;
    }

  if (maxval > eps) return IS_OUTSIDE;
  if (maxval < -eps) return IS_INSIDE;
  return DOES_INTERSECT;
}


INSOLID_TYPE Brick :: VecInSolid (const Point<3> & p,
				  const Vec<3> & v,
				  double eps) const
{
  INSOLID_TYPE result = IS_INSIDE;
  for (int i = 0; i < faces.Size(); i++)
    {
      INSOLID_TYPE hres = faces[i]->VecInSolid(p, v, eps);
      if (hres == IS_OUTSIDE || result == IS_OUTSIDE) result = IS_OUTSIDE;
      else if (hres == DOES_INTERSECT || result == DOES_INTERSECT) result = DOES_INTERSECT;
      else result = IS_INSIDE;
    }
  return result;

  /*
  INSOLID_TYPE is = IS_INSIDE;
  Vec<3> grad;
  double scal;

  for (int i = 0; i < faces.Size(); i++)
    {
      if (faces[i] -> PointOnSurface (p, eps))
	{
	  GetSurface(i).CalcGradient (p, grad);
	  scal = v * grad;
	  
	  if (scal >= eps) 
	    is = IS_OUTSIDE;
	  if (scal >= -eps && is == IS_INSIDE)
	    is = DOES_INTERSECT;
	}
    }
  return is;
  */

  /*
  Point<3> p2 = p + 1e-2 * v;
  return PointInSolid (p2, eps);
  */
}





INSOLID_TYPE Brick :: VecInSolid2 (const Point<3> & p,
				    const Vec<3> & v1,
				    const Vec<3> & v2,
				    double eps) const
{
  INSOLID_TYPE result = IS_INSIDE;
  for (int i = 0; i < faces.Size(); i++)
    {
      INSOLID_TYPE hres = faces[i]->VecInSolid2(p, v1, v2, eps);
      if (hres == IS_OUTSIDE || result == IS_OUTSIDE) result = IS_OUTSIDE;
      else if (hres == DOES_INTERSECT || result == DOES_INTERSECT) result = DOES_INTERSECT;
      else result = IS_INSIDE;
    }
  return result;
}

INSOLID_TYPE Brick :: VecInSolid3 (const Point<3> & p,
				    const Vec<3> & v1,
				    const Vec<3> & v2,
				    double eps) const
{
  INSOLID_TYPE result = IS_INSIDE;
  for (int i = 0; i < faces.Size(); i++)
    {
      INSOLID_TYPE hres = faces[i]->VecInSolid3(p, v1, v2, eps);
      if (hres == IS_OUTSIDE || result == IS_OUTSIDE) result = IS_OUTSIDE;
      else if (hres == DOES_INTERSECT || result == DOES_INTERSECT) result = DOES_INTERSECT;
      else result = IS_INSIDE;
    }
  return result;
}

INSOLID_TYPE Brick :: VecInSolid4 (const Point<3> & p,
				    const Vec<3> & v,
				    const Vec<3> & v2,
				    const Vec<3> & m,
				    double eps) const
{
  INSOLID_TYPE result = IS_INSIDE;
  for (int i = 0; i < faces.Size(); i++)
    {
      INSOLID_TYPE hres = faces[i]->VecInSolid4(p, v, v2, m, eps);
      if (hres == IS_OUTSIDE || result == IS_OUTSIDE) result = IS_OUTSIDE;
      else if (hres == DOES_INTERSECT || result == DOES_INTERSECT) result = DOES_INTERSECT;
      else result = IS_INSIDE;
    }
  return result;
}



















void Brick :: 
GetPrimitiveData (const char *& classname, NgArray<double> & coeffs) const
{
  classname = "brick";
  coeffs.SetSize(12);
  coeffs.Elem(1) = p1(0);
  coeffs.Elem(2) = p1(1);
  coeffs.Elem(3) = p1(2);

  coeffs.Elem(4) = p2(0);
  coeffs.Elem(5) = p2(1);
  coeffs.Elem(6) = p2(2);

  coeffs.Elem(7) = p3(0);
  coeffs.Elem(8) = p3(1);
  coeffs.Elem(9) = p3(2);

  coeffs.Elem(10) = p4(0);
  coeffs.Elem(11) = p4(1);
  coeffs.Elem(12) = p4(2);
}

void Brick :: SetPrimitiveData (NgArray<double> & coeffs)
{
  p1(0) = coeffs.Elem(1);
  p1(1) = coeffs.Elem(2);
  p1(2) = coeffs.Elem(3);

  p2(0) = coeffs.Elem(4);
  p2(1) = coeffs.Elem(5);
  p2(2) = coeffs.Elem(6);

  p3(0) = coeffs.Elem(7);
  p3(1) = coeffs.Elem(8);
  p3(2) = coeffs.Elem(9);

  p4(0) = coeffs.Elem(10);
  p4(1) = coeffs.Elem(11);
  p4(2) = coeffs.Elem(12);

  CalcData();
}



void Brick :: CalcData()
{
  v12 = p2 - p1;
  v13 = p3 - p1;
  v14 = p4 - p1;

  Point<3> pi[8];
  int i1, i2, i3;
  int i, j;
  
  i = 0;
  for (i3 = 0; i3 <= 1; i3++)
    for (i2 = 0; i2 <= 1; i2++)
      for (i1 = 0; i1 <= 1; i1++)
	{
	  pi[i] = p1 + i1 * v12 + i2 * v13 + i3 * v14;
	  i++;
	}

  static int lface[6][4] =
  { { 1, 3, 2, 4 },
    { 5, 6, 7, 8 },
    { 1, 2, 5, 6 },
    { 3, 7, 4, 8 },
    { 1, 5, 3, 7 },
    { 2, 4, 6, 8 } };
  
  NgArray<double> data(6);
  for (i = 0; i < 6; i++)
    {
      const Point<3> lp1 = pi[lface[i][0]-1];
      const Point<3> lp2 = pi[lface[i][1]-1];
      const Point<3> lp3 = pi[lface[i][2]-1];

      Vec<3> n = Cross ((lp2-lp1), (lp3-lp1));
      n.Normalize();
      
      for (j = 0; j < 3; j++)
	{
	  data[j] = lp1(j);
	  data[j+3] = n(j);
	}
      faces[i] -> SetPrimitiveData (data);
      /* 
	 {
	 faces.Elem(i+1) -> SetPoints
	 (pi[lface[i][0]-1],
	 pi[lface[i][1]-1],
	 pi[lface[i][2]-1]);
	 }
      */
    }
}


void Brick :: Reduce (const BoxSphere<3> & box)
{
  double val;
  // Point<3> p;
  Point<3> p[8];
  for(int j=0;j<8;j++)
    p[j]=box.GetPointNr(j);

  for (int i = 0; i < 6; i++)
    {
      bool hasout = 0;
      bool hasin = 0;
      for (int j = 0; j < 8; j++)
	{
	  // p = box.GetPointNr (j);
	  val = faces[i]->Plane::CalcFunctionValue (p[j]);
	  if (val > 0)  hasout = 1;
	  else if (val < 0)  hasin = 1;
	  if (hasout && hasin) break;
	}
      surfaceactive[i] =  hasout && hasin;
    }
}

void Brick :: UnReduce ()
{ 
  for (int i = 0; i < 6; i++)
    surfaceactive[i] = 1;
}



OrthoBrick :: OrthoBrick (const Point<3> & ap1, const Point<3> & ap2)
  : Brick (ap1, 
	   Point<3> (ap2(0), ap1(1), ap1(2)),
	   Point<3> (ap1(0), ap2(1), ap1(2)),
	   Point<3> (ap1(0), ap1(1), ap2(2)))
{
  pmin = ap1;
  pmax = ap2;
}
	 
INSOLID_TYPE OrthoBrick :: BoxInSolid (const BoxSphere<3> & box) const
{
  if (pmin(0) > box.PMax()(0) ||
      pmin(1) > box.PMax()(1) ||
      pmin(2) > box.PMax()(2) ||
      pmax(0) < box.PMin()(0) ||
      pmax(1) < box.PMin()(1) ||
      pmax(2) < box.PMin()(2))
    return IS_OUTSIDE;

  if (pmin(0) < box.PMin()(0) &&
      pmin(1) < box.PMin()(1) &&
      pmin(2) < box.PMin()(2) &&
      pmax(0) > box.PMax()(0) &&
      pmax(1) > box.PMax()(1) &&
      pmax(2) > box.PMax()(2))
    return IS_INSIDE;

  return DOES_INTERSECT;
}


void OrthoBrick :: Reduce (const BoxSphere<3> & box)
{
  surfaceactive.Elem(1) =
    (box.PMin()(2) < pmin(2)) && (pmin(2) < box.PMax()(2));
  surfaceactive.Elem(2) =
    (box.PMin()(2) < pmax(2)) && (pmax(2) < box.PMax()(2));

  surfaceactive.Elem(3) =
    (box.PMin()(1) < pmin(1)) && (pmin(1) < box.PMax()(1));
  surfaceactive.Elem(4) =
    (box.PMin()(1) < pmax(1)) && (pmax(1) < box.PMax()(1));

  surfaceactive.Elem(5) =
    (box.PMin()(0) < pmin(0)) && (pmin(0) < box.PMax()(0));
  surfaceactive.Elem(6) =
    (box.PMin()(0) < pmax(0)) && (pmax(0) < box.PMax()(0));
}

RegisterClassForArchive<Parallelogram3d, Surface> regpar;
RegisterClassForArchive<Brick, Primitive> regbrick;
RegisterClassForArchive<OrthoBrick, Brick> regob;
}
