#include "brush.hpp"

#include <gxgraphics.hpp>
#include <gxscene.hpp>
#include <gxcanvas.hpp>

struct Brush::Rep {
	union {
		int  ref_cnt;
		Rep* next;
	};
	int                  blend, max_tex;
	bool                 blend_valid;
	gxScene::RenderState rs;
	Texture              texs[gxScene::MAX_TEXTURES];

	static Rep* pool;

	Rep() : ref_cnt(1), blend(0), max_tex(0), blend_valid(true)
	{
		memset(&rs, 0, sizeof(rs));
		rs.blend    = gxScene::BLEND_REPLACE;
		rs.color[0] = rs.color[1] = rs.color[2] = rs.alpha = 1;
	}

	Rep(const Rep& t) : ref_cnt(1), blend(t.blend), max_tex(t.max_tex), rs(t.rs), blend_valid(t.blend_valid)
	{
		for (int k = 0; k < max_tex; ++k)
			texs[k] = t.texs[k];
	}

	void* operator new(size_t sz)
	{
		static const int GROW = 64;
		if (!pool) {
			pool = new Rep[GROW];
			for (int k = 0; k < GROW - 1; ++k)
				pool[k].next = &pool[k + 1];
			pool[GROW - 1].next = 0;
		}
		Rep* p = pool;
		pool   = p->next;
		return p;
	}

	void operator delete(void* q)
	{
		Rep* p  = (Rep*)q;
		p->next = pool;
		pool    = p;
	}
};

Brush::Rep* Brush::Rep::pool;

Brush::Brush() : rep(new Rep()) {}

Brush::Brush(const Brush& t) : rep(t.rep)
{
	++rep->ref_cnt;
}

Brush::Brush(const Brush& a, const Brush& b) : rep(new Rep(*a.rep))
{
	*(Vector*)rep->rs.color *= *(Vector*)b.rep->rs.color;

	rep->rs.alpha *= b.rep->rs.alpha;
	rep->rs.shininess += b.rep->rs.shininess;

	if (b.rep->blend)
		rep->blend = b.rep->blend;

	rep->rs.fx |= b.rep->rs.fx;

	if (b.rep->max_tex > rep->max_tex)
		rep->max_tex = b.rep->max_tex;

	for (int k = 0; k < rep->max_tex; ++k) {
		if (b.rep->rs.tex_states[k].canvas) {
			rep->rs.tex_states[k].canvas = b.rep->rs.tex_states[k].canvas;
			rep->texs[k]                 = b.rep->texs[k];
		}
	}

	rep->blend_valid = false;
}

Brush::~Brush()
{
	if (!--rep->ref_cnt)
		delete rep;
}

Brush& Brush::operator=(const Brush& t)
{
	++t.rep->ref_cnt;
	if (!--rep->ref_cnt)
		delete rep;
	rep = t.rep;
	return *this;
}

Brush::Rep* Brush::write() const
{
	if (rep->ref_cnt > 1) {
		--rep->ref_cnt;
		rep = new Rep(*rep);
	}
	return rep;
}

void Brush::setColor(const Vector& color)
{
	*(Vector*)write()->rs.color = color;
}

void Brush::setAlpha(float alpha)
{
	float a           = rep->rs.alpha;
	write()->rs.alpha = alpha;
	if ((a < 1) != (alpha < 1))
		rep->blend_valid = false;
}

void Brush::setShininess(float n)
{
	write()->rs.shininess = n;
}

void Brush::setBlend(int blend)
{
	write()->blend   = blend;
	rep->blend_valid = false;
}

void Brush::setFX(int fx)
{
	write()->rs.fx   = fx;
	rep->blend_valid = false;
}

void Brush::setTexture(int index, const Texture& t, int n)
{
	write();
	gxScene::RenderState& rs = rep->rs;

	rep->texs[index]            = t;
	rs.tex_states[index].canvas = t.getCanvas(n);

	rep->max_tex = 0;
	for (int k = 0; k < gxScene::MAX_TEXTURES; ++k) {
		if (rs.tex_states[k].canvas)
			rep->max_tex = k + 1;
	}
	rep->blend_valid = false;
}

const Vector& Brush::getColor() const
{
	return *(Vector*)rep->rs.color;
}

float Brush::getAlpha() const
{
	return rep->rs.alpha;
}

float Brush::getShininess() const
{
	return rep->rs.shininess;
}

int Brush::getBlend() const
{
	if (rep->blend_valid)
		return rep->rs.blend;

	rep->blend_valid = true; //well, it will be...

	gxScene::RenderState& rs = rep->rs;

	//alphatest
	if (rep->texs[0].getCanvasFlags() & gxCanvas::CANVAS_TEX_MASK) {
		rs.fx |= gxScene::FX_ALPHATEST;
	} else {
		rs.fx &= ~gxScene::FX_ALPHATEST;
	}

	//0 = default/replace
	//1 = alpha
	//2 = multiply
	//3 = add
	if (rep->blend) {
		if (rep->blend != gxScene::BLEND_ALPHA) {
			return rs.blend = rep->blend;
		}
		for (int k = 0; k < rep->max_tex; ++k) {
			if (rep->texs[k].isTransparent()) {
				return rs.blend = gxScene::BLEND_ALPHA;
			}
		}
	} else if (rep->max_tex == 1 && rep->texs[0].isTransparent()) {
		//single transparent texture?
		return rs.blend = gxScene::BLEND_ALPHA;
	}

	//vertex alpha or entityalpha?
	if ((rs.fx & gxScene::FX_VERTEXALPHA) || rs.alpha < 1) {
		return rs.blend = gxScene::BLEND_ALPHA;
	}

	return rs.blend = gxScene::BLEND_REPLACE;
}

int Brush::getFX() const
{
	return rep->rs.fx;
}

Texture Brush::getTexture(int index) const
{
	return rep->texs[index];
}

const gxScene::RenderState& Brush::getRenderState() const
{
	getBlend();
	for (int k = 0; k < rep->max_tex; ++k) {
		gxScene::RenderState::TexState* ts = &rep->rs.tex_states[k];
		ts->matrix                         = rep->texs[k].getMatrix();
		ts->blend                          = rep->texs[k].getBlend();
		ts->flags                          = rep->texs[k].getFlags();
	}
	return rep->rs;
}

bool Brush::operator<(const Brush& t) const
{
	return memcmp(&getRenderState(), &t.getRenderState(), sizeof(gxScene::RenderState)) < 0;
}
