#include "rays/ruby/painter.h"


#include <vector>
#include "rays/ruby/point.h"
#include "rays/ruby/bounds.h"
#include "rays/ruby/color.h"
#include "rays/ruby/matrix.h"
#include "rays/ruby/image.h"
#include "rays/ruby/font.h"
#include "rays/ruby/shader.h"
#include "defs.h"


RUCY_DEFINE_VALUE_FROM_TO(Rays::Painter)

#define THIS  to<Rays::Painter*>(self)

#define CHECK RUCY_CHECK_OBJECT(Rays::Painter, self)


static
VALUE alloc(VALUE klass)
{
	return new_type<Rays::Painter>(klass);
}

static
VALUE canvas(VALUE self)
{
	CHECK;
	check_arg_count(__FILE__, __LINE__, "Painter#canvas", argc, 4, 5, 6, 7);

	switch (argc)
	{
		case 4:
			THIS->canvas(
				to<coord>(argv[0]),
				to<coord>(argv[1]),
				to<coord>(argv[2]),
				to<coord>(argv[3]));
			break;

		case 5:
			THIS->canvas(
				to<coord>(argv[0]),
				to<coord>(argv[1]),
				to<coord>(argv[2]),
				to<coord>(argv[3]),
				to<float>(argv[4]));
			break;

		case 6:
			THIS->canvas(
				to<coord>(argv[0]),
				to<coord>(argv[1]),
				to<coord>(argv[2]),
				to<coord>(argv[3]),
				to<coord>(argv[4]),
				to<coord>(argv[5]));
			break;

		case 7:
			THIS->canvas(
				to<coord>(argv[0]),
				to<coord>(argv[1]),
				to<coord>(argv[2]),
				to<coord>(argv[3]),
				to<coord>(argv[4]),
				to<coord>(argv[5]),
				to<float>(argv[6]));
			break;
	}

	return self;
}

static
VALUE bounds(VALUE self)
{
	CHECK;
	return value(THIS->bounds());
}

static
VALUE pixel_density(VALUE self)
{
	CHECK;
	return value(THIS->pixel_density());
}


static
VALUE begin_paint(VALUE self)
{
	CHECK;
	THIS->begin();
	return self;
}

static
VALUE end_paint(VALUE self)
{
	CHECK;
	THIS->end();
	return self;
}

static
VALUE clear(VALUE self)
{
	CHECK;
	THIS->clear();
}

static
VALUE polygon(VALUE self, VALUE poly)
{
	CHECK;

	THIS->polygon(to<Rays::Polygon&>(poly));
	return self;
}

static
VALUE line(VALUE self, VALUE args, VALUE loop)
{
	CHECK;

	std::vector<Rays::Point> points;
	get_line_args(&points, args.size(), args.as_array());

	THIS->line(&points[0], points.size(), loop);
	return self;
}

static
VALUE polyline(VALUE self, VALUE poly)
{
	CHECK;

	THIS->line(to<Rays::Polyline&>(poly));
	return self;
}

static
VALUE rect(VALUE self, VALUE args, VALUE round, VALUE lefttop, VALUE righttop, VALUE leftbottom, VALUE rightbottom)
{
	CHECK;

	coord x, y, w, h, lt, rt, lb, rb;
	uint _;
	get_rect_args(
		&x, &y, &w, &h, &lt, &rt, &lb, &rb, &_,
		args.size(), args.as_array(),
		round, lefttop, righttop, leftbottom, rightbottom, nil());

	THIS->rect(x, y, w, h, lt, rt, lb, rb);
	return self;
}

static
VALUE ellipse(VALUE self, VALUE args, VALUE center, VALUE radius, VALUE hole, VALUE angle_from, VALUE angle_to)
{
	CHECK;

	coord x, y, w, h;
	Rays::Point hole_size;
	float from, to_;
	uint _;
	get_ellipse_args(
		&x, &y, &w, &h, &hole_size, &from, &to_, &_,
		args.size(), args.as_array(),
		center, radius, hole, angle_from, angle_to, nil());

	THIS->ellipse(x, y, w, h, hole_size, from, to_);
	return self;
}

static
VALUE curve(VALUE self, VALUE args, VALUE loop)
{
	CHECK;

	if (args.empty())
		argument_error(__FILE__, __LINE__);

	std::vector<Rays::Point> points;
	get_line_args(&points, args.size(), args.as_array());

	THIS->curve(&points[0], points.size(), loop);
	return self;
}

static
VALUE bezier(VALUE self, VALUE args, VALUE loop)
{
	CHECK;

	if (args.empty())
		argument_error(__FILE__, __LINE__);

	std::vector<Rays::Point> points;
	get_line_args(&points, args.size(), args.as_array());

	THIS->bezier(&points[0], points.size(), loop);
	return self;
}

static
VALUE image(VALUE self)
{
	CHECK;
	check_arg_count(__FILE__, __LINE__, "Painter#image", argc, 1, 3, 5, 7, 9);

	const Rays::Image* image = to<Rays::Image*>(argv[0]);
	if (!image)
		argument_error(__FILE__, __LINE__, "%s is not an image.", argv[0].inspect().c_str());

	if (argc == 1)
		THIS->image(*image);
	else if (argc == 3)
	{
		coord x = to<coord>(argv[1]), y = to<coord>(argv[2]);
		THIS->image(*image, x, y);
	}
	else if (argc == 5)
	{
		coord x = to<coord>(argv[1]), w = to<coord>(argv[3]);
		coord y = to<coord>(argv[2]), h = to<coord>(argv[4]);
		THIS->image(*image, x, y, w, h);
	}
	else if (argc == 7)
	{
		coord sx = to<coord>(argv[1]), dx = to<coord>(argv[5]);
		coord sy = to<coord>(argv[2]), dy = to<coord>(argv[6]);
		coord sw = to<coord>(argv[3]);
		coord sh = to<coord>(argv[4]);
		THIS->image(*image, sx, sy, sw, sh, dx, dy);
	}
	else if (argc == 9)
	{
		coord sx = to<coord>(argv[1]), dx = to<coord>(argv[5]);
		coord sy = to<coord>(argv[2]), dy = to<coord>(argv[6]);
		coord sw = to<coord>(argv[3]), dw = to<coord>(argv[7]);
		coord sh = to<coord>(argv[4]), dh = to<coord>(argv[8]);
		THIS->image(*image, sx, sy, sw, sh, dx, dy, dw, dh);
	}

	return self;
}

static
VALUE text(VALUE self)
{
	CHECK;
	check_arg_count(__FILE__, __LINE__, "Painter#text", argc, 1, 3, 5);

	if (argc == 1)
		THIS->text(argv[0].c_str());
	else if (argc == 3)
	{
		coord x = to<coord>(argv[1]), y = to<coord>(argv[2]);
		THIS->text(argv[0].c_str(), x, y);
	}
	else if (argc == 5)
	{
		coord x = to<coord>(argv[1]), w = to<coord>(argv[3]);
		coord y = to<coord>(argv[2]), h = to<coord>(argv[4]);
		THIS->text(argv[0].c_str(), x, y, w, h);
	}

	return self;
}


static
VALUE set_background(VALUE self)
{
	CHECK;
	THIS->set_background(to<Rays::Color>(argc, argv));
	return self;
}

static
VALUE get_background(VALUE self)
{
	CHECK;
	return value(THIS->background());
}

static
VALUE no_background(VALUE self)
{
	CHECK;
	THIS->no_background();
	return self;
}

static
VALUE set_fill(VALUE self)
{
	CHECK;
	THIS->set_fill(to<Rays::Color>(argc, argv));
	return self;
}

static
VALUE get_fill(VALUE self)
{
	CHECK;
	return value(THIS->fill());
}

static
VALUE no_fill(VALUE self)
{
	CHECK;
	THIS->no_fill();
	return self;
}

static
VALUE set_stroke(VALUE self)
{
	CHECK;
	THIS->set_stroke(to<Rays::Color>(argc, argv));
	return self;
}

static
VALUE get_stroke(VALUE self)
{
	CHECK;
	return value(THIS->stroke());
}

static
VALUE no_stroke(VALUE self)
{
	CHECK;
	THIS->no_stroke();
	return self;
}

static
VALUE set_stroke_width(VALUE self, VALUE width)
{
	CHECK;
	THIS->set_stroke_width(to<coord>(width));
	return self;
}

static
VALUE get_stroke_width(VALUE self)
{
	CHECK;
	return value(THIS->stroke_width());
}

static
VALUE set_stroke_cap(VALUE self, VALUE cap)
{
	CHECK;
	THIS->set_stroke_cap(to<Rays::CapType>(cap));
	return self;
}

static
VALUE get_stroke_cap(VALUE self)
{
	CHECK;
	return value(THIS->stroke_cap());
}

static
VALUE set_stroke_join(VALUE self, VALUE join)
{
	CHECK;
	THIS->set_stroke_join(to<Rays::JoinType>(join));
	return self;
}

static
VALUE get_stroke_join(VALUE self)
{
	CHECK;
	return value(THIS->stroke_join());
}

static
VALUE set_miter_limit(VALUE self, VALUE limit)
{
	CHECK;
	THIS->set_miter_limit(to<coord>(limit));
	return self;
}

static
VALUE get_miter_limit(VALUE self)
{
	CHECK;
	return value(THIS->miter_limit());
}

static
VALUE set_nsegment(VALUE self, VALUE nsegment)
{
	CHECK;
	THIS->set_nsegment(nsegment ? to<int>(nsegment) : 0);
	return self;
}

static
VALUE get_nsegment(VALUE self)
{
	CHECK;
	return value(THIS->nsegment());
}

static
VALUE set_blend_mode(VALUE self, VALUE mode)
{
	CHECK;
	THIS->set_blend_mode(to<Rays::BlendMode>(mode));
	return self;
}

static
VALUE get_blend_mode(VALUE self)
{
	CHECK;
	return value(THIS->blend_mode());
}

static
VALUE set_clip(VALUE self)
{
	CHECK;
	THIS->set_clip(to<Rays::Bounds>(argc, argv));
	return self;
}

static
VALUE get_clip(VALUE self)
{
	CHECK;
	return value(THIS->clip());
}

static
VALUE no_clip(VALUE self)
{
	CHECK;
	THIS->no_clip();
	return self;
}

static
VALUE set_font(VALUE self)
{
	CHECK;
	check_arg_count(__FILE__, __LINE__, "Painter#set_font", argc, 0, 1, 2);

	THIS->set_font(to<Rays::Font>(argc, argv));
	return self;
}

static
VALUE get_font(VALUE self)
{
	CHECK;
	return value(THIS->font());
}

static
VALUE set_shader(VALUE self)
{
	CHECK;
	check_arg_count(__FILE__, __LINE__, "Painter#set_shader", argc, 1);

	if (argc >= 1 && !argv[0])
		THIS->no_shader();
	else
		THIS->set_shader(to<Rays::Shader>(argc, argv));
	return self;
}

static
VALUE get_shader(VALUE self)
{
	CHECK;

	const Rays::Shader& shader = THIS->shader();
	return shader ? value(shader) : nil();
}

static
VALUE no_shader(VALUE self)
{
	CHECK;
	THIS->no_shader();
	return self;
}

static
VALUE push_state(VALUE self)
{
	CHECK;
	THIS->push_state();
	return self;
}

static
VALUE pop_state(VALUE self)
{
	CHECK;
	THIS->pop_state();
	return self;
}


static
VALUE translate(VALUE self)
{
	CHECK;
	check_arg_count(__FILE__, __LINE__, "Painter#translate", argc, 2, 3);

	coord xx = to<coord>(argv[0]);
	coord yy = to<coord>(argv[1]);
	coord zz = (argc >= 3) ? to<coord>(argv[2]) : 0;
	THIS->translate(xx, yy, zz);

	return self;
}

static
VALUE scale(VALUE self)
{
	CHECK;
	check_arg_count(__FILE__, __LINE__, "Painter#scale", argc, 2, 3);

	coord xx = to<coord>(argv[0]);
	coord yy = to<coord>(argv[1]);
	coord zz = (argc >= 3) ? to<coord>(argv[2]) : 1;
	THIS->scale(xx, yy, zz);

	return self;
}

static
VALUE rotate(VALUE self)
{
	CHECK;
	check_arg_count(__FILE__, __LINE__, "Painter#rotate", argc, 1, 3, 4);

	coord aa = to<coord>(argv[0]), xx = 0, yy = 0, zz = 1;
	if (argc >= 3)
	{
		xx = to<coord>(argv[1]);
		yy = to<coord>(argv[2]);
		zz = argc >= 4 ? to<coord>(argv[3]) : 0;
	}

	THIS->rotate(aa, xx, yy, zz);

	return self;
}

static
VALUE set_matrix(VALUE self)
{
	CHECK;
	THIS->set_matrix(to<Rays::Matrix>(argc, argv));
	return value(THIS->matrix());
}

static
VALUE get_matrix(VALUE self)
{
	CHECK;
	return value(THIS->matrix());
}

static
VALUE push_matrix(VALUE self)
{
	CHECK;
	THIS->push_matrix();
	return self;
}

static
VALUE pop_matrix(VALUE self)
{
	CHECK;
	THIS->pop_matrix();
	return self;
}


static Class cPainter;

void
Init_painter ()
{
	Module mRays = rb_define_module("Rays");

	cPainter = rb_define_class_under(mRays, "Painter", rb_cObject);
	rb_define_alloc_func(cPainter, alloc);

	rb_define_method(cPainter, "canvas", RUBY_METHOD_FUNC(canvas), -1);
	rb_define_method(cPainter, "bounds", RUBY_METHOD_FUNC(bounds), 0);
	rb_define_method(cPainter, "pixel_density", RUBY_METHOD_FUNC(pixel_density), 0);

	rb_define_private_method(cPainter, "begin_paint", RUBY_METHOD_FUNC(begin_paint), 0);
	rb_define_private_method(cPainter, "end_paint", RUBY_METHOD_FUNC(end_paint), 0);
	rb_define_method(cPainter, "clear", RUBY_METHOD_FUNC(clear), 0);
	rb_define_method(cPainter, "polygon", RUBY_METHOD_FUNC(polygon), 1);
	rb_define_private_method(cPainter, "draw_line", RUBY_METHOD_FUNC(line), 2);
	rb_define_private_method(cPainter, "draw_polyline", RUBY_METHOD_FUNC(polyline), 1);
	rb_define_private_method(cPainter, "draw_rect", RUBY_METHOD_FUNC(rect), 6);
	rb_define_private_method(cPainter, "draw_ellipse", RUBY_METHOD_FUNC(ellipse), 6);
	rb_define_private_method(cPainter, "draw_curve", RUBY_METHOD_FUNC(curve), 2);
	rb_define_private_method(cPainter, "draw_bezier", RUBY_METHOD_FUNC(bezier), 2);
	rb_define_method(cPainter, "image", RUBY_METHOD_FUNC(image), -1);
	rb_define_method(cPainter, "text", RUBY_METHOD_FUNC(text), -1);

	rb_define_method(cPainter, "background=", RUBY_METHOD_FUNC(set_background), -1);
	rb_define_method(cPainter, "background", RUBY_METHOD_FUNC(get_background), 0);
	rb_define_method(cPainter, "no_background", RUBY_METHOD_FUNC(no_background), 0);
	rb_define_method(cPainter, "fill=", RUBY_METHOD_FUNC(set_fill), -1);
	rb_define_method(cPainter, "fill", RUBY_METHOD_FUNC(get_fill), 0);
	rb_define_method(cPainter, "no_fill", RUBY_METHOD_FUNC(no_fill), 0);
	rb_define_method(cPainter, "stroke=", RUBY_METHOD_FUNC(set_stroke), -1);
	rb_define_method(cPainter, "stroke", RUBY_METHOD_FUNC(get_stroke), 0);
	rb_define_method(cPainter, "no_stroke", RUBY_METHOD_FUNC(no_stroke), 0);
	rb_define_method(cPainter, "stroke_width=", RUBY_METHOD_FUNC(set_stroke_width), 1);
	rb_define_method(cPainter, "stroke_width", RUBY_METHOD_FUNC(get_stroke_width), 0);
	rb_define_method(cPainter, "stroke_cap=", RUBY_METHOD_FUNC(set_stroke_cap), 1);
	rb_define_method(cPainter, "stroke_cap", RUBY_METHOD_FUNC(get_stroke_cap), 0);
	rb_define_method(cPainter, "stroke_join=", RUBY_METHOD_FUNC(set_stroke_join), 1);
	rb_define_method(cPainter, "stroke_join", RUBY_METHOD_FUNC(get_stroke_join), 0);
	rb_define_method(cPainter, "miter_limit=", RUBY_METHOD_FUNC(set_miter_limit), 1);
	rb_define_method(cPainter, "miter_limit", RUBY_METHOD_FUNC(get_miter_limit), 0);
	rb_define_method(cPainter, "nsegment=", RUBY_METHOD_FUNC(set_nsegment), 1);
	rb_define_method(cPainter, "nsegment", RUBY_METHOD_FUNC(get_nsegment), 0);
	rb_define_method(cPainter, "blend_mode=", RUBY_METHOD_FUNC(set_blend_mode), 1);
	rb_define_method(cPainter, "blend_mode", RUBY_METHOD_FUNC(get_blend_mode), 0);
	rb_define_method(cPainter, "clip=", RUBY_METHOD_FUNC(set_clip), -1);
	rb_define_method(cPainter, "clip", RUBY_METHOD_FUNC(get_clip), 0);
	rb_define_method(cPainter, "no_clip", RUBY_METHOD_FUNC(no_clip), 0);
	rb_define_method(cPainter, "font=", RUBY_METHOD_FUNC(set_font), -1);
	rb_define_method(cPainter, "font", RUBY_METHOD_FUNC(get_font), 0);
	rb_define_private_method(cPainter, "set_shader", RUBY_METHOD_FUNC(set_shader), -1);
	rb_define_method(cPainter, "shader", RUBY_METHOD_FUNC(get_shader), 0);
	rb_define_method(cPainter, "no_shader", RUBY_METHOD_FUNC(no_shader), 0);
	rb_define_method(cPainter, "push_state", RUBY_METHOD_FUNC(push_state), 0);
	rb_define_method(cPainter, "pop_state", RUBY_METHOD_FUNC(pop_state), 0);

	rb_define_method(cPainter, "translate", RUBY_METHOD_FUNC(translate), -1);
	rb_define_method(cPainter, "scale", RUBY_METHOD_FUNC(scale), -1);
	rb_define_method(cPainter, "rotate", RUBY_METHOD_FUNC(rotate), -1);
	rb_define_method(cPainter, "matrix=", RUBY_METHOD_FUNC(set_matrix), -1);
	rb_define_method(cPainter, "matrix", RUBY_METHOD_FUNC(get_matrix), 0);
	rb_define_method(cPainter, "push_matrix", RUBY_METHOD_FUNC(push_matrix), 0);
	rb_define_method(cPainter, "pop_matrix", RUBY_METHOD_FUNC(pop_matrix), 0);
}


namespace Rays
{


	Class
	painter_class ()
	{
		return cPainter;
	}


}// Rays