/********************************************************************/
/* Copyright (c) 2017 System fugen G.K. and Yuzi Mizuno          */
/* All rights reserved.                                             */
/********************************************************************/
#include "MGCLStdAfx.h"
#include "mg/Box.h"
#include "mg/BPointSeq.h"
#include "mg/Position.h"
#include "mg/LBRep.h"
#include "mg/RLBRep.h"
#include "mg/Straight.h"
#include "mg/SPointSeq.h"
#include "mg/SBRep.h"
#include "mg/RSBRep.h"
#include "mg/Tolerance.h"

#if defined(_DEBUG)
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

using namespace std;

// Implementation of knot remove.

//ノット削除関数(B表現曲線のみ)
//トレランスはline_zeroを使用する。元のノットが細かいものほど削除しやすい
//removal knot. line_zero tolerance is used.
void MGCurve::remove_knot(){;}

#define START_DELNUM 4000
//Remove knot if removed line has the difference less than line_zero();
//The difference is checked only for the space id of coef(.,j+k)
//for k=0, ..., nd-1.
void MGLBRep::remove_knot(int j, int nd){
	int k=order();
	int km1 = k-1;	//次元数
	int i, n=bdim();
	int mid=(km1+n)/2;
	if(mid<=km1) mid=k;
	else if(mid>START_DELNUM){mid=START_DELNUM;}
	double totalTol = 0.0;
	const double line0=MGTolerance::line_zero();

	//std::cout<<(*this)<<endl;
	int num_remained;
	for(i=n-1; i>=mid;){
		int ndel = remove_knot_one(line0,i,totalTol,num_remained,j,nd);
		i-=int(ndel+num_remained);
	}
//	std::cout<<(*this)<<endl;
	totalTol = 0.0;
	i=k;
//	int nt=int(bdim())-mid;
	int nt=int(bdim())-mid-1;if(nt<0) nt=0;
	while(int(bdim())-i>nt){
		remove_knot_one(line0,i,totalTol,num_remained,j,nd);
		i+=num_remained;
	}
//	std::cout<<(*this)<<endl;
}

//ノット削除関数(1つのノット)
//戻り値は、削除したノットの数
//When snum!=0, tolerance of totalTol is checked only for coef(.,sid+j),
//of j=0, ..., snum-1. When snum=0, snum is set as sdim();
int MGLBRep::remove_knot_one(
	double line0,		//Tolerance allowed for the knot removal.
	int	id,			//削除しようとするノットの番号
	double& totalTol,	//誤差合計
	int& num_knot,	//Remained knot number at knot(id) after removed.
	int sid,			//Space dimension start id of this LBRep's B-coef.
	int snum			//Num of space dimension for the totalTol tolerance check.
){
	int sd=sdim();
	if(!snum)
		snum=sd;
	MGPosition P(snum), Q(snum), R(snum);
	const MGBPointSeq& bcoef=line_bcoef();

	const int k = order();	//オーダー
	const int km1 = k - 1;	//次数
	const int n=bdim();	//B表現次元
	const double tau=knot(id);

	//Get the multiplicity of knot(id).
	//We need the end knot id of the multiplicity in 'id'.
	int m, idold=id;
	int multi=1;
	for(m=id+1; m<n && tau==knot(m); m++){
		multi++; id++;
	}
	for(m=idold-1; m>=k && tau==knot(m); m--)
		multi++;
	num_knot=multi;

	//const double line0=MGTolerance::line_zero();
	MGBPointSeq temp_bp(2 * km1 + 1, sd);

	int	last = id - multi;
	int first = id - km1;
	int ndel;				//削除したノットの数
	if(k == 2){
	//オーダー2のときの処理
		const int off = first - 1;
		bcoef.point(off,sid,snum,P);
		bcoef.point(last+1,sid,snum,Q);
		MGStraight st1(P, Q);
		bcoef.point(first,sid,snum,R);
		double tmpTol = st1.distance(R);
		totalTol += tmpTol;
		if(line0>0. && totalTol>line0)
			ndel=0;
			//誤差合計がトレランス以上の場合ノット削除を行わない
		else ndel=1;
	}else{

	//オーダー3以上のときの処理
	for(ndel = 0; ndel<multi; ndel++){
		const int off = first - 1;
		temp_bp.store_at(0, coef(off));
		temp_bp.store_at(last + 1 - off, coef(last + 1));
		int i = first;	int j = last;
		int ii = 1;		int jj = last - off;
		while((j-i) > ndel){
			double ti=knot(i), tjmndel=knot(j - ndel);
			double alfi = (tau-ti) / (knot(i+k+ndel) - ti);
			double alfj = (tau - tjmndel) / (knot(j+k) - tjmndel);
			temp_bp.store_at(ii, (coef(i) - (1.0 - alfi) * temp_bp(ii-1)) / alfi);
			temp_bp.store_at(jj, (coef(j) - alfj * temp_bp(jj+1)) / (1.0 - alfj));
			i++;	j--;	ii++;	jj--;
		}

		if((j-i) < ndel){
			temp_bp.point(ii - 1,sid,snum,P);
			temp_bp.point(jj + 1,sid,snum,Q);
			totalTol += (P - Q).len();
		}else{
			double ti=knot(i);
			double alfi = (tau - ti) / (knot(i+k+ndel) - ti);
			temp_bp.point(ii+ndel+1,sid,snum,P);
			temp_bp.point(ii-1,sid,snum,Q);
			bcoef.point(i,sid,snum,R);
			totalTol += (R - (alfi*P + (1.0 - alfi)*Q)).len();
		}
		//誤差合計がトレランス以上の場合ノット削除を行わない
		if(line0>0. && totalTol > line0) break;

		i = first;	j = last;
		while((j-i) > ndel){
			m_line_bcoef.store_at(i, temp_bp(i - off));
			m_line_bcoef.store_at(j, temp_bp(j - off));
			i++;	j--;
		}
		first--; last++;
	}

	}

	if(!ndel){totalTol=0.; return 0;}
	int l = id + 1;
	const int num_end_knot = n + km1;	//最終ノットの番号
	for(; l <= num_end_knot; l++) knot(l-ndel) = knot(l);
	int j = (2*id - multi - km1)/2;//最初のコントロールポイント id
	int i = j;
	for(l = 1; l < ndel; l++){
		if((l % 2) == 1)i++; else j--;
	}
	for(l = i+1; l<n; l++, j++) m_line_bcoef.store_at(j, coef(l));

	//曲線を更新する
	int newnbd=n - ndel;
	m_knot_vector.set_bdim(newnbd);
	m_line_bcoef.set_length(newnbd);
	num_knot=multi - ndel;
	if(num_knot) totalTol = 0.0;//ノット削除が行われないとき誤差合計をクリアする
	return ndel;
}

//ノット削除関数
void MGRLBRep::remove_knot(){
	double Pmax = 0.0, Wmin = 1.0;
	int sd=sdim();
	for(int i = 0;i < bdim(); i++){
		MGPosition pos = eval_position(knot(i));
		double P = pos.len();
		double W = coef(i, sd);		//最終次元が重みである
		if(P > Pmax) Pmax = P;
		if(W < Wmin) Wmin = W;
	}
	double save=MGTolerance::line_zero();
	double tol =  save* Wmin /(1 + Pmax);
	MGTolerance::set_line_zero(tol);
	m_line.remove_knot();
	MGTolerance::set_line_zero(save);
}

//ノット削除関数
//トレランスはline_zeroを使用する。元のノットが細かいものほど削除しやすい
void MGSBRep::remove_knot(){
	remove_knot_u();
	exchange_uv();
	remove_knot_u();
	exchange_uv();
}

//uノットを削除する
int MGSBRep::remove_knot_u(){
	int k = order_u(), n = bdim_u();//order and bdim along u.
	int km1=k-1;
	int mid=(km1+n)/2;
	if(mid<=km1)
		mid=k;
	int num_remained;

	int nDel = 0;		//削除したノットの数
	double totalTol = 0.0;
	double line0=MGTolerance::line_zero();
	int i;
	for(i=n-1; i>=mid;){
		int nDelOne = remove_knot_u_one(line0,i, totalTol, num_remained);
		i-=(nDelOne+num_remained);
		nDel+=nDelOne;
	}
	i=k;
	int nt=bdim_u()-mid-1;
	if(nt<0) nt=0;

	totalTol=0.;
	while(int(bdim_u())-i>nt){
		int nDelOne=remove_knot_u_one(line0,i,totalTol,num_remained);
		i+=num_remained;
		nDel+=nDelOne;
	}
	return nDel;
}

//uノットを削除する
//関数の戻り値は削除したノットの数
int MGSBRep::remove_knot_u_one(
	double line0,		//Tolerance allowed for the knot removal. 
						//When line0<=0., removal will be done uncoditionally.
	int	id,			//削除しようとするノットの番号
	double& totalTol,	//最初は0を入力する。あとはremove_knot_u_oneが管理する。
	//削除を繰り返しているときは誤差が加算される。これ以上削除できない時0クリアされる。
	int& num_knot	//Remained knot number at knot(id) after removed.
){
	const int k = order_u();	//オーダー
	const int km1 = k - 1;		//次数
	const int m = bdim_u(), n = bdim_v();
	const int sd=sdim();
	const double tau=knot_u(id);

	//Get the multiplicity of knot(id).
	//We need the end knot id of the multiplicity in 'id'.
	int a, multi=1, idold=id;
	for(a=id+1; a<n && tau==knot_u(a); a++){
		multi++; id++;
	}
	for(a=idold-1; a>=k && tau==knot_u(a); a--)
		multi++;
	num_knot=multi;

	int	last = id - multi;
	int first = id - km1;

	int l;
	int ndel=0;			//削除したノットの数
	if(k==2){

//オーダー2のときの処理
	const int off = first - 1;
	double maxTol = 0.0;
	int l;
	for(l=0; l<n; l++){
		MGStraight st1(coef(off, l), coef(last + 1, l));
		double tmpTol = st1.distance(coef(first, l));
		if(line0>0. && tmpTol>line0){ndel= 0; break;}//ノットが削除できなかった
		if(tmpTol>maxTol) maxTol = tmpTol;
	}
	if(l>=n){//When possible to delete knot.
		ndel=1;totalTol += maxTol;
	}

	}else{
//オーダー3以上のときの処理
	for(ndel = 0; ndel < multi; ndel++){

	MGSPointSeq temp(2*km1+1,n,sd);
	const int off = first - 1;
	int i, j, ii, jj;
	double maxTol = 0.0;
	for(l=0; l<n; l++){
		i = first;	j = last;
		ii = 1;		jj = last-off;

		temp.store_at(0, l, coef(off,l));
		temp.store_at(last+1-off,l,coef(last+1,l));
		double alfi, alfj, tmpTol = 0.0;
		while((j-i) > ndel){
			double ti=knot_u(i), tjmndel=knot_u(j-ndel);
			alfi = (tau-ti) / (knot_u(i+k+ndel) - ti);
			alfj = (tau-tjmndel) / (knot_u(j+k) - tjmndel);
			temp.store_at(ii,l,(coef(i,l) - (1.0 - alfi)*temp(ii-1,l)) / alfi);
			temp.store_at(jj, l, (coef(j,l) - alfj * temp(jj+1,l)) / (1.-alfj));
			i++;	j--;	ii++;	jj--;
		}
		if((j-i) < ndel){
			tmpTol = (temp(ii-1,l) - temp(jj+1,l)).len();
			if(line0>0. && tmpTol>line0){ maxTol=tmpTol; break;}
		}else{
			double ti=knot_u(i);
			alfi = (tau-ti) / (knot_u(i+k+ndel)-ti);
			tmpTol=(coef(i,l)-(alfi * temp(ii+ndel+1,l)+(1.-alfi)*temp(ii-1,l))).len();
			if(line0>0. && tmpTol>line0){ maxTol=tmpTol; break;}
		}
		if(tmpTol > maxTol) maxTol=tmpTol;
	}
	totalTol += maxTol;
	if(line0>0. && totalTol>line0) break;
	i = first;	j = last;
	while((j-i) > ndel){
		for(int vk=0; vk<n; vk++){
			m_surface_bcoef.store_at(i,vk,temp(i-off,vk));
			m_surface_bcoef.store_at(j,vk,temp(j-off,vk));
		}
		i++; j--;
	}
	first--;	last++;

	}
	}

	if(!ndel){totalTol=0.; return 0;}
	const int num_end_uknot = m + km1;		//最終uノットの番号
	for(l=id+1; l<=num_end_uknot; l++) knot_u(l-ndel)=knot_u(l);
	int j=(2*id-multi-km1)/2;	//最初のコントロールポイント
	int i=j;
	for(l=1; l<ndel; l++){
		if((l % 2) == 1) i++; else j--;
	}
	for(l=i+1; l<m; l++){
		for(int vk = 0; vk < n; vk++) m_surface_bcoef.store_at(j,vk,coef(l,vk));
		j++;
	}
	//曲面を更新する
	int newnbd=m-ndel;
	m_uknot.set_bdim(newnbd);
	m_surface_bcoef.set_length(newnbd, n);
	num_knot=multi - ndel;
	if(num_knot) totalTol = 0.0;//ノット削除が行われないとき誤差合計をクリアする
	return ndel;
}

//uノットを一つ削除する
//関数の戻り値は削除したノットの数
int MGSBRep::remove_knot_one(
	double line0,		//Tolerance allowed for the knot removal. 
						//When line0<=0., removal will be done uncoditionally.
	int	id,	//削除しようとするノットの番号
	double& tol,//削除後の誤差が出力される
	bool u_knot	//削除対象が(u,v)のいずれのknot vectorかを入力する
				//=trueのとき、u-knot_vectorを削除
){
	int num_knot;
	int delnum;
	if(u_knot) delnum=remove_knot_u_one(line0,id,tol,num_knot);
	else{
		exchange_uv();
		delnum=remove_knot_u_one(line0,id,tol,num_knot);
		exchange_uv();
	}
	return delnum;
}

//ノット削除関数
//トレランスはline_zeroを使用する。元のノットが細かいものほど削除しやすい
void MGRSBRep::remove_knot(){
	int i = 0, j = 0;
	double Pmax = 0.0, Wmin = 1.0;
	for(;i < bdim_u(); i++){
		for(j = 0; j < bdim_v(); j++){
			MGPosition pos = eval(knot_u(i), knot_v(j), 0);
			double P = pos.len();
			double W = coef(i, j, sdim());		//最終次元が重みである
			if(P>Pmax)
				Pmax = P;
			if(W<Wmin)
				Wmin = W;
		}
	}
	double save=MGTolerance::line_zero();
	double tol =  save* Wmin /(1 + Pmax);
	MGTolerance::set_line_zero(tol);
	m_surface.remove_knot();
	MGTolerance::set_line_zero(save);
}

//複数カーブの共通で削除できるノットを削除する。
//ただし、入力カーブは同じノットベクトルを持つものとする。
void remove_knot_curves(
	MGPvector<MGLBRep>& brepList,		//曲線列
	MGLBRep**		tp,	//接続面	input and output.
		//if tp[i] for crvl[i] was not null, converted new tp will be output.
	double tp_length
){
	MGLBRep& lb0=*(brepList[0]);
	int nCrv = (int)brepList.size();	//曲線数
	int k = lb0.order(),n = lb0.bdim();//次元数&B表現次元
	int km1=k-1;
	int mid=(km1+n)/2;
	if(mid<=km1) mid=k;
	const double line0=MGTolerance::line_zero();
	const double rc0=tp_length*MGTolerance::angle_zero();
	int num_remained;
	int preDel, nDel, nDel2;

	std::vector<double> totalTol(nCrv,0.);
	std::vector<double> totalTolTP(nCrv,0.);
	std::vector<MGLBRep> lb(nCrv), tpSave(nCrv);
	std::vector<bool> tp_specified(nCrv);

	int i;
	for(i=n-1; i >= mid;){
		int j=0;
		for(; j<nCrv; j++){
			tp_specified[j]=false;if(tp){ if(tp[j]) tp_specified[j]=true;};
			MGLBRep& lbj=*(brepList[j]);lb[j]=lbj; //曲線は保存しておく
			nDel2=nDel = lbj.remove_knot_one(line0,i, totalTol[j],num_remained);
			if(tp_specified[j]){
				tpSave[j]=*tp[j];
				nDel2 = tp[j]->remove_knot_one(rc0,i, totalTolTP[j],num_remained);
			}
			if(j == 0) preDel = nDel;
			if(!nDel || nDel!=nDel2 || preDel!=nDel){//ノット削除に失敗したときの処理
				std::fill(totalTol.begin(), totalTol.end(), 0.0);	//誤差合計をクリアする
				std::fill(totalTolTP.begin(), totalTolTP.end(), 0.0);	//誤差合計をクリアする
				int j2=j;
				while(j2>=0){
					MGLBRep& lbj2=*(brepList[j2]); lbj2=lb[j2];	//曲線を元に戻す
					if(tp_specified[j2]) *(tp[j2])=tpSave[j2];
					j2--;
				}
				break;
			}
		}
		if(j>=nCrv)
			i-=(nDel+num_remained);
		else i--;
	}

	std::fill(totalTol.begin(), totalTol.end(), 0.0);	//誤差合計をクリアする
	std::fill(totalTolTP.begin(), totalTolTP.end(), 0.0);//誤差合計をクリアする
	i=k;
	int nt=lb0.bdim()-mid;
	while(lb0.bdim()-i>nt){
		int j=0;
		for(; j<nCrv; j++){
			tp_specified[j]=false;if(tp){ if(tp[j]) tp_specified[j]=true;};
			MGLBRep& lbj=*(brepList[j]);lb[j]=lbj;//曲線は保存しておく
			nDel2=nDel = lbj.remove_knot_one(line0,i, totalTol[j],num_remained);
			if(tp_specified[j]){
				tpSave[j]=*tp[j];
				nDel2 = tp[j]->remove_knot_one(rc0,i, totalTolTP[j],num_remained);
			}
			if(j == 0) preDel = nDel;
			if(!nDel || nDel!=nDel2 || preDel!=nDel){//ノット削除に失敗したときの処理
				std::fill(totalTol.begin(), totalTol.end(), 0.0);	//誤差合計をクリアする
				std::fill(totalTolTP.begin(), totalTolTP.end(), 0.0);	//誤差合計をクリアする
				int j2=j;
				while(j2>=0){
					MGLBRep& lbj2=*(brepList[j2]); lbj2=lb[j2];	//曲線を元に戻す
					if(tp_specified[j2]) *(tp[j2])=tpSave[j2];
					j2--;
				}
				break;
			}
		}
		if(j>=nCrv)
			i+=num_remained;
		else i++;
	}
}

#define INCNUM 20
///Rebuild this NURBS by reconstructing new knot configuration.
std::unique_ptr<MGRLBRep> MGRLBRep::rebuild_with_new_knot_configuration(
	double error,	//Error alowed to rebuild. If error<=0., MGTolerance::line_zero()
	                //will be employed.
	int parameter_normalization
		//Indicates how the parameter normalization be done:
		//=0: no parameter normalization.
		//=1: normalize to range=(0., 1.);
		//=2: normalize to make the average length of the 1st derivative 
		//    is as equal to 1. as possible.
)const{
	if(error<=0.)
		error=MGTolerance::line_zero();
	double esave=MGTolerance::set_line_zero(error);

	//制御点を生成する
	MGKnotVector t=knot_vector();
	int len = t.length();
	t.change_number(len*INCNUM);
	std::unique_ptr<MGRLBRep> rlb=std::unique_ptr<MGRLBRep>(new MGRLBRep(*this,t));
	rlb->remove_knot();
	MGTolerance::set_line_zero(esave);

	if(parameter_normalization){
		rlb->change_range(0.,1.);
		if(parameter_normalization==2){
			MGVector v0=rlb->eval(0.,1);
			MGVector v1=rlb->eval(0.5,1);
			MGVector v2=rlb->eval(1.,1);
			double t2=(v0.len()+v1.len()+v2.len())/3.;
			rlb->change_range(0.,t2);
		}
	}
	return rlb;
}