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

// 'Deviation between a curve and a surfce' classes member functions

#include "stdafx.h"
#include "mg/Curve.h"
#include "mg/FSurface.h"
#include "Calc/evalcs.h"

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

namespace mgcalc{

	// class MGEvalCSDevInfo

	// ヌルオブジェクトを生成
	MGEvalCSDevInfo::MGEvalCSDevInfo()
		 : m_null_distance(true),
		   m_null_param(true),
		   m_null_pos(true)
	{
		assert(is_distance_null());
		assert(is_param_null());
		assert(is_position_null());
	}

	// すべてのパラメータを与えてオブジェクトを生成
	MGEvalCSDevInfo::MGEvalCSDevInfo(
		double t,
		double u,
		double v
		)
		 : m_t(t),
		   m_u(u),
		   m_v(v),
		   m_null_distance(true),
		   m_null_param(false),
		   m_null_pos(false)
	{
		assert(is_distance_null());
		assert(!is_param_null());
		assert(is_position_null());
	}

	// すべてのパラメータを与えてオブジェクトを生成
	MGEvalCSDevInfo::MGEvalCSDevInfo(
		double t,
		const MGPosition& uv
		)
		 : m_t(t),
		   m_u(uv[0]),
		   m_v(uv[1]),
		   m_null_distance(true),
		   m_null_param(false),
		   m_null_pos(false)
	{
		assert(uv.sdim() >= 2);
		assert(is_distance_null());
		assert(!is_param_null());
		assert(is_position_null());
	}

	// ある地点における曲線と曲面の離れを返す
	double MGEvalCSDevInfo::distance() const{
		assert(!is_distance_null());
		assert(!is_param_null());
		//assert(!is_position_null());
		
		return m_distance;
	}

	// 位置をセットする
	void MGEvalCSDevInfo::evaluate(const MGCurve& curve, const MGFSurface& surf){
		m_null_distance = true;
		m_null_pos = true;
		if(!is_param_null()){
			m_pos_curve = curve.eval(m_t);
			m_pos_surf = surf.eval(m_u, m_v);
			m_null_pos = false;

			m_distance = m_pos_curve.distance(m_pos_surf);
			m_null_distance = false;
		}
	}

	// 曲線上の点におけるパラメータを返す
	double MGEvalCSDevInfo::param_curve() const{
		assert(!is_param_null());
		return m_t;
	}

	// 曲面上の点におけるパラメータを返す
	MGPosition* MGEvalCSDevInfo::param_surf() const{
		assert(!is_param_null());
		return new MGPosition(m_u, m_v);
	}

	// パラメータに対応する位置を返す
	const MGPosition& MGEvalCSDevInfo::position_curve() const{
		assert(!is_param_null());
		assert(!is_position_null());
		return m_pos_curve;
	}

	// パラメータに対応する位置を返す
	const MGPosition& MGEvalCSDevInfo::position_surf() const{
		assert(!is_param_null());
		assert(!is_position_null());
		return m_pos_surf;
	}

	// このオブジェクトを無効化する
	void MGEvalCSDevInfo::set_null(){
		m_null_distance = true;
		m_null_param = true;
		m_null_pos = true;

		assert(is_distance_null());
		assert(is_param_null());
		assert(is_position_null());
	}

	// 曲線と曲面のパラメータを設定する
	void MGEvalCSDevInfo::set_param(double t, const MGPosition& uv){
		assert(uv.sdim() >= 2);

		// パラメータを更新
		m_t = t;
		m_u = uv[0];
		m_v = uv[1];

		// フラグをリセット
		m_null_distance = true;
		m_null_param = false;
		m_null_pos = true;
	}
	
	void MGEvalCSDevInfo::set_param(double t, double u, double v){
		// パラメータを更新
		m_t = t;
		m_u = u;
		m_v = v;

		// フラグをリセット
		m_null_distance = true;
		m_null_param = false;
		m_null_pos = true;
	}

	// デバッグ用関数
	std::ostream& operator<<(std::ostream& os, const MGEvalCSDevInfo& info){
		os << "MGEvalCSDevInfo " << &info << std::endl
			<< "distance: ";
		if(info.is_distance_null()){
			os << "(null)";
		}else{
			os << info.m_distance;
		}
		os << std::endl << "parameter: ";
		if(info.is_param_null()){
			os << "(null)";
		}else{
			os << "t: " << info.m_t << " u: " << info.m_u << " v: " << info.m_v;
		}
		os << std::endl << "position: ";
		if(info.is_position_null()){
			os << "(null)";
		}else{
			os << "curve: " << info.m_pos_curve << " surf: " << info.m_pos_surf;
		}
		return os;
	}

	// class MGEvalCSInfoGroup

	// ヌルオブジェクトを生成
	MGEvalCSInfoGroup::MGEvalCSInfoGroup()
		 : m_curve(0),
		   m_surf(0)
	{
		assert(is_null());
	}

	// 所有権は移らない
	MGEvalCSInfoGroup::MGEvalCSInfoGroup(const MGCurve* curve,const MGFSurface* surf)
		 : m_curve(curve),
		   m_surf(surf)
	{
		assert(!is_null());
	}

	// 曲線を返す
	const MGCurve* MGEvalCSInfoGroup::curve() const{
		return m_curve;
	}

	// 曲線の細分、距離計測を行う
	void MGEvalCSInfoGroup::evaluate(double maxdev, int div){
		assert(!is_null());
		m_info.clear();

		const MGCurve& c = *m_curve;
		const MGFSurface& surf = *m_surf;

		const MGKnotVector& t = c.knot_vector();
		int k = c.order();
		int n = c.bdim();
		MGPosition cpos;
		MGPosition uv;

		// 1. 構成点位置から面への距離を求める
		for(int i = k-1; i < n; i++){
			MGInterval iv(t[i], t[i+1]);
			double dt = double(iv.length())/div;

			double prm = t[i];
			for(int j = 1; j <= div; j++){
				cpos = c.eval(prm);
				//uv = surf.closest(cpos);
				if(uv.is_null() || !surf.perp_guess(cpos, uv, uv)){
					uv = surf.closest(cpos);
				}

				// ここ気に入らないので後で修正
				std::unique_ptr<MGEvalCSDevInfo> info(new MGEvalCSDevInfo);
				info->set_param(prm, uv);
				info->m_pos_curve = cpos;
				info->m_pos_surf = surf.eval(uv);
				info->m_distance = info->m_pos_curve.distance(info->m_pos_surf);
				info->m_null_distance = false;
				info->m_null_param = false;
				info->m_null_pos = false;
				if(info->distance() < maxdev){
					m_info.emplace_back(info.release());
				}
				prm += dt;
			}
			cpos = c.eval(prm);
			if(uv.is_null() || !surf.perp_guess(cpos, uv, uv)){
				uv = surf.closest(cpos);
			}
				
			// ここ気に入らないので後で修正
			std::unique_ptr<MGEvalCSDevInfo> info(new MGEvalCSDevInfo);
			info->set_param(prm, uv);
			info->m_pos_curve = cpos;
			info->m_pos_surf = surf.eval(uv);
			info->m_distance = info->m_pos_curve.distance(info->m_pos_surf);
			info->m_null_distance = false;
			info->m_null_param = false;
			info->m_null_pos = false;
			if(info->distance() < maxdev){
				m_info.emplace_back(info.release());
			}
		}

		// 2. the closest point を追加する...
		// どうやって?
	}

	// このオブジェクトが有効かどうか
	bool MGEvalCSInfoGroup::is_null() const{
		return !m_curve || !m_surf;
	}

	// 評価する曲線を設定する
	void MGEvalCSInfoGroup::set_curve(const MGCurve* curve){
		m_curve = curve;
		m_info.clear();
	}

	// このオブジェクトを無効にする
	void MGEvalCSInfoGroup::set_null(){
		m_curve = 0;
		m_surf = 0;
		m_info.clear();
		
		assert(is_null());
	}

	// 評価に使う曲面を設定する
	void MGEvalCSInfoGroup::set_surf(const MGFSurface* surf){
		m_surf = surf;
		m_info.clear();
	}

	// 曲面を返す
	const MGFSurface* MGEvalCSInfoGroup::surf() const{
		return m_surf;
	}

	// デバッグ用
	std::ostream& operator<<(std::ostream& os, const MGEvalCSInfoGroup& info){
		os << "MGEvalCSInfoGroup " << &info << std::endl;
		std::copy(info.begin(), info.end(),
				std::ostream_iterator<MGEvalCSInfoGroup::MYELM>(os, "\n"));
		return os;
	}
	
} // namespace mgcalc
