/****************************************************************************
** GMAF - The Geometry Modeling Algorithms Framework
**
** Created : 04032000
**
** Copyright (C) 2000 by Le-Jeng Shiue.  All rights reserved.
**
****************************************************************************/
#include <assert.h>
#include <vector>
#include <math.h>

#include "BlitzExtension/blitzConfig.h"

#include "gmafPCCM.h"
#include "DataStructure/gmafDataStructureManager.h"
#include "Algorithm/CCSubdivision/gmafQuad.h"
#include "Algorithm/CCSubdivision/gmafQuadMap.h"
#include "gmafPCCMQuad.h"
#include "gmafPCCMQuadMap.h"

#include "Algorithm/CCSubdivision/gmafQuadVertex.h"
#include "Algorithm/CCSubdivision/gmafCCSubdivision.h"

#include "gmafPCCMVisualObject.h"

#ifdef _GUI_QT
#include "qtPCCMWidget.h"
#endif _GUI_QT

gmafPCCM::gmafPCCM(gmafDataStructureManager* dsm)
  : gmafAlgorithm(dsm, "PCCM"), mpQuadMap(NULL), mpPCCMQuadMap(NULL),
    mQuadArray(), mPCCMQuadArray(), mUseNormalRule(false) {
  mpCCSub = new gmafCCSubdivision(dsm);
}

gmafPCCM::~gmafPCCM() {
  delete mpCCSub;
}

void gmafPCCM::run() {
#ifdef _GUI_QT
  qtPCCMWidget* dialog = new qtPCCMWidget(this);
  dialog->show();
#endif _GUI_QT
}

void gmafPCCM::run(int rule, int depth, float blrat, bool normal) {
  mpCCSub->run((gmafCCSubdivision::RULE)rule, depth, blrat);
  setNormalRule(normal);
  exec();
}

void gmafPCCM::exec() {
  mpQuadMap = mpDSM->getQuadMap();
  mpPCCMQuadMap = mpDSM->createPCCMQuadMap();
  
  // --------------------------------------------------------------------
  vector<gmafQuad*>& qlist = mpQuadMap->getQuadList();
  vector<gmafPCCMQuad*>& pccmqlist = mpPCCMQuadMap->getPCCMQuadList();

  vector<gmafQuad*>::iterator qitr = qlist.begin();
  vector<gmafPCCMQuad*>::iterator pccmqitr = pccmqlist.begin();

  for (; qitr != qlist.end(); qitr++, pccmqitr++) {
    mQuadArray.reference((*qitr)->getQuadArray());
    mPCCMQuadArray.reference((*pccmqitr)->getPCCMControlNet());

    knotInsertion();
  }
  cornerSmoothing();
}


//======================================================================
void gmafPCCM::knotInsertion() {
  Range all = Range::all();

  VertexMatrix2D tmpArray(mQuadArray.rows(), mPCCMQuadArray.cols());

  VertexMatrix2D qarray;
  VertexMatrix2D pccmqarray;
  // ------------------
  // vertical scan-line
  tmpArray(all, Range(4, tmpArray.cols()-5)) =
    mQuadArray(all, Range(3, mQuadArray.cols()-4));
  qarray.reference(mQuadArray(all, Range(0,3)));
  pccmqarray.reference(tmpArray(all, Range(0,3)));
  vpass(qarray, pccmqarray);

  qarray.reference(mQuadArray(all, Range(mQuadArray.cols()-4, blitz::toEnd)));
  pccmqarray.reference(tmpArray(all, Range(tmpArray.cols()-4, blitz::toEnd)));
  qarray.reverseSelf(1);
  pccmqarray.reverseSelf(1);
  vpass(qarray, pccmqarray);
  
  // --------------------
  // horizontal scan-line
  mPCCMQuadArray(Range(4, mPCCMQuadArray.rows()-5), all) =
    tmpArray(Range(3, tmpArray.rows()-4), all);
  qarray.reference(tmpArray(Range(0,3), all));
  pccmqarray.reference(mPCCMQuadArray(Range(0,3), all));
  hpass(qarray, pccmqarray);

  qarray.reference(tmpArray(Range(tmpArray.rows()-4, blitz::toEnd), all));
  pccmqarray.reference(mPCCMQuadArray(Range(tmpArray.rows()-4, blitz::toEnd), all));
  qarray.reverseSelf(0);
  pccmqarray.reverseSelf(0);
  hpass(qarray, pccmqarray);

  // -----------------------------------
  // Create Q00 (extraordinary vertices)
  // traverse the etraordinary vertex list
  vector<gmafQuadVertex*>& vl = mpQuadMap->getVertexList();
  vector<gmafQuadVertex*>::iterator vlitr = vl.begin();
  Vertex3D a, b, r, newV;
  for (; vlitr != vl.end(); vlitr++) {
    int n = (*vlitr)->getValance();
    if (n != 4) { // skip ordinary vertex
      a = (*vlitr)->sumAlphaVertices(mpQuadMap->getQuadList());
      b = (*vlitr)->sumBetaVertices(mpQuadMap->getQuadList());
      r = (*vlitr)->sumGammaVertices(mpQuadMap->getQuadList());
      newV = ((n*n*a) + (4*b) + r)/(n*(n+5));
      (*vlitr)->writeBackPCCMAlphaVertex(newV, mpPCCMQuadMap->getPCCMQuadList());
    }
  }  
}

#define PI 3.1415926535897932384626433832795029L
#define Nij ((n+i-j)%n)
void gmafPCCM::cornerSmoothing() {
  blitz::firstIndex i;
  blitz::secondIndex j;

  vector<gmafQuadVertex*>& vl = mpQuadMap->getVertexList();
  vector<gmafQuadVertex*>::iterator vlitr = vl.begin();
  for (; vlitr != vl.end(); vlitr++) {
    int n = (*vlitr)->getValance();
    if (n != 4) { // skip ordinary vertex
      Range r(1,n);

      // Create An and Bn
      Matrix2D An(r, r);
      Matrix2D Bn(r, r);
      double c = cos(2.0*PI/n);
      double a = 1-c;
      An = (2.0*a/n)*cos((2.0*PI/n)*(i-j));
      if (n%2) { // n is odd
	Bn = pow(-1.0, Nij); 
      } else { // n is even
	Bn = pow(-1.0, j) - (2.0*Nij*pow(-1.0, j-i))/n;
      }

      // collect the data 
      Vertex3D Q00;
      VertexMatrix1D Q10(r), _Q20(r), Q40(r); 
      (*vlitr)->getPCCMQij(Q00, Q10, _Q20, Q40, 
			   mpPCCMQuadMap->getPCCMQuadList());

      VertexMatrix1D Q20(r), dQ20(r), dQ40(r);
      // calculate the new Qij
      if (n%2 == 0) { // special rare case (n > 4 & n is even)
	//Vertex3D residual = sum(pow(-1, i)*_Q40(i)/n, i);
	Vertex3D residual = 0;
	for (int idx = 1; idx <= n; idx++) residual +=  pow(-1, idx)*Q40(idx);
	residual /= n;
	double rthreshold = 0.0000001;
	if (residual(0) > rthreshold || residual(0) < -rthreshold ||
	    residual(1) > rthreshold || residual(1) < -rthreshold ||
	    residual(2) > rthreshold || residual(2) < -rthreshold) {
	  dQ40 = pow(-1,i) * residual;
	  Q40 -= dQ40;
	}
      }
      
      VertexMatrix1D H(r), Q11(r);

      // D = sum(A(i,k) * B(k,j), k);
      // The above expression computes the matrix product of A and B.
      if (!mUseNormalRule)
	Q10 = Q00 + sum(An(i,j) * Q10(j), j);
      else {
	DOUBLE Wn = 1+cos(2*PI/n)+cos(PI/n)*sqrt(2*(9+cos(2*PI/n)));

	Matrix2D An_plus(r,r);
	An_plus = (2.0*a/n)*cos((2.0*PI/n)*(i-j-1));

	Matrix2D An_p_Anp(r,r);
	An_p_Anp = An + An_plus;
      
	Vertex3D P00;
	VertexMatrix1D P30(r), P33(r); 
	(*vlitr)->getVertices(P00, P30, P33, mpQuadMap->getQuadList());
	
	VertexMatrix1D Q10_normals(r);
	Q10_normals = 
          Wn*(sum(An(i,j) * P30(j), j) + sum(An_p_Anp(i,j) * P33(j), j));
	float scale = 6.0; // it's an experimental value
	Q10 = Q00 + (1.0/scale/(2.0+Wn)) * Q10_normals;
      }
      Q20 = (Q40 - (2*Q00))/5 + ((6*Q10)/5);
      dQ20 = Q20 - _Q20;
      
      H = Q10 + (cos(2*PI/n)/6)*(Q40 - Q20);
      Q11 = sum(Bn(i,j) * H(j), j);

      // contribute the data
      if (n%2 == 0) { // n is even and greater than 4
	(*vlitr)->setPCCMQij(Q10, Q20, Q11, dQ20, dQ40, 
			     mpPCCMQuadMap->getPCCMQuadList());
      } else {
	(*vlitr)->setPCCMQij(Q10, Q20, Q11, dQ20, 
			     mpPCCMQuadMap->getPCCMQuadList());
      }
    }
  }  
}
#undef PI
#undef Nij

#define P_3 qarray(all,0)
#define P0  qarray(all,1)
#define P3  qarray(all,2)
#define P6  qarray(all,3)
#define Q0  pccmqarray(all,0)
#define Q1  pccmqarray(all,1)
#define Q2  pccmqarray(all,2)
#define Q4  pccmqarray(all,3)
inline void gmafPCCM::vpass(VertexMatrix2D& qarray, VertexMatrix2D& pccmqarray) {
  Range all = Range::all();
  Q0 = (P_3 + P3)/6 + (4*P0)/6;
  Q1 = ((2*P0) + P3)/3;
  Q2 = (P0 + (2*P3))/3;
  Q4 = ((2*P3) + P6)/3;
}

inline void gmafPCCM::hpass(VertexMatrix2D& qarray, VertexMatrix2D& pccmqarray) {
  qarray.transposeSelf(blitz::secondDim, blitz::firstDim);
  pccmqarray.transposeSelf(blitz::secondDim, blitz::firstDim);
  vpass(qarray, pccmqarray);
}


gmafVisualObject* gmafPCCM::visualize() {
  assert(mpPCCMQuadMap != NULL);
  return new gmafPCCMVisualObject(mpPCCMQuadMap);
}
