/********************************************************************/
/* Copyright (c) 2019 System fugen G.K. and Yuzi Mizuno          */
/* All rights reserved.                                             */
/********************************************************************/
/********************************************************************/
/**
 * @file editfunc.cpp
 * @brief editfunc.h の実装
 */
#include "stdafx.h"
#include "mg/CParam_list.h"
#include "mg/CCisects.h"
#include "mg/CSisects.h"

#include "mg/Straight.h"
#include "mg/LBRep.h"
#include "mg/RLBRep.h"
#include "mg/CompositeCurve.h"
#include "topo/Face.h"
#include "topo/Loop.h"
#include "Calc/mgcalc.h"
#include "Common/CommandBase.h"
#include "fugenView.h"
#include "EditCmd/editfunc.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

/// 仮想交差オプション有効時の切断オブジェクト投影方向を決める。
/// @param[in] c_or_p 切断オブジェクトとなる曲線
/// @param[in] pView トリムイベントが発生したビュー
/// @param[in] bApparentIntersection 仮想交差を有効にするか
/// @return 投影方向が求まれば、その方向。
/// @throws 特別な例外は投げない。
///
/// 投影方向は分割コマンドと同様のルールで決定し、UI 側から計算側
/// (MGCL) に指示すること。
///
/// -# 仮想交差が ON の場合、投影方向をビュー直とする。
/// -# トリムイベントが平行ビューで発生した場合、切断曲線をビュー直方向に投影する。
/// -# トリムイベントが透視図ビューで発生し、かつ切断曲線が平面曲線である場合、
///    切断曲線を曲線平面に対して垂直な方向へ投影する。
/// -# それ以外の場合、曲線の投影方向を MGCL が計算する「面直方向」とする。
MGVector GetObjProjDir(
	const MGObject& c_or_p,
	const fugenView* pView,
	bool bApparentIntersection)
{
	bool bPlanView = !pView->is_perspective();
	const MGCurve* curve = dynamic_cast<const MGCurve*>(&c_or_p);
	const MGPoint* point = dynamic_cast<const MGPoint*>(&c_or_p);

	// 仮想交差オプションを優先する。
	if(bApparentIntersection || bPlanView){
		MGStraight ray;
		if(curve){
			pView->unproject_to_sl(curve->center(), ray);
		}else if(point){
			pView->unproject_to_sl(point->center(), ray);
		}else{
			pView->unproject_to_sl(CPoint(0, 0), ray);
		}
		return ray.direction();
	}

	if(curve && !dynamic_cast<const MGStraight*>(curve)){
		MGPlane plane;
		if(curve->is_planar(plane)){
			return plane.normal(0., 0.);
		}
	}

	// curve は 3D 形状。
	// 透視図ビュー。面直投影させたいので空オブジェクトを返す。
	return MGVector();
}

/// 曲線の両端をとにかく延長して新しい曲線を返す。
/// @param[in] curve 曲線
/// @param[in] dExtend 延長部分の長さ
/// @param[in] type 延長タイプ
/// @return 延長した曲線を返す。
/// @throws 特別な例外は投げない。
///
/// @pre dExtend > MGTolerance::wc_zero
///
/// @a curve が延長できない形状の場合は、戻り値は元と同じ形状となる。
/// いずれにせよ戻り値は new したオブジェクトであり、呼び出し側で
/// delete する必要がある。
///
/// @a curve が直線の場合、延長の長さを無限とする。
MGCurve* ExtendCurveNaive(
	const MGCurve& curve,
	double dExtend,
	ExtendType type)
{
	ASSERT(dExtend > MGTolerance::wc_zero());

	MGCurve* crvExtended= curve.clone();
	if (!curve.is_closed()) {
		switch (type) {
		case START:	crvExtended->extend(dExtend, true);	break;
		case END:	crvExtended->extend(dExtend, false);break;
		case BOTH:
			crvExtended->extend(dExtend, false);
			crvExtended->extend(dExtend, true);
			break;
		}
	}
	return crvExtended;
}

/// ピック曲線を複数のピックオブジェクトでトリムする。
/// @param[in] trimmee トリムターゲット曲線
/// @param[in] Ctrimmers 切断曲線群
/// @param[in] Strimmers 切断サーフェス群
/// @param[in] pView トリムイベントが発生したビュー
/// @param[in] bExtendCutters 切断曲線群を延長するか
/// @param[in] bApparentIntersection 仮想交差を有効にするか
/// @param[in,out] cmd コマンド（実際はドキュメントを処理）
/// @param[in] bExtract トリムではなく抽出を行う
/// @return トリムが確定してドキュメントに変更が入ったら true を返す。
/// @throws 特別な例外は投げない。
///
/// @note 旧 MGTrimTool::trim_curve
bool ExecTrimCurve(
	MGPickObject& trimmee,
	const MGPickObjects& Ctrimmers,
	const MGPickObjects& Strimmers,
	const fugenView* pView,
	bool bExtendCutters,
	bool bApparentIntersection,
	MGCommandBase& cmd,
	bool bExtract)
{
	MGCurve* c = dynamic_cast<MGCurve*>(trimmee.top_object());
	if(!c)
		return false;

	// 実際にはターゲットに変化を与えない。
	const MGCurve& Ctrimmee=*c;

	//1. Obtain intersection points with trimmers.
	MGCParam_list isects(c);
	MGPickObjects::const_iterator i=Ctrimmers.begin(), iend=Ctrimmers.end();
	for(; i!=iend; i++){
		const MGCurve* curvei = static_cast<MGCurve*>((**i).top_object());
		const MGCurve* cutter = 0;

		// 延長オプション ON の場合は
		// カッター要素をなるべく延長する。
		if(bExtendCutters){
			const double dExtend = curvei->box().len() * .5;
			cutter = ExtendCurveNaive(*curvei, dExtend, BOTH);
		}
		else{
			cutter = curvei;
		}

		const MGVector& dir = GetObjProjDir(*cutter, pView, bApparentIntersection);
		if(dir.is_null()){
			// 単純に MGCL に任せる。
			const MGCCisects& isectsi = Ctrimmee.isect(*cutter);

			MGCCisects::const_iterator j=isectsi.begin(), jend=isectsi.end();
			for(; j!=jend; j++){
				auto& cci = isectCast<MGCCisect>(j);
				isects.append(cci.param1());
			}
		}
		else{
			// 仮想交差用の形状を一時的に作成する。
			// - 曲線を押し出して曲面を生成してそれとターゲットとの交線を作成する。
			// - ターゲットと押出面との交線をトリムカッターとする。
			const double dExtrudeLen = (c->box() | cutter->box()).len();
			const std::unique_ptr<MGSurface> extrus(
				cutter->sweep(dir, -dExtrudeLen, dExtrudeLen));

			const MGCSisects& isectsi = Ctrimmee.isect(*extrus);
			MGCSisects::const_iterator j=isectsi.begin(), jend=isectsi.end();
			for(; j!=jend; j++){
				auto& csi = isectCast<MGCSisect>(j);
				isects.append(csi.param_curve());
			}
		}

		if(bExtendCutters){
			delete cutter;
		}
	}

	i=Strimmers.begin(), iend=Strimmers.end();
	for(; i!=iend; i++){
		const MGFSurface& fi=*((**i).top_object()->fsurface());
		const MGCSisects& isectsi=Ctrimmee.isect(fi);
		MGCSisects::const_iterator j=isectsi.begin(), jend=isectsi.end();
		for(; j!=jend; j++){
			auto& csi = isectCast<MGCSisect>(j);
			isects.append(csi.param_curve());
		}
	}

	if(isects.empty())
		return false;

	//2. Find where the pick point is located in the section points.
	isects.sort();
	double t=trimmee.parameter()[0];
	MGCParam_list::iterator location, isstart=isects.begin(), isend=isects.end();
	location=std::lower_bound(isstart, isend, t);

	//3. Trim the curve so that picked portion be discarded.
	MGCurve* cnew=Ctrimmee.clone();
	MGInterval crange=cnew->param_range();
	if(!bExtract){//trim
		if(location==isstart){
			//3.1 trim starting portion of the curve.
			crange.set_low(*isstart);
			cnew->limit(crange);
			cmd.replace(trimmee,cnew);
			return true;
		}else if(location==isend){
			//3.2 trim ending portion of the curve.
			location--;
			crange.set_high(*location);
			cnew->limit(crange);
			cmd.replace(trimmee,cnew);
			return true;
		}else{
			//3.3 Curve is divided into 2 curves.
			MGInterval crange2(crange);
			crange.set_low(*location);
			location--;
			crange2.set_high(*location);
			MGCurve* cnew2=cnew->clone();
			cnew->limit(crange);
			cnew2->limit(crange2);
			MGGelPositions gelps;
			gelps.push_back(MGGelPosition(trimmee.bottom_group(),cnew));
			gelps.push_back(MGGelPosition(trimmee.bottom_group(),cnew2));
			cmd.replace(trimmee,gelps);
			return true;
		}
	}else{//extract
		MGCParam_list::iterator loc1 = location;
		MGCParam_list::iterator loc2 = location;
		if(loc1 != isstart) crange.set_low(*--loc1);
		if(loc2 != isend) crange.set_high(*loc2);
		cnew->limit(crange);
		cmd.replace(trimmee,cnew);
		return true;
	}
}

/// ピックサーフェスを複数のピックオブジェクトでトリムする。
/// @param[in] surface surface to trim (trimmee).
/// @param[in] Ctrimmers 切断曲線群
/// @param[in] Strimmers 切断サーフェス群
/// @param[in] dir projection direction to project @a Ctrimmers onto the surface.
/// @param[in] bExtendCutters 切断曲線群を延長するか
/// @param[in,out] cmd コマンド（実際はドキュメントを処理）
/// @param[in] bExtract トリムではなく抽出を行う
/// @return トリムが確定してドキュメントに変更が入ったら true を返す。
/// @throws 特別な例外は投げない。
///
/// @note 旧 MGTrimTool::trim_fsurface
bool ExecTrimFSurf(
	MGPickObject& surface,
	const MGPickObjects& Ctrimmers,
	const MGPickObjects& Strimmers,
	const MGVector& dir,
	bool bExtendCutters,
	MGCommandBase& cmd,
	bool bExtract)
{
	MGFSurface* f = surface.top_object()->fsurface();
	if(!f)
		return false;

	size_t n = Ctrimmers.size();
	std::vector<const MGCurve*> curves(n);
	for (size_t i =0; i < n; i++)
		curves[i]=dynamic_cast<const MGCurve*>(Ctrimmers[i].top_object());

	//a point(in the parameter space) of the surface that
	//indicates the part to remove. The part that uv lies on is removed.
	const MGPosition& uv = surface.parameter();

	std::vector<UniqueFace> result;
	if(bExtendCutters){
		// curves を可能な限り延長する。
		const size_t nCurve = curves.size();
		std::vector<UniqueCurve> wkcurves(nCurve);
		const double dExtend = f->get_box().len();

		for(size_t i = 0; i < nCurve; ++i)
			wkcurves[i].reset(ExtendCurveNaive(*curves[i], dExtend, BOTH));

		if(bExtract){
			std::unique_ptr<MGFace> face;
			f->extract(wkcurves, dir, uv, face);
			if(!face.get()) return false;
			result.emplace_back(face.release());
		}else{
			f->trim(wkcurves, dir, uv, result);
		}
	}else{
		if(bExtract){
			std::unique_ptr<MGFace> face;
			f->extract(curves, dir, uv, face);
			if(!face.get()) return false;
			result.emplace_back(face.release());
		}else{
			f->trim(curves, dir, uv, result);
		}
	}
	if(result.empty())
		return false;

	MGGelPositions gelps;
	size_t nresult=result.size();
	for(size_t i=0; i<nresult; i++){
		gelps.push_back(MGGelPosition(surface.bottom_group(),result[i].release()));
	}

	cmd.replace(surface,gelps);
	return true;
}

/// 分割コマンド実行（交点・交線による）
/// @param[in,out] target 分割処理の対象曲線
/// @param[in,out] cutter 切断位置を決めるための要素群
/// @param[in,out] cmd コマンド（実際はドキュメントを処理）
/// @return 曲線分割後のパーツの個数
/// @throws 特別な例外は投げない。
size_t ExecSplitByIntersection(
	MGPickObjects& target,
	const MGPickObjects& cutter,
	MGCommandBase& cmd)
{
	const size_t nTarg = target.size();
	const size_t nCutter = cutter.size();

	MGGelPositions gelposall;

	// MGObject::intersection を利用してみる。

	for(size_t i = 0; i < nTarg; ++i){
		const MGObject* lhs = target[i].top_object();

		const MGCurve* curve = dynamic_cast<const MGCurve*>(lhs);
		if(curve){
			// ターゲットが曲線のケースでは、一律 intersecion ベースで
			// 分割形状を定義する。
			MGisects isectsi;
			for(size_t j = 0; j < nCutter; ++j){
				const MGObject* rhs = cutter[j].top_object();
				isectsi.push_back(lhs->isect(*rhs));
			}
			MGisects::iterator
				first = isectsi.begin(), last = isectsi.end();

			// isectsi の曲線上パラメータをすべて回収する。
			// パラメータの sort | uniq は後続関数内で処理。
			std::vector<double> params;
			for(; first != last; ++first){
				const MGPosition& param = (*first)->isect0_param1();
				ASSERT(param.sdim() == 1);
				params.push_back(param[0]);
			}
			// ExecSplitCurveByPoints のメイン部分と同じ処理
			if(!params.empty()){
				MGGelPositions& gelpos = ExecSplitCurveByParam(target[i], params, cmd.current_group());
				gelposall.append(gelpos);
			}
		}else if(const MGFSurface* fsurf = lhs->fsurface()){
			// ターゲットがサーフェスのケースでは、切断オブジェクトが
			// 曲線の場合はそれをそのまま利用し、
			// サーフェスの場合は両者の交線を計算して、それを利用する。

			MGisects isects; // ここで宣言しないと交線が削除される。
			std::vector<const MGCurve*> curves;

			for(size_t j = 0; j < nCutter; ++j){
				const MGObject* rhs = cutter[j].top_object();
				const MGCurve* curve = dynamic_cast<const MGCurve*>(rhs);
				if(curve){
					curves.push_back(curve);
				}else if(const MGFSurface* surf = rhs->fsurface()){
					isects.push_back(lhs->isect(*surf));
					MGisects::iterator
						first = isects.begin(), last = isects.end();
					for(; first != last; ++first){
						if(MGSSisect* ss = dynamic_cast<MGSSisect*>(first->get())){
							// この curve は isects とライフタイムが同じなので注意。
							const MGCurve* curve = &ss->line();
							ASSERT(curve->sdim() == 3);
							curves.push_back(curve);
						}
					}
				}
			}

			// 一気に分割する。
			if(!curves.empty()){
				MGGelPositions& gelpos = ExecSplitSurfByCurve(target[i], curves, cmd.current_group());
				gelposall.append(gelpos);
			}
		}
	}

	const size_t nresult = gelposall.size();
	if(nresult){
		cmd.replace(target, gelposall);
	}
	return nresult;
}

/// 補助関数
/// @param[in,out] target 分割処理の対象曲線
/// @param[in,out] params 分割位置の曲線パラメータ。
///    呼び出し後、内容が後はソートされている。
/// @param[in,out] parent
/// @return 
MGGelPositions ExecSplitCurveByParam(
	MGPickObject& target,
	std::vector<double>& params,
	MGGroup* parent)
{
	const MGCurve* targ = dynamic_cast<const MGCurve*>(target.top_object());
	size_t nPoint = params.size();
	ASSERT(nPoint);

	MGGelPositions gelpos;
	std::sort(params.begin(), params.end());

	if(targ->is_closed() && nPoint > 1){
		// 閉曲線の場合は特別ロジック起動。

		--nPoint;
		for(size_t i = 0; i < nPoint; ++i){
			double st = params[i];
			double et = params[i + 1];

			MGCurve* ci = targ->copy_limitted(
				MGInterval(st, et));
			if(ci){
				gelpos.push_back(MGGelPosition(parent,ci));
			}
		}
		// 最後に et-st 間を作成する。
		// BUG: MGCL の閉曲線はこの辺の処理が弱い。
		double st = params.back();
		double et = params.front();
		//MGCurve* clast = targ->copy_limitted(
		//	MGInterval(st, et));

		std::unique_ptr<MGCurve> clast(targ->copy_limitted(MGInterval(st, targ->param_e())));
		if(clast.get()){
			std::unique_ptr<MGCurve> cfirst(targ->copy_limitted(MGInterval(targ->param_s(), et)));
			if(cfirst.get()){
				MGCompositeCurve* result = new MGCompositeCurve(clast.release());
				result->connect_to_end(cfirst.release());
				gelpos.push_back(MGGelPosition(parent,result));
			}
		}
	}
	else{
		// 閉曲線でない場合は単純になる。
		MGCurve* c0 = targ->copy_limitted(
			MGInterval(targ->param_s(), params.front()));
		if(c0){
			gelpos.push_back(MGGelPosition(parent,c0));
		}

		params.push_back(targ->param_e());

		// nPoint は push_back する前のものを利用する。
		for(size_t i = 0; i < nPoint; ++i){
			double st = params[i];
			double et = params[i + 1];

			MGCurve* ci = targ->copy_limitted(
				MGInterval(st, et));
			if(ci){
				gelpos.push_back(MGGelPosition(parent,ci));
			}
		}
	}
	return gelpos;
}

/// 分割コマンド実行（曲線をロケート点で）
/// @param[in,out] target 分割処理の対象曲線
/// @param[in,out] params 分割位置の曲線パラメータ。
///    呼び出し後、内容が後はソートされている。
/// @param[in,out] cmd コマンド（実際はドキュメントを処理）
/// @return 曲線分割後のパーツの個数
/// @throws 特別な例外は投げない。
///
/// @note 旧 MGSplitTool::SplitCurveByPoints
///
/// @bug 閉曲線の分割では「端点」を含むセグメントが抽出できていない。
size_t ExecSplitCurveByPoints(
	MGPickObject& target,
	std::vector<double>& params,
	MGCommandBase& cmd)
{
	MGGelPositions gelpos =  ExecSplitCurveByParam(target, params, cmd.current_group());
	const size_t nRes = gelpos.size();
	cmd.replace(target, gelpos);
	return nRes;
}

/// 補助関数
/// @param[in,out] target 分割処理の対象サーフェス
/// @param[in,out] curves 分割位置を決める曲線群
/// @param[in,out] parent
/// @return 
MGGelPositions ExecSplitSurfByCurve(
	MGPickObject& target,
	const std::vector<const MGCurve*>& curves,
	MGGroup* parent)
{
	MGGelPositions gelpos;

	MGFSurface* surf = target.top_object()->fsurface();
	std::vector<UniqueFace> results;
	MGVector null_vec;
	surf->split(curves,null_vec,results);

	const size_t nresult = results.size();
	for(size_t i = 0; i < nresult; ++i)
		gelpos.push_back(MGGelPosition(parent,results[i].release()));
	return gelpos;
}

/// 分割コマンド実行（サーフェスをアイソカーブで）
/// @param[in,out] target 分割処理の対象サーフェス
/// @param[in,out] uparams 分割位置の U 一定アイソカーブ
/// @param[in,out] vparams 分割位置の V 一定アイソカーブ
/// @param[in,out] cmd コマンド（実際はドキュメントを処理）
/// @return サーフェス分割後のパーツの個数
/// @throws 特別な例外は投げない。
size_t ExecSplitSurfByIsocurves(
	MGPickObject& target,
	std::vector<bool>& paramKinds,
	std::vector<double>& params,
	MGCommandBase& cmd)
{
	// 前準備
	std::vector<double> uparams, vparams;
	size_t npara=paramKinds.size();
	for(size_t k=0; k<npara; k++)
		if(paramKinds[k])
			uparams.push_back(params[k]);
		else
			vparams.push_back(params[k]);

	const MGFSurface* targ = target.top_object()->fsurface();
	ASSERT(targ);

	// targ から領域 [u[i],v[j]] X [u[i + 1],v[j + 1]] を次々抽出する処理
	// ……ではなく、MGCL の split を利用する。

	std::sort(uparams.begin(), uparams.end());
	uparams.erase(
		std::unique(uparams.begin(), uparams.end(), MGREqual),
		uparams.end());
	std::sort(vparams.begin(), vparams.end());
	vparams.erase(
		std::unique(vparams.begin(), vparams.end(), MGREqual),
		vparams.end());

	const size_t nU = uparams.size();
	const size_t nV = vparams.size();

	MGGelPositions gelpos;
	MGGroup* parent = cmd.current_group();

	std::vector<UniqueFSurface> work;
	work.emplace_back(targ->clone_fsurface());

	// U 方向をまとめて処理
	for(size_t j = 0; j < nU; ++j){
		std::vector<UniqueFSurface> work3;
		const size_t nWork = work.size();
		for(size_t k = 0; k < nWork; ++k){
			const MGFSurface* fwork = work[k].get();
			std::vector<UniqueFSurface> work2;
			fwork->split(uparams[j], true, work2);
			std::move(work2.begin(), work2.end(), std::back_inserter(work3));
		}
		work = std::move(work3);
	}
	//for(size_t jjj=0; jjj<work.size(); jjj++) work[jjj]->outFS(std::cout);

	// V 方向をまとめて処理
	for(size_t j = 0; j < nV; ++j){
		std::vector<UniqueFSurface> work3;
		const size_t nWork = work.size();
		for(size_t k = 0; k < nWork; ++k){
			const MGFSurface* fwork = work[k].get();
			std::vector<UniqueFSurface> work2;
			fwork->split(vparams[j], false, work2);
			std::move(work2.begin(), work2.end(), std::back_inserter(work3));
		}
		work = std::move(work3);
	}
	//for(size_t jjj=0; jjj<work.size(); jjj++) work[jjj]->outFS(std::cout);

	const size_t nRes = work.size();
	for(size_t i = 0; i < nRes; ++i){
		gelpos.push_back(MGGelPosition(parent,work[i].release()->object_pointer()));
	}
	cmd.replace(target, gelpos);
	return nRes;
}
