/*
 *  psychlops_g_shape.cpp
 *  Psychlops Standard Library (Universal)
 *
 *  Last Modified 2005/10/05 by Kenchi HOSOKAWA
 *  (C) 2005 Kenchi HOSOKAWA, Kazushi MARUYA, Takao SATO
 */

#include "psychlops_g_fundamental.h"
#include "psychlops_g_color.h"
#include "psychlops_g_shape.h"

#include "psychlops_g_canvas.h"
#include "psychlops_g_image.h"

#include <stdlib.h>
#include <Math.h>


namespace Psychlops {

//	Shape& Shape::draw() { draw(*Drawable::prime); return *this; };


	Stroke::Stroke() {
		color = Color::white;
		width = 1;
		pattern = SOLID;
	}
	Stroke::Stroke(const Color col, const double wid, const Pattern pat) {
		color = col;
		width = wid;
		pattern = pat;
	}

	Stroke& Stroke::set(const Color col, const double wid, const Pattern pat) {
		color = col;
		width = wid;
		pattern = pat;
		return *this;
	}

	const Stroke Stroke::null_line(Color::null_color, 0, SOLID);
	const Stroke Stroke::hair_line(Color(0.75), 1, SOLID);

	Shape::Shape() : fill(Color::null_color), stroke(Stroke::null_line) {
	}
	Shape& Shape::draw_base(Drawable &target) {
		if(fill.getA()!=0) draw(fill, target);
		if(stroke.width!=0) draw(stroke, target);
		return *this;
	}
#if !defined(_MSC_VER)
	Shape& Shape::draw(Drawable &target) {
		return draw_base(target);
	}
#endif





////////	Rectangle ////////

	template <typename X> inline void Rectangle::correctInversion(X &l, X &t, X &r, X &b) {
		X tmp;
		if(l>r) { tmp=l; l=r; r=tmp; }
		if(t>b) { tmp=t; t=b; b=tmp; }
	}
	inline void Rectangle::setbypix(double l, double t, double r, double b) {
		left = l;
		top = t;
		right = r;
		bottom = b;
	}
	inline void Rectangle::setbypix(double width, double height) {
		setbypix(0.0, 0.0, width-1, height-1);
	}
	inline void Rectangle::set(double size) {
		setbypix(0.0, 0.0, size-1, size-1);
	}


//	Rectangle::Rectangle() : left(0.0), top(0.0), right(0.0), bottom(0.0), color(0.0, 0.0, 0.0) {
	Rectangle::Rectangle() {}
	Rectangle::Rectangle(double l, double t, double r, double b) {
		if(r-l<=0 || b-t<=0) throw Exception(typeid(*this), "SIZE ERROR", "Width and Height must be defined as positive value.");
		set(l, t, r, b);
	}
	Rectangle::Rectangle(double width, double height) {
		if(width<=0 || height<=0) throw Exception(typeid(*this), "SIZE ERROR", "Width and Height must be defined as positive value.");
		set(width, height);
	}

	Rectangle& Rectangle::set(const Rectangle &r) {
		set(r.left, r.top, r.right, r.bottom);
		return *this;
	}
	Rectangle& Rectangle::set(const Point &po1, const Point &po2) {
		double l=po1.x, t=po1.y, r=po2.x, b=po2.y;
		correctInversion(l,t,r,b);
		setbypix(l, t, r, b);
		return *this;
	}
	Rectangle& Rectangle::set(double l, double t, double r, double b) {
		correctInversion(l,t,r,b);
		setbypix(l, t, r, b);
		return *this;
	}
	Rectangle& Rectangle::set(double width, double height) {
		setbypix(width, height);
		return *this;
	}
	Rectangle Rectangle::dup() {
		return *this;
	}
	Rectangle& Rectangle::resize(double width, double height) {
		Point po = getCenter();
		setbypix(width, height);
		centering(po);
		return *this;
	}
	Rectangle& Rectangle::locate(const Point &p) {
		return locate(p.x, p.y);
	}
	Rectangle& Rectangle::locate(double x, double y) {
		double w = getWidth(), h = getHeight();
		set(x,y,x+w-1,y+h-1);
		return *this;
	}
	Rectangle& Rectangle::alignLeft(const double lef) {
		return move_to(lef, getTop(), getDatum().z);
	}
	Rectangle& Rectangle::alignTop(const double to_) {
		return move_to(getLeft(), to_, getDatum().z);
	}
	Rectangle& Rectangle::alignRight(const double rig) {
		return move_to(rig-getWidth(), getTop(), getDatum().z);
	}
	Rectangle& Rectangle::alignBottom(const double bot) {
		return move_to(getLeft(), bot-getHeight(), getDatum().z);
	}


	Rectangle& Rectangle::setDatum(const Point& p) {
		return move_to(p.x, p.y, p.z);
	}
	Rectangle& Rectangle::move_to(const double x, const double y, const double z) {
		//int Hoffset = x-getHcenter(), Voffset=getVcenter();
		//return shift(Hoffset, Voffset);
		double l = x,
			r = x + (getWidth()-1),
			t = y,
			b = y + (getHeight()-1);
		setbypix(l,t,r,b);
		return *this;
	}
	Rectangle& Rectangle::centering(const double x, const double y, const double z) {
		//int Hoffset = x-getHcenter(), Voffset=getVcenter();
		//return shift(Hoffset, Voffset);
		double l = x - (getWidth()-1)/2.0,
			r = x + (getWidth()-1)/2.0,
			t = y - (getHeight()-1)/2.0,
			b = y + (getHeight()-1)/2.0;
		setbypix(l,t,r,b);
		return *this;
	}
	Rectangle& Rectangle::centering(const Drawable& target) {
		return centering(target.getCenter());
	}
	Rectangle& Rectangle::centering(const Figure &fig) {
		return centering(fig.getDatum());
	}
	Rectangle& Rectangle::centering(const Point &po) {
		return centering(po.x, po.y);
	}
	Rectangle& Rectangle::shift(const double h, const double v, const double d) {
		set(	left + h,
				top + v,
				right + h,
				bottom + v);
		return *this;
	}

	Rectangle& Rectangle::draw(Drawable &target) {
		Shape::draw_base(target);
		return *this;
	}
	Rectangle& Rectangle::draw(const Color &col, Drawable &target) {
		Drawable::prime->rect(*this, col);
		return *this;
	}
	Rectangle& Rectangle::draw(const Stroke &strk, Drawable &target) {
		Drawable::prime->rect(*this, strk);
		return *this;
	}
		// obsolete
		Rectangle& Rectangle::display() {
			return draw();
		}
		Rectangle& Rectangle::display(const Color &col) {
			return draw(col);
		}


	void Rectangle::clipped_by(const Rectangle &source) {
		double t=top, l=left, b=bottom, r=right;
		if(top < source.top) { t = source.top; }
		if(left < source.left) { l = source.left; }
		if(bottom > source.bottom) { b = source.bottom; }
		if(right > source.right) { r = source.right; }
		try {
			set(l, t, r, b);
		} catch(...) {
			throw Exception(typeid(*this), "SIZE ERROR", "Cripped rectangle is null.");
		}
	}
	void Rectangle::clip(Rectangle &target) {
		double t=top, l=left, b=bottom, r=right;
		if(top < target.top) { t = target.top; }
		if(left < target.left) { l = target.left; }
		if(bottom > target.bottom) { b = target.bottom; }
		if(right > target.right) { r = target.right; }
		try {
			set(l, t, r, b);
		} catch(...) {
			throw Exception(typeid(*this), "SIZE ERROR", "Cripped rectangle is null.");
		}
	}
		// obsolete: misspelled
		void Rectangle::cripped(const Rectangle &source) { clipped_by(source); }
		void Rectangle::crip(Rectangle &target) { clip(target); }

	bool Rectangle::include(double x, double y) const {
		return (top <= y) && (left <= x) && (bottom >= y) && (right >= x);
	}
	bool Rectangle::include(const Point &p) const {
		return (top <= p.y) && (left <= p.x) && (bottom >= p.y) && (right >= p.x);
	}
	bool Rectangle::include(const Rectangle &rect) const {
		return (top <= rect.top) && (left <= rect.left) && (bottom >= rect.bottom) && (right >= rect.right);
	}


	void Rectangle::setColor(Color col) {
		fill = col;
	}

	double Rectangle::getWidth() const { return right>left ? right-left+1 : left-right+1; }
	double Rectangle::getHeight() const { return bottom>top ? bottom-top+1 : top-bottom+1; }
	const Point Rectangle::getDatum() const {
		//Point po(getHcenter(), getVcenter());
		Point po(left, top);
		return po;
	}
	const Point Rectangle::getCenter() const {
		Point po(getHcenter(), getVcenter());
		return po;
	}
	double Rectangle::getHcenter() const { return (double)( (right + left) / 2.0 ); }
	double Rectangle::getVcenter() const { return (double)( (bottom + top) / 2.0 ); }
	double Rectangle::getTop() const { return top; }
	double Rectangle::getLeft() const { return left; }
	double Rectangle::getBottom() const { return bottom; }
	double Rectangle::getRight() const { return right; }




	////////	Line ////////

	Line::Line() {}
	Line::Line(const double x1, const double y1, const double x2, const double y2) : end(Point(x2, y2)) {
		datum.x = x1;
		datum.y = y1;
		datum.z = 0;
	}
	Line::Line(const Point &dbegin, const Point &dend) : end(dend) {
		datum = dbegin;
	}
	Line& Line::set(const double x1, const double y1, const double x2, const double y2) {
		datum.x = x1;
		datum.y = y1;
		datum.z = 0;
		end.x = x2;
		end.y = y2;
		end.z = 0;
		return *this;
	}
	Line& Line::set(const Point &dbegin, const Point &dend) {
		datum = dbegin;
		end = dend;
		return *this;
	}

	Line& Line::setDatum(const Point &p) {
		Point vec = end-datum;
		datum = p;
		end = (Point)p + vec;
		return *this;
	}
	Line& Line::centering(const Point &p) {
		Point v = end-datum;
		datum.set(p.x-v.x, p.y-v.y, p.z-v.y);
		end.set(p.x+v.x, p.y+v.y, p.z+v.y);
		return *this;
	}
	Line& Line::draw(Drawable &target) {
		target.line(*this, stroke);
		return *this;
	}
	Line& Line::draw(const Color &col, Drawable &target) {
		target.line(*this, col);
		return *this;
	}
	Line& Line::draw(const Stroke &strk, Drawable &target) {
		target.line(*this, strk);
		return *this;
	}

	Point Line::getEnd() const {
		return (Point)end;
	}


	////////	Ellipse ////////

	Ellipse::Ellipse() : radius(0), v_radius(0) {}
	Ellipse::Ellipse(const double d_radius, const double d_v_radius, const Point& d_datum) { set(d_radius, d_v_radius, d_datum); }
	Ellipse::Ellipse(const Rectangle& rect) : radius(0), v_radius(0) { set(rect); }
	Ellipse::~Ellipse(){}
	Ellipse& Ellipse::set(const double d_radius, const double d_v_radius, const Point& d_datum) {
		datum = d_datum;
		radius = d_radius;
		v_radius = (d_v_radius<0) ? d_radius : d_v_radius;
		return *this;
	}
	Ellipse& Ellipse::set(const Rectangle& rect) {
		datum = rect.getDatum();
		radius = rect.getWidth();
		v_radius = rect.getHeight();
		return *this;
	}
	Ellipse& Ellipse::centering(const Point &p) {
		datum = p;
		return *this;
	}
	Ellipse& Ellipse::draw(Drawable &target) {
		Shape::draw_base(target);
		return *this;
	}
	Ellipse& Ellipse::draw(const Color &col, Drawable &target){
		target.ellipse(*this, col);
		return *this;
	}
	Ellipse& Ellipse::draw(const Stroke &strk, Drawable &target) {
		target.ellipse(*this, strk);
		return *this;
	}


	////////	Polygon ////////

	Polygon::Polygon() {}
	bool Polygon::empty() const {
		return vertices.empty();
	}
	Polygon& Polygon::append(const Point& p) {
		vertices.push_back(p);
		return *this;
	}
	Polygon& Polygon::append(const double x, const double y, const double z) { return append(Point(x,y,z)); }
	Polygon& Polygon::centering(const Point &p) {
		datum = p;
		return *this;
	}
	Polygon& Polygon::draw(Drawable& target) {
		Shape::draw_base(target);
		return *this;
	}
/*
	Polygon& Polygon::append(const Point& p, const Color& col) {
		Vertex v;
		v.point = p;
		v.color = col;
		vertices.push_back(v);
		return *this;
	}
*/
//	Polygon& Polygon::append(const double x, const double y, const double z, const Color& col) { return append(Point(x, y, z), col); }
//	Polygon& Polygon::append(const double x, const double y, const Color& col) { return add(x,y,0,col); }

	Polygon& Polygon::draw(const Color& col, Drawable& target) {
		target.polygon(*this, col);
		return *this;
	}
	Polygon& Polygon::draw(const Stroke& strk, Drawable &target) {
		target.polygon(*this, strk);
		return *this;
	}


	////////	PolyLine ////////

	PolyLine::PolyLine() {}
	bool PolyLine::empty() const {
		return vertices.empty();
	}
	PolyLine& PolyLine::append(const Point& p) {
		vertices.push_back(p);
		return *this;
	}
	PolyLine& PolyLine::append(const double x, const double y, const double z) { return append(Point(x,y,z)); }
	PolyLine& PolyLine::centering(const Point &p) {
		datum = p;
		return *this;
	}
	PolyLine& PolyLine::draw(Drawable& target) {
		Shape::draw_base(target);
		return *this;
	}
	PolyLine& PolyLine::draw(const Color& col, Drawable& target) {
		target.polyline(*this, col);
		return *this;
	}
	PolyLine& PolyLine::draw(const Stroke& strk, Drawable &target) {
		target.polyline(*this, strk);
		return *this;
	}


}	/*	<- namespace Psycholops 	*/
