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

// mgcalc_rect.cpp
#include "stdafx.h"
#include "mg/CompositeCurve.h"
#include "mg/Ellipse.h"
#include "mg/Straight.h"
#include "Calc/line.h"
#include "Calc/rect.h"
#include "Calc/mgcalc.h"

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

// 実装の詳細
namespace{
	// (0, pi/2)
	const MGInterval range0toHALFpi(0., mgHALFPAI);
	const MGInterval range0toPi(0., mgPAI);

	// MGPosition 同士の距離の比較用
	struct Distance : public std::binary_function<MGPosition, MGPosition, bool>{
		Distance(const MGPosition& p):m_p(p){}

		bool operator()(const MGPosition& p1, const MGPosition& p2) const{
			return m_p.distance(p1) < m_p.distance(p2);
		}

		const MGPosition& m_p;
	};

	// ラウンドコーナー仕上げ部分
	std::unique_ptr<MGCurve> create_round_rect_finish(
		const std::vector<MGPosition>& pos,  // all of corner positions
		double                         r     // fillet radius
	){
		MGVector P03=pos[3]-pos[0];
		double P03len=P03.len();
		r=__min(P03len*.5,r);

		MGVector P01=pos[1]-pos[0];
		double P01len=P01.len();
		r=__min(P01len*.5,r);
		if(MGAZero(r))
			return mgcalc::create_closed_polyline(pos);

		double diam=r*2.;
		bool P03isDiameter=MGAEqual(P03len,diam);
		bool P01isDiameter=MGAEqual(P01len,diam);

		MGVector ru = P03.normalize()*r;
		MGVector rv = P01.normalize()*r;
		MGVector ruPrv=ru+rv;
		MGVector ruMrv=ru-rv;

		MGPosition P0C=pos[0]+ruPrv;
		MGPosition P1C=pos[1]+ruMrv;
		MGPosition P2C=pos[2]-ruPrv;
		MGPosition P3C=pos[3]-ruMrv;

		MGPosition P0Prv=pos[0]+rv, P1Mrv=pos[1]-rv;
		MGPosition P1Pru=pos[1]+ru, P2Mru=pos[2]-ru;
		MGPosition P2Mrv=pos[2]-rv, P3Prv=pos[3]+rv;
		MGPosition P3Mru=pos[3]-ru, P0Pru=pos[0]+ru;

		// 5. 合成曲線を生成
		std::unique_ptr<MGCompositeCurve> roundrect(new MGCompositeCurve);
		if(P03isDiameter&&P01isDiameter){
			MGPosition C=(pos[0]+pos[2])*.5;
			return std::unique_ptr<MGCurve>(new MGEllipse(C,-rv,-ru,mgZERO_TO_DBLPAI));
		}else if(P03isDiameter){
			roundrect->connect_to_end(new MGEllipse(P0C,ru,-rv,range0toPi));
			roundrect->connect_to_end(new MGStraight(P1Mrv, P0Prv));
			roundrect->connect_to_end(new MGEllipse(P1C,-ru,rv,range0toPi));
			roundrect->connect_to_end(new MGStraight(P3Prv, P2Mrv));
		}else if(P01isDiameter){
			roundrect->connect_to_end(new MGEllipse(P0C,-rv,-ru, range0toPi));
			roundrect->connect_to_end(new MGStraight(P2Mru, P1Pru));
			roundrect->connect_to_end(new MGEllipse(P3C,rv,ru, range0toPi));
			roundrect->connect_to_end(new MGStraight(P0Pru, P3Mru));
		}else{
			roundrect->connect_to_end(new MGEllipse(P0C,-rv,-ru,range0toHALFpi));
			roundrect->connect_to_end(new MGStraight(P1Mrv, P0Prv));
			roundrect->connect_to_end(new MGEllipse(P1C,-ru,rv,range0toHALFpi));
			roundrect->connect_to_end(new MGStraight(P2Mru, P1Pru));
			roundrect->connect_to_end(new MGEllipse(P2C,rv,ru,range0toHALFpi));
			roundrect->connect_to_end(new MGStraight(P3Prv, P2Mrv));
			roundrect->connect_to_end(new MGEllipse(P3C,ru,-rv,range0toHALFpi));
			roundrect->connect_to_end(new MGStraight(P0Pru, P3Mru));
		}
		return std::move(roundrect);
	}
}

namespace mgcalc{

// MGRect

MGRect::MGRect(){}

MGRect::MGRect(const MGRect& other):m_plane(other.m_plane){
	if(other.curve()){
		m_polyline.reset(other.m_polyline->clone());
	}
}

MGPosition MGRect::corner(int i) const{
	assert(valid() && 0<=i && i<=3);
	return m_polyline->coef(i);
}

bool MGRect::create_from_corner(
	const MGPlane&    plane,
	const MGPosition& corner1,
	const MGPosition& corner2
){
	if(corner1 == corner2){
		return false;
	}
	assert(!plane.is_null());

	// 1. 矩形のすべての頂点を計算する
	m_plane=plane;
	
	// 2. corner2 を今の平面に面直投影する
	MGPosition c2 = plane.eval(plane.closest(corner2));

	// 3. 残りの頂点を求める
	MGVector vdir = (c2 - corner1).project(plane.v_deriv());

	std::vector<MGPosition> pos;pos.reserve(4);
	pos.push_back(corner1);
	pos.push_back(corner1 + vdir);
	pos.push_back(c2);
	pos.push_back(c2 - vdir);

	// 4. ポリライン化して終了
	m_polyline = mgcalc::create_closed_polyline(pos);
	return m_polyline.get() != 0;
}

bool MGRect::create_from_center(
	const MGPlane&    plane,
	const MGPosition& center,
	const MGPosition& corner
){
	if(center == corner){
		return false;
	}
	assert(!plane.is_null());
	m_plane=plane;

	// 1. corner を今の平面に面直投影する
	MGPosition cn = m_plane.eval(m_plane.closest(corner));

	// 2. 残りの頂点を求める
	MGVector udir = (cn - center).project(m_plane.u_deriv()) * 2;
	MGVector vdir = (cn - center).project(m_plane.v_deriv()) * 2;

	std::vector<MGPosition> pos;pos.reserve(4);
	pos.push_back(cn);
	cn -= vdir;
	pos.push_back(cn);
	cn -= udir;
	pos.push_back(cn);
	cn += vdir;
	pos.push_back(cn);

	// 4. ポリライン化して終了
	m_polyline = mgcalc::create_closed_polyline(pos);
	return m_polyline.get() != 0;
}

bool MGRect::create_from_edge(
	const MGPosition& es,
	const MGPosition& ee,
	const MGPosition& p
){
	if(es.is_null() || ee.is_null() || p.is_null()){
		return false;
	}
	// 対辺まで届くエッジに対し垂直な方向のベクトル
	MGVector u = ee - es;
	MGVector v = p - es;
	v -= v.project(u);

	// 対辺のエッジ頂点を求める
	std::vector<MGPosition> pos;pos.reserve(4);
	pos.push_back(es);
	pos.push_back(ee);
	pos.push_back(ee + v);
	pos.push_back(es + v);

	// 平面をセット
	m_plane=MGPlane(u,v,MGPosition((pos[0] + pos[2]) * .5));

	// ポリライン化
	m_polyline = mgcalc::create_closed_polyline(pos);
	return m_polyline.get() != 0;
}

const MGCurve* MGRect::curve() const{
	return m_polyline.get();
}

void MGRect::draw(mgSysGL& sgl) const{
	if(m_polyline.get()){
		m_polyline->drawWire(sgl);
	}
}

const MGPlane* MGRect::plane() const{
	return &m_plane;
}

MGCurve* MGRect::release(){
	return m_polyline.release();
}

bool MGRect::valid() const{
	return m_polyline.get() != nullptr;
}

std::ostream& operator<<(std::ostream& os, const MGRect& rect){
	os << "MGRect ";
	if(rect.m_polyline.get()){
		os << *rect.m_polyline;
	}
	return os;
}

// MGRoundRect

MGRoundRect::MGRoundRect(){}

MGRoundRect::MGRoundRect(const MGRoundRect& other){
	if(other.curve()){
		m_roundRect.reset(other.m_roundRect->clone());
	}
}

const MGCurve* MGRoundRect::curve() const{
	return m_roundRect.get();
}

/**
 *  @brief  2コーナーを与えて角の丸い矩形カーブを生成する
 *  @param  rect     矩形曲線
 *  @param  through  角の円弧上の点
 *
 *  もし @a corner2 が @a corner1 を通り @a plane に平行な平面に
 *  乗っていない場合、@a corner2 は平行な平面に面直投影され、
 *  その点が @a corner1 の対角線上の向かいの頂点となる。
 *
 *  もし @a through が角の丸みを指定できない場合、この関数は
 *  丸みの半径を min(矩形の辺の長さ) * 0.5 として計算する。
 */
bool MGRoundRect::create(
	const MGRect*     rect,
	const MGPosition& th
){
	if(th.is_null() || !rect || !rect->valid()){
		return false;
	}
	
	// 1. コーナー準備
	std::vector<MGPosition> pos;
	pos.reserve(4);
	for(int k = 0; k < 4; k++){
		pos.push_back(rect->corner(k));
	}
	
	// 2. through がどの頂点に一番近いか
	const MGPlane& pl = *rect->plane();
	MGPosition through = pl.eval(pl.closest(th));

	std::vector<MGPosition>::iterator j,i=
		std::min_element(pos.begin(), pos.end(), Distance(through));
	assert(i != pos.end());
	j=i;
	const MGPosition& P=*i;
	const MGPosition& Ppre=(i==pos.begin() ? pos.back() :*( --j));
	const MGPosition& Paft=(++i==pos.end() ? pos.front() :*i);
	MGVector Vaft=Paft-P;
	MGVector Vpre=Ppre-P;
	MGVector VaftUnit=Vaft.normalize();
	MGVector VpreUnit=Vpre.normalize();
	MGVector Vthru=through-P;
	double a=fabs(Vthru%VpreUnit), d=fabs(Vthru%VaftUnit);
	double radius = a+d+sqrt(2.*a*d);
	
	// 3. 円弧部分を計算
	m_roundRect = create_round_rect_finish(pos, radius);
	return true;
}

bool MGRoundRect::create(
	const MGRect* rect,
	double        radius
){
	if(!rect || !rect->valid()){
		return false;
	}
	
	// 1. コーナー準備
	std::vector<MGPosition> pos;
	pos.reserve(4);
	for(int i = 0; i < 4; i++){
		pos.push_back(rect->corner(i));
	}

	// 2. 円弧部分を計算
	m_roundRect = create_round_rect_finish(pos, radius);
	return true;
}

void MGRoundRect::draw(mgSysGL& sgl) const{
	if(m_roundRect.get()){
		m_roundRect->drawWire(sgl);
	}
}

MGCurve* MGRoundRect::release(){
	return m_roundRect.release();
}

bool MGRoundRect::valid() const{
	return m_roundRect.get()!=nullptr;
}

} // namespace mgcalc
