#include "GR_misc.h"
#include "GR_Face.h"
#include "GR_Geometry.h"
#include "GR_Vertex.h"
#include "GR_Vec.h"

Face::Face(Cell* const pCA, Cell* const pCB, const int iNV,
	   Vert* const pV0, Vert* const pV1,
	   Vert* const pV2, Vert* const pV3) :
  pCL(pCA), pCR(pCB)
  // Other stuff initialized in SetDefaultFlags and SetFaceLoc
{
  assert(iNV>0 && iNV<=4);
  // Fallthrough is deliberate
  switch (iNV) {
  case 4:
    apVVerts[3] = pV3;
  case 3:
    apVVerts[2] = pV2;
  case 2:
    apVVerts[1] = pV1;
  case 1:
    apVVerts[0] = pV0;
    break;
  default:
    assert2(0, "Bad number of verts");
  }
  vSetDefaultFlags();
  vSetFaceLoc();
}

void Face::vCopyAllFlags(const Face &F) {
  uiLoc = F.uiLoc;
  uiUser = F.uiUser;
  qSwapCheck = F.qSwapCheck;
  qSizeOK = F.qSizeOK;
  qSameShape = F.qSameShape;
  qStruct = F.qStruct;
  qDel = F.qDel;
}

void Face::vSetDefaultFlags()
{
  uiLoc = eInterior;
  uiUser = 0;
  qSizeOK = qStruct = qSameShape = qDel = false;
  qSwapCheck = true;
}

Face& Face::operator=(const Face& F)
{
  assert(eType() == F.eType());
  if (this != &F) {
    if (!qDeleted()) {
      // The face *this will no longer be connected to its previous
      // cells. Only do this if the face hasn't been marked as deleted
      // from the mesh already.
      if (pCL->qValid() && pCL->qHasFace(this)) pCL->vRemoveFace(this);
      if (pCR->qValid() && pCR->qHasFace(this)) pCR->vRemoveFace(this);
    }

    // Copy all the flag information for the face.
    vCopyAllFlags(F);

    // Not only copy the cells, but also make sure that the face
    // pointers for the cells are updated properly.
    pCL = F.pCL;
    if (pCL->qValid())
      pCL->vReplaceFace(&F, this);
    pCR = F.pCR;
    if (pCR->qValid())
      pCR->vReplaceFace(&F, this);

    // Copy the vertex pointers and make sure that the vertex hints are
    // updated properly.
    for (int i = 0; i < iNumVerts(); i++) {
      apVVerts[i] = F.apVVerts[i];
      if (apVVerts[i]->qValid() && !apVVerts[i]->qDeleted())
	apVVerts[i]->vSetHintFace(this);
    }
  }
  assert(!qDeleted());
  return (*this);
}

int Face::iFullCheck() const
{
  if (!qValid() || qDeleted()) return 0;
  if ( (pCL->qValid() && (pCL->qDeleted() || !pCL->qHasFace(this))) ||
       (pCR->qValid() && (pCR->qDeleted() || !pCR->qHasFace(this))) )
    return 0;
  else
    return 1;
}

#ifdef BROKEN_INLINE

bool Face::qHasVert(const Vert* const pV) const {
  assert(qValid());
  for (int i = iNumVerts() - 1; i >= 0; i--)
    if (pV == apVVerts[i]) return (true);
  return (false);
};

#endif

bool Face::qIsBdryFace() const
{
  assert(qValid());

  // Modified 07-Sep-99 by Charles Boivin
  // Face location flags should now be set properly, so no need to
  // compute it anymore..
  //
  // 28-Jan-00:  Does this work properly for 3D?  CFO-G

  if (uiLoc == Face::eUnknown) {
    if (pCL->iFullCheck()) {
      switch (pCL->eType()) {
      case Cell::eBdryEdge:
      case Cell::eTriBFace:
      case Cell::eQuadBFace:
	uiLoc = Face::eBdryFace;
	break;
      case Cell::eIntBdryEdge:
      case Cell::eIntTriBFace:
      case Cell::eIntQuadBFace:
	uiLoc = Face::eBdryTwoSide;
	break;
      default:
	// Check the other cell
	if (pCR->iFullCheck()) {
	  switch (pCR->eType()) {
	  case Cell::eBdryEdge:
	  case Cell::eTriBFace:
	  case Cell::eQuadBFace:
	    uiLoc = Face::eBdryFace;
	    break;
	  case Cell::eIntBdryEdge:
	  case Cell::eIntTriBFace:
	  case Cell::eIntQuadBFace:
	    uiLoc = Face::eBdryTwoSide;
	    break;
	  default:
	    uiLoc = Face::eInterior;
	  } // end of inner switch
	} // end of pCR handling
      } // end of outer switch
    } // end of pCL handling
  }

  // FIX ME: This shouldn't need to be computed every time.
  //	int iLoc = iFaceLoc();

  if ((uiLoc == Face::eBdryFace) ||
      (uiLoc == Face::eBdryTwoSide))
    return true;
  else
    return false;
}

bool Face::qSwapAllowed() const
{
  assert(qValid());
  if (qStructured() ||
//       !qCellsSameShape() ||
      qDeleted() ||
      qIsBdryFace() || (uiLoc == Face::ePseudoSurface))
    return false;
  else
    return true;
}

void Face::vAddCell(Cell* const pCNew)
{
  assert(qValid());
  assert2((pCL == pCInvalidCell || pCR == pCInvalidCell)
	  &&
	  (pCL != pCInvalidCell || pCR != pCInvalidCell),
	  "Need exactly one invalid cell");
  if (pCL == pCInvalidCell) pCL = pCNew;
  else                      pCR = pCNew;
  vSetFaceLoc();
  vMarkForSwap();
  vMarkIllSized();
}

void Face::vRemoveCell(const Cell* const pCOld)
{
  if (!qValid()) return; // A useful short-circuit when destroying part
			 // of a mesh.  This way there is no need to be
			 // careful about the order in which objects are
			 // killed.
  assert2((pCL == pCOld || pCR == pCOld), "Cell must bound the face");
  if (pCL == pCOld) pCL = pCInvalidCell;
  else              pCR = pCInvalidCell;
  vMarkForSwap();
  vMarkIllSized();
}

void Face::vReplaceCell(const Cell* const pCOld, Cell* const pCNew)
{
  assert(qValid());
  assert(pCNew->qValid());
  assert2((pCL == pCOld || pCR == pCOld), "Cell must bound the face");
  if (pCL == pCOld) pCL = pCNew;
  else              pCR = pCNew;
  vSetFaceLoc();
  vMarkForSwap();
  vMarkIllSized();
}

void Face::vInterchangeCells(void)
{
  assert(qValid());
  Cell * const pCTemp = pCL;
  pCL = pCR;
  pCR = pCTemp;
}

void Face::vInterchangeCellsAndVerts(void)
{
  assert(qValid());
  vInterchangeCells();
  Vert * apVTmp[4];
  int iV, iNV = iNumVerts();
  for (iV = 0; iV < iNV; iV++) {
    apVTmp[iV] = apVVerts[iV];
  }
  for (iV = 0; iV < iNV; iV++) {
    apVVerts[iV] = apVTmp[iNV-iV-1];
  }
}

void Face::vReplaceVert(Vert* const pVOld, Vert* const pVNew)
{
  assert(qValid());
  assert2(pVOld->qValid(), "Tried to replace non-existent vert");
  assert2(pVNew->qValid(), "Tried to add non-existent vert");
  int iRepl = -1;
  int i;
  for (i = 0; i < iNumVerts(); i++) {
    assert2(pVNew != apVVerts[i], "Vert already exists for this Face");
    if (apVVerts[i] == pVOld) iRepl = i;
  }
  assert2(iRepl >= 0, "Tried to replace vert which doesn't bound this Face");
  apVVerts[iRepl] = pVNew;
  vMarkForSwap();
  vMarkIllSized();
}

void Face::vSetFaceLoc()
{
  int iLType = Cell::eUnknown;
  int iRType = Cell::eUnknown;
  if (pCL->qValid())
    iLType = pCL->eType();
  if (pCR->qValid())
    iRType = pCR->eType();
  if (iLType == Cell::eIntBdryEdge ||
      iLType == Cell::eIntTriBFace ||
      iLType == Cell::eIntQuadBFace ||
      iRType == Cell::eIntBdryEdge ||
      iRType == Cell::eIntTriBFace ||
      iRType == Cell::eIntQuadBFace) {
    uiLoc = eBdryTwoSide;
  }
  else if (iLType == Cell::eBdryEdge ||
	   iLType == Cell::eTriBFace ||
	   iLType == Cell::eQuadBFace ||
	   iRType == Cell::eBdryEdge ||
	   iRType == Cell::eTriBFace ||
	   iRType == Cell::eQuadBFace) {
    uiLoc = eBdryFace;
  }
  else {
    uiLoc = eInterior;
  }
}

void Face::vAssign(Cell* const pCA, Cell* const pCB,
		   Vert* const pV0, Vert* const pV1,
		   Vert* const pV2, Vert* const pV3)
{
  assert(qValid());
  pCL = pCA;
  pCR = pCB;
  vSetFaceLoc();
  // Fallthrough is deliberate
  switch (iNumVerts()) {
  case 4:
    apVVerts[3] = pV3;
  case 3:
    apVVerts[2] = pV2;
  case 2:
    apVVerts[1] = pV1;
  case 1:
    apVVerts[0] = pV0;
    break;
  default:
    assert2(0, "Bad number of verts");
  }
  vMarkForSwap();
  vMarkIllSized();
}

void Face::vProjOntoFace(double adPoint[]) const
{
  double adNorm[3];
  vUnitNormal(adNorm);

  int iDim = pVVert(0)->iSpaceDimen();
  if (iDim == 2) {
    double adTemp[] = {adPoint[0] - pVVert(0)->dX(),
		       adPoint[1] - pVVert(0)->dY()};
    double dDistToFace = dDOT2D(adTemp, adNorm);
    adPoint[0] -= adNorm[0] * dDistToFace;
    adPoint[1] -= adNorm[1] * dDistToFace;
  }
  else {
    double adTemp[] = {adPoint[0] - pVVert(0)->dX(),
		       adPoint[1] - pVVert(0)->dY(),
		       adPoint[2] - pVVert(0)->dZ()};
    double dDistToFace = dDOT3D(adTemp, adNorm);
    adPoint[0] -= adNorm[0] * dDistToFace;
    adPoint[1] -= adNorm[1] * dDistToFace;
    adPoint[2] -= adNorm[2] * dDistToFace;
  }
}

void Face::vCentroid(double adCent[]) const
{
  assert(qValid());
  if (qIsBdryFace()) {
    // Use the BFace centroid routine so that the result lies on the
    // boundary.
    if (pCL->qIsBdryCell()) {
      pCL->vCentroid(adCent);
    }
    else {
      assert(pCR->qIsBdryCell());
      pCR->vCentroid(adCent);
    }
  }
  else {
    int iDim = pVVert(0)->iSpaceDimen();
    switch (iDim) {
    case 2:
      // Always okay in 2D.
      {
	adCent[0] = adCent[1] = 0;
	for (int i = 0; i < iNumVerts(); i++) {
	  adCent[0] += pVVert(i)->dX();
	  adCent[1] += pVVert(i)->dY();
	}
	adCent[0] /= iNumVerts();
	adCent[1] /= iNumVerts();
      }
      break;
    case 3:
      {
	assert(eType() == eTriFace);
	adCent[0] = adCent[1] = adCent[2] = 0;
	for (int i = 0; i < iNumVerts(); i++) {
	  adCent[0] += pVVert(i)->dX();
	  adCent[1] += pVVert(i)->dY();
	  adCent[2] += pVVert(i)->dZ();
	}
	adCent[0] /= iNumVerts();
	adCent[1] /= iNumVerts();
	adCent[2] /= iNumVerts();
      }
      break;
    default:
      assert2(0, "Unknown number of dimensions");
    }
  }
}

Cell* pCCommonCell(Face* const pF0, Face* const pF1)
{
  if (pF0 == pFInvalidFace || pF1 == pFInvalidFace) return (pCInvalidCell);
  Cell* pC0L = pF0->pCCellLeft();
  Cell* pC0R = pF0->pCCellRight();
  Cell* pC1L = pF1->pCCellLeft();
  Cell* pC1R = pF1->pCCellRight();
  // Don't return invalid cell if there's still a chance for a match
  if ((pC0L == pC1L || pC0L == pC1R) && pC0L->qValid()) return (pC0L);
  // Now returning invalid cell doesn't eliminate hope for a match
  else if (pC0R == pC1L || pC0R == pC1R) return (pC0R);
  else return (pCInvalidCell);
}

void Face::vAllCellHandles(GRUMMP_Entity* apEnt[]) const
{
  int i = 0;
  if (pCL->qValid() && !pCL->qIsBdryCell()) {
    apEnt[i++] = pCL;
  }
  if (pCR->qValid() && !pCR->qIsBdryCell()) {
    apEnt[i] = pCR;
  }
}
