/********************************************************************/
/* Copyright (c) 2019 System fugen G.K. and Yuzi Mizuno          */
/* All rights reserved.                                             */
/* ***************************************************** */
/********************************************************************/
// fillet.cpp
// implementation for fillet module
#include "stdafx.h"
#include "Calc/curve.h"
#include "Calc/fillet.h"
#include "mg/Ellipse.h"
#include "mg/Plane.h"
#include "mg/Straight.h"

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

namespace{

	std::unique_ptr<MGCurve> generate_arc(
		const MGCurve& curve1, double& t1,
		const MGCurve& curve2, double& t2,
		const MGUnit_vector& normal,
		double radius,
		int& ret
		){
		std::unique_ptr<MGCurve> arc(
			new MGEllipse(curve1,curve2,normal,radius,t1,t2,ret));
		if(ret)
			return std::unique_ptr<MGCurve>(nullptr);

		MGPosition Ps=arc->start_point();
		MGPosition Pe=arc->end_point();
		MGPosition P1=curve1.eval(t1);
		if((Pe-P1).len()<(Ps-P1).len())
			arc->negate();
		return arc;
	}

	// extension を説明すると、
	// ret -1 : 失敗
	// ret 0 : どの curvei も延長しなくて済んだ
	// ret 1 : curve1 は延長しないとフィレットができない
	// ret 2 : curve2 は延長しないとフィレットができない
	// ret 3 : すべての curvei を延長しないとフィレットできない
	//
	// また、extension の値によって hint_param の戻り値としての意味が違う:
	// ret -1 : 無意味
	// ret 0 : curvei の　フィレット地点におけるパラメータ
	// ret 1 : hint1 が 延長直線の端点と一致するほうの端点のパラメータ、
	//         hint2 は curve2 のパラメータ
	// ret 2 : {ret 1 で 1 と 2 を置き換えよ}
	// ret 3 : hinti は 延長直線のパラメータ (無用)
	std::unique_ptr<MGCurve> fillet_aux(
		const MGCurve& curve1,
		double&        hint_param1,
		const MGCurve& curve2,
		double&        hint_param2,
		double         radius,
		int&           extension
	){
		assert(curve1.in_range(hint_param1));
		assert(curve2.in_range(hint_param2));
		assert(radius > 0.);  // 0 は許さない (0 のときは全然違う処理をする)

		// 1. 平面曲線かどうかを確認する
		MGPlane plane;
		if(!curve1.is_coplanar(curve2,plane)){
			// アウツ
			extension = -1;
			return std::unique_ptr<MGCurve>(nullptr);
		}
		const MGUnit_vector& normal = plane.normal();

		// 2. 元の曲線に手をいれないでフィレット部分が求まるか
		int ret = 0;
		
		std::unique_ptr<MGCurve> fil=
			generate_arc(curve1,hint_param1,curve2,hint_param2,normal,radius,ret);
		if(!ret){
			extension = 0;
			return fil;
		}
		double zero1 = 0;

		// 2.1 元の曲線を延長してトライしてみる
		// 直線で延長する
		std::unique_ptr<MGStraight> ext1(mgcalc::extension(curve1, hint_param1));

		// 2.2 ext1 vs curve2
		fil =generate_arc(*ext1,zero1,curve2,hint_param2,normal,radius,ret);
						// 半直線の閉じているほうのパラメータは必ずゼロ
		if(!ret){
			extension = 1;
			return fil;
		}
		assert(zero1 == 0.);

		double zero2 = 0.;
		
		// 2.3 curve1 vs ext2
		std::unique_ptr<MGStraight> ext2(mgcalc::extension(curve2, hint_param2));
		fil =generate_arc(curve1,hint_param1,*ext2,zero2,normal,radius,ret);
		// 半直線の閉じているほうのパラメータは必ずゼロ
		if(!ret){
			extension = 2;
			return fil;
		}
		assert(zero2 == 0.);

		// 2.4 ext1 vs ext2
		fil =generate_arc(*ext1,zero1,*ext2,zero2,normal,radius,ret);
		if(!ret){
			extension = 3;
			return fil;
		}
		extension = -1;
		return std::unique_ptr<MGCurve>(nullptr);
	}
} // namespace

namespace mgcalc{
	// フィレット部分の生成とトリム曲線の生成をする
	std::unique_ptr<MGCurveFilletInfo> fillet(
		const MGCurve& curve1,
		double&        hint_param1,
		const MGCurve& curve2,
		double&        hint_param2,
		double         radius
		){
		int extension = 0;
		std::unique_ptr<MGCurveFilletInfo> info(new MGCurveFilletInfo);
		info->m_fillet=fillet_aux(curve1,hint_param1,curve2,hint_param2,radius,extension);
		if(extension == -1){
			// failed.
			return std::unique_ptr<MGCurveFilletInfo>(nullptr);
		}

		assert(info->m_fillet.get());
		const MGCurve& fil = *info->m_fillet;

		switch(extension){
		case 0:
			// curve1, curve2 を通常の意味でトリムする
			{
				assert(curve1.in_range(hint_param1));
				assert(curve2.in_range(hint_param2));
				info->m_curve1.reset(curve1.clone());
				if(curve1.eval(hint_param1, 1) % fil.eval(fil.param_s(), 1) < 0){
					info->m_curve1->trim_start(hint_param1);
				}else{
					info->m_curve1->trim_end(hint_param1);
				}
				
				info->m_curve2.reset(curve2.clone());
				if(curve2.eval(hint_param2, 1) % fil.eval(fil.param_e(), 1) < 0){
					info->m_curve2->trim_end(hint_param2);
				}else{
					info->m_curve2->trim_start(hint_param2);
				}
			}
			break;
		case 1:
			// curve1 はトリム, curve2 は延長
			{
				assert(curve1.in_range(hint_param1));
				info->m_curve1.reset(curve1.clone());
				if(curve1.eval(hint_param1, 1) % fil.eval(fil.param_s(), 1) < 0){
					info->m_curve1->trim_start(hint_param1);
				}else{
					info->m_curve1->trim_end(hint_param1);
				}

				const MGPosition& end   = fil.end_point();
				std::unique_ptr<MGStraight> ext2(
					new MGStraight(curve2.eval(hint_param2), end)
					);
				info->m_curve2 = connect(curve2, *ext2);
			}
			break;
		case 2:
			// curve1 は延長、curve2 はトリム
			{
				const MGPosition& start   = fil.start_point();
				std::unique_ptr<MGStraight> ext1(
					new MGStraight(curve1.eval(hint_param1), start)
					);
				info->m_curve1 = connect(curve1, *ext1);

				assert(curve2.in_range(hint_param2));
				info->m_curve2.reset(curve2.clone());
				if(curve2.eval(hint_param2, 1) % fil.eval(fil.param_e(), 1) < 0){
					info->m_curve2->trim_end(hint_param2);
				}else{
					info->m_curve2->trim_start(hint_param2);
				}
			}
			break;
		case 3:
			// curve1, curve2 を延長
			{
				const MGPosition& start   = fil.start_point();
				std::unique_ptr<MGStraight> ext1(
					new MGStraight(curve1.eval(curve1.param_se(hint_param1)), start)
					);
				info->m_curve1 = connect(curve1, *ext1);

				const MGPosition& end   = fil.end_point();
				std::unique_ptr<MGStraight> ext2(
					new MGStraight(curve2.eval(curve2.param_se(hint_param2)), end)
					);
				info->m_curve2 = connect(curve2, *ext2);
			}
			break;
		}
		if(!(info->m_curve1.get())||!(info->m_curve2.get()))
			return std::unique_ptr<MGCurveFilletInfo>(nullptr);

		return info;
	}
	
	// フィレット部分だけを生成する
	std::unique_ptr<MGCurve> fillet_arc(
		const MGCurve& curve1,
		double&        hint_param1,
		const MGCurve& curve2,
		double&        hint_param2,
		double         radius
		){
		int extension = 0;
		return fillet_aux(curve1, hint_param1, curve2, hint_param2, radius, extension);
	}
} // mgcalc
