module vs.effect.program;



private import std.math;
private import gedo.math.vector;
private import gedo.audio.sound;
private import gedo.video.ggl;
private import gedo.video.color;
private import gedo.video.texture;
private import gedo.video.staticmesh;
private import gedo.video.skinnedmesh;
private import gedo.video.alphameshlist;
public import vs.action.effect;



private void contract(float k, float so)
{
	auto s = so*(1.0 - k*k);

	GGL_scale(s, s, s);
}



private void expand(float k, float so, float sd)
{
	k -= 1.0;

	auto s = so + sd*(1.0-k*k);

	GGL_scale(s, s, s);
}



private int fadeoutAlpha(float k)
{
	return cast(int)(255*(1.0-k*k));
}



private Color rainbowColor(float k)
{
	const int MIN = 64;
	const int W   = 128;

	if(k < 1.0/3.0)
	{
		k *= 3.0;

		return rgba(cast(int)(W*(1.0-k)+MIN), cast(int)(W*k+MIN), MIN, 255);
	}
	else if(k < 2.0/3.0)
	{
		k = 3.0*k - 1.0;

		return rgba(MIN, cast(int)(W*(1.0-k)+MIN), cast(int)(W*k+MIN), 255);
	}
	else
	{
		k = 3.0*k - 2.0;

		return rgba(cast(int)(W*k)+MIN, MIN, cast(int)(W*(1.0-k)+MIN), 255);
	}
}



public class HitMark : Flash5
{
public:
	this()
	{
		_flash = StaticMesh("$LIB/action/flash7.gsa");
		_ring  = StaticMesh("$LIB/action/ring.gsa");

		_scale = 8.0;

		Color[4] pixels;

		pixels[0] = WHITE;
		pixels[1] = rgba(255, 192, 0, 255);
		pixels[2] = rgba(255, 128, 0, 255);
		pixels[3] = setA(pixels[2], 0);

		_texture = new Texture(pixels, pixels.length, 1);

		_blendMode = GGL_BlendMode.MIX;

		_sound = null;

		_color = rgba(192, 255, 0, 255);
	}


	void update(EffectTask task)
	{
		if(task.time >= LIFE_TIME)
		{
			task.stop();
		}
		else if(task.time == 0 && _sound)
		{
			Source().play(_sound);
		}
	}


	void draw(EffectTask task)
	{
		if(auto am = task.list.get())
		{
			am.set(task.position.current, &task.drawAlpha);
		}
	}


	void drawAlpha(EffectTask task)
	{
		task.direction.transform(task.position.current);

		if(task.time < FLASH_TIME)
		{
			GGL_setBlendMode(GGL_BlendMode.MIX);

			GGL_pushMatrix();

			GGL_enterBillBoardMode();

			GGL_rotate(task.time*PI/64.0, 0, 0, 1);

			auto k = task.time * 1.0/FLASH_TIME;

			auto s = _scale*(2.0 - k*k);

			GGL_scale(s, s, s);

			_flash.draw(_texture, WHITE);

			GGL_popMatrix();
		}

		GGL_setBlendMode(_blendMode);

		auto k = task.time * 1.0/LIFE_TIME - 1.0;

		auto s = 1.0 + _scale*(1.0 - k*k);

		GGL_scale(s, s, s);

		k += 1.0;

		_ring.draw(setA(_color, cast(int)(255*(1.0 - k*k))));
	}


	bool isLiving(EffectTask task)
	{
		return (task.time < LIFE_TIME);
	}


private:
	const int LIFE_TIME = 32;
	const int FLASH_TIME = 8;

	GGL_BlendMode _blendMode;
	StaticMesh    _flash;
	StaticMesh    _ring;
	Texture       _texture;
	Sound         _sound;
	Color         _color;
	float         _scale;
}



public class GuardMark : EffectProgram
{
public:
	this()
	{
		_ring = SkinnedMesh("$LIB/guardmark/guardmark.gsa");

		_sound = Sound("$LIB/action/guard.ogg");
	}


	void update(EffectTask task)
	{
		if(task.time >= _ring.animationLength(0))
		{
			task.stop();
		}
		else if(task.time == 0)
		{
			Source().play(_sound);
		}
	}


	void draw(EffectTask task)
	{
		if(auto am = task.list.get())
		{
			am.set(_ring, task.position.current, 0, task.time, GGL_BlendMode.MIX);
		}
	}


	void drawAlpha(EffectTask task)
	{
	}


	bool isLiving(EffectTask task)
	{
		return (task.time < _ring.animationLength(0));
	}


private:
	SkinnedMesh _ring;
	Sound       _sound;
}



public class LandMark : Ring
{
public:
	this()
	{
		super(8.0, GGL_BlendMode.MIX, WHITE);

		_sound = Sound("$LIB/action/land.ogg");
	}


	override void update(EffectTask task)
	{
		if(task.time == 0)
		{
			Source().play(_sound);
		}

		super.update(task);
	}


private:
	Sound _sound;
}



public class DashMark : Ring
{
public:
	this()
	{
		super(16.0, GGL_BlendMode.MIX, rgba(128, 128, 255, 255));

		_sound = Sound("$LIB/action/dash.ogg");
	}


	override void update(EffectTask task)
	{
		if(task.time == 0)
		{
			Source().play(_sound);
		}
		super.update(task);
	}


private:
	Sound _sound;
}



public class SetSpellMark : EffectProgram
{
public:
	this()
	{
		_ray = StaticMesh("$LIB/action/ray4.gsa");

		_sound = Sound("$LIB/action/setspell.ogg");
	}


	void update(EffectTask task)
	{
		if(task.time == 0)
		{
			Source().play(_sound);
		}
	}


	void draw(EffectTask task)
	{
		if(auto am = task.list.get())
		{
			am.set(task.position.current, &task.drawAlpha);
		}
	}


	void drawAlpha(EffectTask task)
	{
		const float SCALE = 2.0;

		auto k = task.time*1.0/LIFE_TIME - 1.0;

		GGL_setBlendMode(GGL_BlendMode.ADD);

		task.direction.transform(task.position.current);

		GGL_pushMatrix();

		GGL_translate(32.0*(1.0-k*k), 0, 0);

		GGL_enterBillBoardMode();

		GGL_rotate(task.time*PI/64.0, 0, 0, 1);

		GGL_scale(SCALE, SCALE, SCALE);

		_ray.draw(rainbowColor((task.time & 31)/32.0));

		GGL_popMatrix();

		GGL_pushMatrix();

		GGL_translate(-32.0*(1.0-k*k), 0, 0);

		GGL_enterBillBoardMode();

		GGL_rotate(task.time*PI/64.0, 0, 0, 1);

		GGL_scale(SCALE, SCALE, SCALE);

		_ray.draw(rainbowColor(((task.time + 8) & 31)/32.0));

		GGL_popMatrix();

		GGL_pushMatrix();

		GGL_translate(0, 0, 16.0*(1.0-k*k));

		GGL_enterBillBoardMode();

		GGL_rotate(task.time*PI/128.0, 0, 0, 1);

		GGL_scale(SCALE, SCALE, SCALE);

		_ray.draw(rainbowColor(((task.time + 16) & 31)/32.0));

		GGL_popMatrix();

		GGL_pushMatrix();

		GGL_translate(0, 0, -16.0*(1.0-k*k));

		GGL_enterBillBoardMode();

		GGL_rotate(task.time*PI/128.0, 0, 0, 1);

		GGL_scale(SCALE, SCALE, SCALE);

		_ray.draw(rainbowColor(((task.time + 24) & 31)/32.0));

		GGL_popMatrix();
	}


	bool isLiving(EffectTask task)
	{
		return (task.time < LIFE_TIME);
	}


private:
	const int LIFE_TIME = 64;


	StaticMesh _ray;
	Sound      _sound;
}



public class Flash4 : EffectProgram
{
public:
	this()
	{
		_circle = StaticMesh("$LIB/action/circle.gsa");
		_ray    = StaticMesh("$LIB/action/ray4.gsa");
	}


	void update(EffectTask task)
	{
		if(task.time >= LIFE_TIME)
		{
			task.stop();
		}
	}


	void draw(EffectTask task)
	{
		if(auto am = task.list.get())
		{
			am.set(task.position.current, &task.drawAlpha);
		}
	}


	void drawAlpha(EffectTask task)
	{
		translate(task.position.current);

		GGL_enterBillBoardMode();

		GGL_pushMatrix();

		auto k = task.time*1.0/LIFE_TIME;
		expand(k, 4.0, 4.0);

		GGL_setBlendMode(GGL_BlendMode.ADD);

		auto alpha = fadeoutAlpha(k);

		auto color = rgba(255, 255, 255, alpha);
		_circle.draw(color);

		GGL_popMatrix();

		GGL_rotate(task.time*PI/64.0, 0, 0, 1);

		_ray.draw(color);
	}


	bool isLiving(EffectTask task)
	{
		return (task.time < LIFE_TIME);
	}


private:
	const int LIFE_TIME = 8;

	StaticMesh _circle;
	StaticMesh _ray;
}



public class Flash5 : EffectProgram
{
public:
	this(float scale = 8.0, GGL_BlendMode blendMode = GGL_BlendMode.MIX, Texture tex = null)
	{
		_flash = StaticMesh("$LIB/action/flash5.gsa");

		_scale = scale;

		_texture = tex;
		if(!_texture)
		{
			_texture = _flash.texture;
		}

		_blendMode = blendMode;
	}


	void update(EffectTask task)
	{
		if(task.time >= LIFE_TIME)
		{
			task.stop();
		}
	}


	void draw(EffectTask task)
	{
		if(auto am = task.list.get())
		{
			am.set(task.position.current, &task.drawAlpha);
		}
	}


	void drawAlpha(EffectTask task)
	{
		if(task.time < LIFE_TIME)
		{
			GGL_setBlendMode(_blendMode);

			GGL_pushMatrix();
			scope(exit)
			{
				GGL_popMatrix();
			}

			translate(task.position.current);

			GGL_enterBillBoardMode();

			GGL_rotate(task.time*PI/64.0, 0, 0, 1);

			expand(task.time*1.0/LIFE_TIME, _scale, _scale*0.5);

			_flash.draw(_texture, WHITE);
		}
	}


	bool isLiving(EffectTask task)
	{
		return (task.time < LIFE_TIME);
	}


private:
	const int LIFE_TIME = 8;

	GGL_BlendMode _blendMode;
	StaticMesh    _flash;
	Texture       _texture;
	float         _scale;
}



public class Circle : EffectProgram
{
public:
	this(float scale, GGL_BlendMode blend = GGL_BlendMode.ADD, Color color = WHITE)
	{
		_scale = scale;

		_blendMode = blend;

		_color = color;

		_circle = StaticMesh("$LIB/action/circle.gsa");
	}


	void update(EffectTask task)
	{
		if(task.time >= LIFE_TIME)
		{
			task.stop();
		}
	}


	void draw(EffectTask task)
	{
		if(auto am = task.list.get())
		{
			am.set(task.position.current, &task.drawAlpha);
		}
	}


	void drawAlpha(EffectTask task)
	{
		if(task.time < LIFE_TIME)
		{
			GGL_setBlendMode(_blendMode);

			GGL_pushMatrix();
			scope(exit)
			{
				GGL_popMatrix();
			}

			translate(task.position.current);

			GGL_enterBillBoardMode();

			auto k = task.time * 1.0 / LIFE_TIME;

			expand(k, _scale, _scale);

			auto alpha = fadeoutAlpha(k);

			auto color = setA(_color, alpha);
			_circle.draw(color);
		}
	}


	bool isLiving(EffectTask task)
	{
		return (task.time < LIFE_TIME);
	}


private:
	const int LIFE_TIME = 32;

	GGL_BlendMode _blendMode;
	StaticMesh    _circle;
	Color         _color;
	float         _scale;
}



public class Ray : EffectProgram
{
public:
	this(float scale, GGL_BlendMode blend = GGL_BlendMode.ADD, Color color = WHITE)
	{
		_scale = scale;

		_blendMode = blend;

		_color = color;

		_ray = StaticMesh("$LIB/action/ray.gsa");
	}


	void update(EffectTask task)
	{
		if(task.time >= LIFE_TIME)
		{
			task.stop();
		}
	}


	void draw(EffectTask task)
	{
		if(auto am = task.list.get())
		{
			am.set(task.position.current, &task.drawAlpha);
		}
	}


	void drawAlpha(EffectTask task)
	{
		if(task.time < LIFE_TIME)
		{
			GGL_setBlendMode(_blendMode);

			GGL_pushMatrix();
			scope(exit)
			{
				GGL_popMatrix();
			}

			translate(task.position.current);

			GGL_enterBillBoardMode();

			GGL_rotate(task.time*PI/64.0, 0, 0, 1);

			auto k = task.time * 1.0 / LIFE_TIME;

			auto s = _scale*(1.0 + (task.time&3)*0.25);

			GGL_scale(s, s, s);

			auto alpha = fadeoutAlpha(k);

			auto color = setA(_color, alpha);
			_ray.draw(color);
		}
	}


	bool isLiving(EffectTask task)
	{
		return (task.time < LIFE_TIME);
	}


private:
	const int LIFE_TIME = 8;

	GGL_BlendMode _blendMode;
	StaticMesh    _ray;
	Color         _color;
	float         _scale;
}



public class Ring : EffectProgram
{
public:
	this(float scale, GGL_BlendMode blend = GGL_BlendMode.ADD, Color color = WHITE)
	{
		_scale = scale;

		_blendMode = blend;

		_color = color;

		_ring = StaticMesh("$LIB/action/ring.gsa");
	}


	void update(EffectTask task)
	{
		if(task.time >= LIFE_TIME)
		{
			task.stop();
		}
	}


	void draw(EffectTask task)
	{
		if(auto am = task.list.get())
		{
			am.set(task.position.current, &task.drawAlpha);
		}
	}


	void drawAlpha(EffectTask task)
	{
		if(task.time < LIFE_TIME)
		{
			GGL_setBlendMode(_blendMode);

			GGL_pushMatrix();
			scope(exit)
			{
				GGL_popMatrix();
			}

			task.direction.transform(task.position.current);

			auto k = task.time * 1.0 / LIFE_TIME;

			expand(k, 1.0, _scale);

			auto alpha = fadeoutAlpha(k);

			auto color = setA(_color, alpha);
			_ring.draw(color);
		}
	}


	bool isLiving(EffectTask task)
	{
		return (task.time < LIFE_TIME);
	}


private:
	const int LIFE_TIME = 32;

	GGL_BlendMode _blendMode;
	StaticMesh    _ring;
	Color         _color;
	float         _scale;
}



public class Explosion : EffectProgram
{
public:
	this(float scale)
	{
		_explosion = Texture("$LIB/moko/explosion.png");
		_scale     = scale;
	}


	void update(EffectTask task)
	{
		if(task.time >= LIFE_TIME)
		{
			task.stop();
		}
	}


	void draw(EffectTask task)
	{
		if(auto am = task.list.get())
		{
			am.set(task.position.current, &task.drawAlpha);
		}
	}


	void drawAlpha(EffectTask task)
	{
		GGL_pushMatrix();
		scope(exit)
		{
			GGL_popMatrix();
		}

		translate(task.position.current);

		GGL_enterBillBoardMode();

		auto k = task.time*2.0/LIFE_TIME;
		if(k >= 1.0)
		{
			k -= 1.0;

			GGL_setBlendMode(GGL_BlendMode.MIX);

			auto s = _scale*(2.0 + k);

			auto alpha = cast(int)(255*(1.0 - k*k));

			auto gb = 0;
			if(k < 0.25)
			{
				gb = cast(int)(255*(0.25 - k));
			}

			_explosion.draw(s, s, rgba(alpha, gb, gb, alpha));
		}
		else
		{
			GGL_setBlendMode(GGL_BlendMode.MIX);

			auto t = k - 1.0;
			auto s = _scale + _scale*(1.0 - t*t);

			_explosion.draw(s, s);

			GGL_setBlendMode(GGL_BlendMode.ADD);

			auto alpha = fadeoutAlpha(k);

			auto color = rgba(255, 255, 255, alpha);

			_explosion.draw(s, s, color);
		}
	}


	bool isLiving(EffectTask task)
	{
		return (task.time < LIFE_TIME);
	}


private:
	const int LIFE_TIME  = 32;

	Texture _explosion;
	float   _scale;
}



public class CompositeEffect : EffectProgram
{
public:
	this(EffectProgram[] programs)
	{
		_programs = programs.dup;
	}


	void update(EffectTask task)
	{
		if(!isLiving(task))
		{
			task.stop();
		}
	}


	void draw(EffectTask task)
	{
		if(auto am = task.list.get())
		{
			am.set(task.position.current, &task.drawAlpha);
		}
	}


	void drawAlpha(EffectTask task)
	{
		foreach(program; _programs)
		{
			if(program.isLiving(task))
			{
				program.drawAlpha(task);
			}
		}
	}


	bool isLiving(EffectTask task)
	{
		bool result = false;

		foreach(program; _programs)
		{
			result |= program.isLiving(task);
		}

		return result;
	}


private:
	EffectProgram[] _programs;
}
