import dfl.all;
import util;
import game;
import driver;

class GUI(Solver) : Form, GameObserver
{
	this(in Game g)
	{
		this.solver = new Solver(g);
		setup_size(g.map.W, g.map.H);
		setup_resources(g);
		draw(g);
	}

	private void delegate(char c) fn;
	void set_fn(F)(F f) { this.fn = f; }

	void run(bool automate = false)
	{
		if(automate) {
			Timer t = new Timer;
			t.interval = 50;
			t.tick ~= (Timer sender, EventArgs ea){
				fn(solver.single_step());
			};
			t.start();
			this.closing ~= (Form f,CancelEventArgs c){t.stop();};
		} else {
			setup_keyhandling();
		}
		Application.run(this);
	}

	override void on_game_changed(char c, in Game g, bool finished)
	{
		draw(g);
	}

private:
	int cell;

	void setup_size(int W, int H)
	{
		this.formBorderStyle = FormBorderStyle.FIXED_DIALOG;
		this.maximizeBox = false;
		this.minimizeBox = false;
		this.cell = min(1024/W, 640/H);
		this.clientSize = Size(W*cell, H*cell);
	}

	Font         font;
	Color[char]  colors;
	string[char] render;
	Graphics     graphicContext;

	void setup_resources(in Game g)
	{
		this.graphicContext = new MemoryGraphics(this.clientSize.width, this.clientSize.height);
		this.setStyle(ControlStyles.OPAQUE, true);
		this.font = new Font("MS Gothic", cell-2, GraphicsUnit.PIXEL);
		this.backColor = Color(255,255,255);
		this.colors['#'] =
		this.colors['.'] = Color(255,191,127);
		this.colors['*'] = Color(255,127,127);
		this.colors['R'] = Color(128,128,0);
		this.colors['D'] = Color(255,0,0);
		this.colors['\\'] =
		this.colors['L'] =
		this.colors['O'] = Color(127,255,127);
		this.colors['W'] = Color(204,229,255);
		foreach(char c; 'A'..'J') this.colors[c] = Color(142,142,255);
		foreach(char c; '1'..':') this.colors[c] = Color(255,142,255);
		this.render['#'] = "■";
		this.render['*'] = "✹";
		this.render['.'] = "♒";
		this.render['\\'] = "λ";
		this.render['R'] = "☃";
		this.render['D'] = "☠";
		this.render['L'] = "☒";
		this.render['O'] = "☐";
		foreach(c,tp; g.map.tr_target) {
			char d = g.map[tp];
			this.render[c] = [cast(dchar)('㋀'+d-'1')].to!string();
		}
		foreach(char c; '1'..':') this.render[c] = [cast(dchar)('㏠'+c-'1')].to!string();
		this.paint ~= (Control c, PaintEventArgs ev) {
			graphicContext.copyTo(ev.graphics, Rect(0,0,this.clientSize.width,this.clientSize.height));
		};
	}

	void draw(in Game g)
	{
		int scrW = this.clientSize.width;
		int scrH = this.clientSize.height;

		// Fill bg.
		graphicContext.fillRectangle(this.backColor, Rect(0,0,scrW,scrH));

		// Fill water.
		int w = g.water_level();
		graphicContext.fillRectangle(this.colors['W'], Rect(0, scrH-cell*w-1, scrW, cell*w+1));

		// Paint map.
		for(int y=1; y<=g.map.H; ++y)
		for(int x=1; x<=g.map.W; ++x) {
			Rect r = Rect(cell*(x-1), scrH-cell*y, cell, cell);
			char c = g.map[y,x];
			if( c != ' ' ) {
				if( c == 'R' && g.dead )
					c = 'D';
				graphicContext.drawText(this.render[c], font, this.colors[c], r);
			}
		}

		// Update textual info.
		this.text = .text("Score: ", g.score, " Air: ", g.hp, " Tide: ", g.water_until_rise);
		invalidate();
	}

private:
	void setup_keyhandling()
	{
		noMessageFilter();
		this.keyDown ~= &my_keydown;
	}

	void my_keydown(Control c, KeyEventArgs ev)
	{
		switch(ev.keyCode)
		{
		case Keys.DOWN:  fn('D'); break;
		case Keys.UP:    fn('U'); break;
		case Keys.LEFT:  fn('L'); break;
		case Keys.RIGHT: fn('R'); break;
		case Keys.W:     fn('W'); break;
		case Keys.A:     fn('A'); break;
		case Keys.G:     fn(solver.single_step()); break;
		default:         break;
		}
	}

	Solver solver;
}
