/********************************************************************/
/* Copyright (c) 2019 System fugen G.K. and Yuzi Mizuno          */
/* All rights reserved.                                             */
/* ***************************************************** */

// chamfer.cpp
// Implementation for chamfer module
#include "stdafx.h"
#include "Calc/curve.h"
#include "Calc/chamfer.h"
#include "mg/Straight.h"
#include "mg/CCisects.h"

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

// ローカルな実装
namespace{
	using namespace mgcalc;

	/**
	 *  class ChamferInfo
	 *
	 *  片側曲線に関する、面取りに必要な情報を記憶するクラス
	 *  トリム計算に使用する
	 */
	class ChamferInfo{
	public:
		virtual ~ChamferInfo();

		// chamfer によってトリムする際の曲線を返す
		virtual std::unique_ptr<MGCurve> create_trimmed_curve(const ChamferParam* param) const = 0;
		virtual MGPosition trim_position(const ChamferParam* param) const = 0;

		bool is_start_nearer() const{ return m_start_is_nearer;}
		double trim_param() const{ return m_trim_param;}
		bool trim_start() const{ return m_trim_start;}

	protected:
		ChamferInfo(double trim_param, bool trim_start, bool start_is_nearer_to_chamfer_point)
			 : m_trim_param(trim_param),
			   m_trim_start(trim_start),
			   m_start_is_nearer(start_is_nearer_to_chamfer_point)
			{}

		double m_trim_param;      // トリムする曲線のトリム位置でのパラメータ
		bool   m_trim_start;      // トリムする曲線のどちら側をトリムするか
		bool   m_start_is_nearer; // chamfer point に近いのは始点か終点か
	};

	/**
	 *  class ChamferInfoOnCurve
	 *
	 *  面取りによるトリム位置が与えられた曲線上にある場合
	 *  トリム計算をするクラス
	 */
	class ChamferInfoOnCurve : public ChamferInfo{
	public:
		ChamferInfoOnCurve(double trim_param, bool trim_start, bool start_is_nearer_to_chamfer_point)
			 : ChamferInfo(trim_param, trim_start, start_is_nearer_to_chamfer_point){}

		// chamfer によってトリムする際の曲線を返す
		virtual std::unique_ptr<MGCurve> create_trimmed_curve(const ChamferParam* param) const;
		virtual MGPosition trim_position(const ChamferParam* param) const;
	};

	/**
	 *  class ChamferInfoOnExtension
	 *
	 *  面取りによるトリム位置が、与えられた曲線上にはないが
	 *  延長した直線上にある場合にトリム計算をするクラス
	 */
	class ChamferInfoOnExtension : public ChamferInfo{
	public:
		ChamferInfoOnExtension(
			double trim_param,
			MGStraight* extension,
			bool extend_start
			)
			 : ChamferInfo(trim_param, extend_start, extend_start),
			   m_extension(extension),
			   m_extend_start(extend_start){}

		// chamfer によってトリムする際の曲線を返す
		// 延長部分の一部とドッキングしている
		virtual std::unique_ptr<MGCurve> create_trimmed_curve(const ChamferParam* param) const;
		virtual MGPosition trim_position(const ChamferParam* param) const;

	private:
		std::unique_ptr<MGStraight>  m_extension;  // 延長部分の線分(半直線かも)
		bool                       m_extend_start; // オリジナル曲線のどちら側から延長したのか
	};

	/**
	 *  交線の計算後、トリム位置を曲線上に求める
	 */
	std::unique_ptr<ChamferInfo> seek_trim_position(const ChamferParam*, double chamfer_position);

	/** 
	 *  延長が必要なときバージョン
	 */
	std::unique_ptr<ChamferInfo> seek_trim_position(const ChamferParam*, const MGStraight* ext, double chamfer_position, bool extend_start);

	ChamferInfo::~ChamferInfo(){
	}

	// 実装

	std::unique_ptr<MGCurve> ChamferInfoOnCurve::create_trimmed_curve(const ChamferParam* param) const{
		std::unique_ptr<MGCurve> curve(param->curve()->clone());
		double t=trim_param();
		if(trim_start()){
			// 始点側を捨てる
			curve->trim_start(t);
		}else{
			// 終点側を捨てる
			curve->trim_end(t);
		}
		return curve;
	}

	MGPosition ChamferInfoOnCurve::trim_position(const ChamferParam* param) const{
		// オリジナル曲線上に存在する
		return param->curve()->eval(trim_param());
	}

	std::unique_ptr<MGCurve> ChamferInfoOnExtension::create_trimmed_curve(const ChamferParam* param) const{
		// オリジナルカーブの全体＋線分
		const MGCurve& curve = *param->curve();

		// 半直線のトリム
		double s = __min(0, trim_param());
		double e = __max(0, trim_param());
		std::unique_ptr<MGCurve> line(m_extension->part(s, e));

		// 接合したものを返す
		return connect(curve, *line);
	}

	MGPosition ChamferInfoOnExtension::trim_position(const ChamferParam*) const{
		// 延長部分上に存在する
		return m_extension->eval(trim_param());
	}

	std::unique_ptr<ChamferInfo> seek_trim_position(const ChamferParam* param, double chamfer_position){
		assert(!param->is_null());
		assert(param->curve()->in_range(chamfer_position));

		// alias
		const MGCurve& curve = *param->curve();

		double distance = param->distance();
		double hint = param->hint();

		// ピック位置が chamfer position から見て曲線の始点方向であるかどうか
		bool to_start = (hint <= chamfer_position);
		double len = to_start
			? curve.length(curve.param_s(), chamfer_position)
				: curve.length(chamfer_position, curve.param_e());

		if(distance > len){
			// 面取り指定距離が長すぎてアウツ
			return std::unique_ptr<ChamferInfo>(nullptr);
		}

		double trim_position = curve.length_param(chamfer_position, (to_start ? -distance : distance));
		assert(curve.in_range(trim_position));

		// OK
		return std::unique_ptr<ChamferInfo>(new ChamferInfoOnCurve(trim_position, !to_start, !to_start));
	}

	std::unique_ptr<ChamferInfo> seek_trim_position(
		const ChamferParam* param,
		const MGStraight*   ext,
		double              chamfer_position, // ext 上のパラメータである
		bool                extend_start  // オリジナル曲線の延長側が始点側かどうか
		){
		assert(!param->is_null());
		assert(ext->in_range(chamfer_position));

		// alias
		const MGCurve& curve = *param->curve();

		double distance = param->distance();
		double hint = param->hint();

		double lenorg = curve.length();
		double lenext = abs(ext->length(0, chamfer_position));
		if(distance > lenorg + lenext){
			// 面取り距離が長すぎてアウツ
			return std::unique_ptr<ChamferInfo>(nullptr);
		}

		// 面取り点を計算
		if(distance < lenext){
			// 直線上に面取り点がある
			double trim_param = ext->length_param(chamfer_position, (ext->param_e() == 0.) ? distance : -distance);

			return std::unique_ptr<ChamferInfo>(
				new ChamferInfoOnExtension(trim_param, new MGStraight(*ext), extend_start)
				);
		}
		else{
			double start_or_end = extend_start ? curve.param_s() : curve.param_e();
			// 元の曲線上に面取り点がある
			double trim_param = curve.length_param(
				start_or_end,
				(extend_start ? (distance - lenext) : (lenext - distance))
				);
			return std::unique_ptr<ChamferInfo>(
				new ChamferInfoOnCurve(trim_param, extend_start, extend_start)
				);
		}
	}
} // namespace

namespace mgcalc{
	/**
	 *  class ChamferResult::Private
	 *
	 *  class ChamferResult のプライベートな実装を担当
	 */
	class ChamferResult::Private{
	public:
		Private(const ChamferParam* param1, const ChamferParam* param2)
			: m_param1(param1), m_param2(param2){}

		bool create_info();
		bool is_null() const;

		bool seek_with_extension(
			const MGStraight* ext1, bool extend_start1,
			const MGStraight* ext2, bool extend_start2);

		const ChamferParam*         m_param1;
		const ChamferParam*         m_param2;
		std::unique_ptr<ChamferInfo>  m_info1;
		std::unique_ptr<ChamferInfo>  m_info2;
		MGPosition                  m_point;
	};

	// class ChamferParam

	ChamferResult::~ChamferResult(){
		// (空でも)デストラクタを定義してやらないとコンパイルが通らなかった
	}

	bool ChamferParam::set(const MGCurve* curve, double distance, double hint){
		if(!curve) return false;
		if(distance < 0) return false;
		if(!curve->in_range(hint)) return false;

		m_curve = curve;
		m_distance = distance;
		m_hint = hint;
		return true;
	}

	// class ChamferResult

	ChamferResult::ChamferResult(
		const ChamferParam* param1, 
		const ChamferParam* param2
		) : m_impl(new Private(param1, param2)){
	}

	bool ChamferResult::chamfer(){
		if(m_impl->is_null()){
			return false;
		}
		// ChamferInfo メンバをセットする
		if(!m_impl->create_info()){
			return false;
		}
		return true;
	}

	MGCurve* ChamferResult::chamfer_line() const{
		if(!m_impl->m_info1.get() || !m_impl->m_info2.get()){
			return 0;
		}
		if(m_impl->m_param1->distance() == 0 && m_impl->m_param2->distance() == 0){
			return 0;
		}
		// 素直に線分を生成する
		MGPosition start = m_impl->m_info1->trim_position(m_impl->m_param1);
		MGPosition end   = m_impl->m_info2->trim_position(m_impl->m_param2);
		return new MGStraight(end, start);
	}

	std::unique_ptr<MGCurve> ChamferResult::trimmed_curve(int i) const{
		const ChamferInfo* info = 0;
		const ChamferParam* param = 0;
		switch(i){
		case 1:
			info = m_impl->m_info1.get();
			param = m_impl->m_param1;
			break;
		case 2:
			info = m_impl->m_info2.get();
			param = m_impl->m_param2;
			break;
		}
		return info->create_trimmed_curve(param);
	}

	// class ChamferResult::Private

	bool ChamferResult::Private::seek_with_extension(
		const MGStraight* ext1, bool extend_start1,
		const MGStraight* ext2, bool extend_start2)
	{
		// alias
		const MGCurve& curve1 = *m_param1->curve();
		const MGCurve& curve2 = *m_param2->curve();
		const double hint1 = m_param1->hint();
		const double hint2 = m_param2->hint();

		// 延長位置を記憶しておく
		MGPosition posFrom1, posFrom2;
		if(ext1){
			posFrom1 = extend_start1 ? curve1.start_point() : curve1.end_point();
		}
		if(ext2){
			posFrom2 = extend_start2 ? curve2.start_point() : curve2.end_point();
		}

		double dLenPrev = -1;

		// curve1 そのものまたはその延長部と
		// curve2 そのものまたはその延長部との交線を計算する
		MGCCisects ls;
		if(ext1 && !ext2){
			ls = ext1->isect(curve2);
		}
		else if(!ext1 && ext2){
			ls = curve1.isect(*ext2);
		}
		else if(ext1 && ext2){
			ls = ext1->isect(*ext2);
		}
		else{
			ls = curve1.isect(curve2);
		}

		MGCCisects::iterator first = ls.begin(), last = ls.end();
		for(; first != last; ++first){
			std::unique_ptr<ChamferInfo> info1, info2;
			const MGCCisect& is = isectCast<const MGCCisect>(first);

			if(ext1){
				// 延長部から求める
				info1 = seek_trim_position(m_param1, ext1, is.param1(), extend_start1);
			}
			else{
				// 元曲線から求める
				info1 = seek_trim_position(m_param1, is.param1());
			}
			if(!info1.get()){
				continue;
			}

			if(ext2){
				// 延長部から求める
				info2 = seek_trim_position(m_param2, ext2, is.param2(), extend_start2);
			}
			else{
				// 元曲線から求める
				info2 = seek_trim_position(m_param2, is.param2());
			}
			if(!info2.get()){
				continue;
			}

			if(ls.size() == 1){
				// 候補点更新
				m_info1 = std::move(info1);
				m_info2 = std::move(info2);
				m_point = is.point();
				return true;
			}
			else{
				if(dLenPrev < 0){
					double dLenCur = 0;
					if(ext1){
						dLenCur = is.point().distance(posFrom1);
					}
					else{
						dLenCur = fabs(curve1.length(hint1, is.param1()));
					}
					
					if(ext2){
						dLenCur += is.point().distance(posFrom2);
					}
					else{
						dLenCur = fabs(curve2.length(hint2, is.param2()));
					}
					dLenPrev = dLenCur;

					// 候補点更新
					m_info1 = std::move(info1);
					m_info2 = std::move(info2);
					m_point = is.point();
				}
				else{
					double dLenCur = 0;
					if(ext1){
						dLenCur = is.point().distance(posFrom1);
					}
					else{
						dLenCur = fabs(curve1.length(hint1, is.param1()));
					}
					
					if(ext2){
						dLenCur += is.point().distance(posFrom2);
					}
					else{
						dLenCur = fabs(curve2.length(hint2, is.param2()));
					}

					if(dLenCur < dLenPrev){
						dLenPrev = dLenCur;

						// 候補点更新
						m_info1 = std::move(info1);
						m_info2 = std::move(info2);
						m_point = is.point();
					}
				}
			}
		}
		// 求められた。
		if(m_info1.get() && m_info2.get()){
			return true;
		}

		return false;
	}

	// m_info1, m_info2 メンバを生成する
	bool ChamferResult::Private::create_info()
	{
		const MGCurve& curve1 = *m_param1->curve();
		const MGCurve& curve2 = *m_param2->curve();
		const double hint1 = m_param1->hint();
		const double hint2 = m_param2->hint();

		// curve1 の始点側を延長するならば true
		const bool extend_start1 = (curve1.param_se(hint1) == curve1.param_s());
		// curve2 の始点側を延長するならば true
		const bool extend_start2 = (curve2.param_se(hint2) == curve2.param_s());

		// 1. 素のままで交点計算ができるか?
		if(seek_with_extension(0, extend_start1, 0, extend_start2)){
			return true;
		}

		// 2. どちらかを延長しなければならない
		// まずは curve1 だけを延長してみる
		const std::unique_ptr<MGStraight> ext1(mgcalc::extension(curve1, hint1));
		if(seek_with_extension(ext1.get(), extend_start1, 0, extend_start2)){
			return true;
		}

		// 3. curve2 側を延長して、オリジナルの curve1 と交差テストする。
		const std::unique_ptr<MGStraight> ext2(mgcalc::extension(curve2, hint2));
		if(seek_with_extension(0, extend_start1, ext2.get(), extend_start2)){
			return true;
		}

		// 4. 両方の延長部分同士の交点に面取り点があるケース確定
		return seek_with_extension(ext1.get(), extend_start1, ext2.get(), extend_start2);
	}

	bool ChamferResult::Private::is_null() const{
		return (   !m_param1
				|| !m_param2
				|| m_param1->is_null()
				|| m_param2->is_null()
				);
	}
} // mgcalc
