#include "md2model.hpp"
#include "md2rep.hpp"
#include "animator.hpp"

struct MD2Model::Rep : public MD2Rep {
	int ref_cnt;

	Rep(const std::string& f) : MD2Rep(f), ref_cnt(1) {}
};

MD2Model::MD2Model(const std::string& f)
	: rep(new Rep(f)), anim_mode(0), anim_time(0), render_a(0), render_b(0), render_t(0), trans_verts(0)
{}

MD2Model::MD2Model(const MD2Model& t)
	: Model(t), rep(t.rep), anim_mode(0), anim_time(0), render_a(0), render_b(0), render_t(0), trans_verts(0)
{
	++rep->ref_cnt;
}

MD2Model::~MD2Model()
{
	if (!--rep->ref_cnt)
		delete rep;
	if (trans_verts)
		delete[] trans_verts;
}

void MD2Model::startMD2Anim(int first, int last, int mode, float speed, float trans)
{
	if (last < first)
		std::swap(first, last);

	if (first < 0)
		first = 0;
	else if (first >= rep->numFrames())
		first = rep->numFrames() - 1;

	if (last < 0)
		last = 0;
	else if (last >= rep->numFrames())
		last = rep->numFrames() - 1;

	if (trans > 0) {
		if (!trans_verts)
			trans_verts = new MD2Rep::Vert[rep->numVertices()];

		if (anim_mode & 0x8000)
			rep->render(trans_verts, anim_time, trans_time);
		else
			rep->render(trans_verts, render_a, render_b, render_t);
		trans_speed = 1.0f / trans;
		trans_time  = 0;
		mode |= 0x8000;
	}

	anim_first = first;
	anim_last  = last;
	anim_len   = last - first;
	anim_speed = speed;
	anim_time  = ((mode & 0x7fff) == Animator::ANIM_MODE_LOOP || anim_speed >= 0) ? anim_first : anim_last;
	anim_mode  = mode;

	if (!anim_speed || !anim_len) {
		render_a = render_b = anim_time;
		render_t            = 0;
		anim_mode &= 0x8000;
	}
}

void MD2Model::animate(float e)
{
	Model::animate(e);
	if (!anim_mode)
		return;
	if (anim_mode & 0x8000) {
		trans_time += trans_speed;
		if (trans_time < 1)
			return;
		anim_mode &= ~0x8000;
		if (!anim_mode)
			return;
	}
	anim_time = anim_time + anim_speed * e;
	if (anim_time < anim_first) {
		switch (anim_mode) {
		case Animator::ANIM_MODE_LOOP:
			anim_time += anim_len;
			break;
		case Animator::ANIM_MODE_PINGPONG:
			anim_time  = anim_first + (anim_first - anim_time);
			anim_speed = -anim_speed;
			break;
		default:
			anim_time = anim_first;
			anim_mode = 0;
			break;
		}
	} else if (anim_time >= anim_last) {
		switch (anim_mode) {
		case Animator::ANIM_MODE_LOOP:
			anim_time -= anim_len;
			break;
		case Animator::ANIM_MODE_PINGPONG:
			anim_time  = anim_last - (anim_time - anim_last);
			anim_speed = -anim_speed;
			break;
		default:
			anim_time = anim_last;
			anim_mode = 0;
			break;
		}
	}
	render_a = floor(anim_time);
	render_b = render_a + 1;
	if (anim_mode == Animator::ANIM_MODE_LOOP && render_b == anim_last)
		render_b = anim_first;
	render_t = anim_time - render_a;
}

bool MD2Model::render(const RenderContext& rc)
{
	static Frustum f;
	new (&f) Frustum(rc.getWorldFrustum(), -getRenderTform());
	if (!f.cull(rep->getBox()))
		return false;

	if (anim_mode & 0x8000) {
		rep->render(this, trans_verts, anim_time, trans_time);
	} else {
		rep->render(this, render_a, render_b, render_t);
	}
	return false;
}

int MD2Model::getMD2AnimLength() const
{
	return rep->numFrames();
}

bool MD2Model::getValid() const
{
	return rep->numFrames() > 0;
}
