package res.devtools;

import res.devtools.sprites.SpritesMenu;
import res.devtools.tilemaps.TilemapMenu;
import res.input.Key;
import res.text.Textmap;

using String;
using StringTools;

typedef ConsoleCommandFunc = Array<String>->Void;

typedef ConsoleCommand = {
	cmd:String,
	help:String,
	callback:ConsoleCommandFunc
};

class Console extends Scene {
	static final BLINK_TIME:Float = 1;
	static final CURSOR:String = '_';

	var consoleText:Textmap;

	final commands:Map<String, ConsoleCommand> = [];

	var commandInput:String = '';

	var blinkTimer:Float = 0;
	var blink:Bool = true;

	final log:Array<String> = [];

	function updateInput(?value:String) {
		if (value != null)
			commandInput = value;

		consoleText.textAt(0, consoleText.vTiles - 1, '>' + commandInput + (blink ? CURSOR : ''));
	}

	@:allow(res)
	private function new(res:RES) {
		super(res);

		addCommand('clear', 'Clear console', clear);
		addCommand('help', 'Show help for commands', help);
		addCommand('exit', 'Exit console', (_) -> {
			res.popScene();
		});

		consoleText = res.createDefaultTextmap([res.palette.brightestIndex]);

		consoleText.scrollY = consoleText.pixelHeight - res.frameBuffer.frameHeight;

		renderList.push(consoleText);

		updateInput('');
	}

	public function initDefaultCommands() {
		addCommand('fps', 'Show/hide fps', (args) -> {
			if (args.length >= 1) {
				res.showFps = (args[0].toLowerCase() == 'true' || args[0] == '1');
			}
			println('Show fps: ${res.showFps}');
		});

		#if sys
		addCommand('quit', 'Quit program', (_) -> {
			Sys.exit(0);
		});
		#end

		addCommand('about', 'About this game', (_) -> {
			println('RES      : v0.1.0'); // TODO: Make dynamic
			println('Tile size: ${res.tileSize}');
			println('Resol.   : ${res.frameBuffer.frameWidth}x${res.frameBuffer.frameHeight}');
			println('Palette  : ${res.palette.colors.length} col.');
		});

		addCommand('palette', 'Show palette', (_) -> {
			res.setScene(PaletteView);
		});

		addCommand('tileset', 'View tileset', (args) -> {
			if (args.length == 0) {
				println('Tilesets:');
				for (id => set in res.rom.tilesets) {
					println(' $id (${set.numTiles})');
				}
			} else if (args.length == 1) {
				if (res.rom.tilesets.exists(args[0])) {
					res.setScene(new TilesetView(res, res.rom.tilesets[args[0]]));
				} else {
					println('`${args[0]}` 404');
				}
			} else
				println('Too many arguments');
		});

		addCommand('sprite', 'View/Edit sprites', (_) -> {
			res.setScene(SpritesMenu);
		});

		addCommand('tilemap', 'View/Edit tilemaps', (args) -> {
			if (args.length == 0) {
				res.setScene(TilemapMenu, true);
			} else {
				if (res.rom.tilemaps.exists(args[0])) {
					// TODO Editor
				} else {
					println('No such tilemap: ${args[0]}');
				}
			}
		});
	}

	override function keyDown(keyCode:Int) {
		switch (keyCode) {
			case Key.BACKSPACE:
				updateInput(commandInput.substr(0, -1));
			case Key.ENTER:
				if (commandInput.trim() != '') {
					execute(commandInput.trim());
					updateInput('');
				}
			case Key.ESCAPE:
				res.popScene();
		}
	}

	override function keyPress(charCode:Int) {
		if (charCode != '`'.code)
			updateInput(commandInput += charCode.fromCharCode());
		else
			res.popScene();
	}

	public function clear(?params:Array<String>) {
		log.resize(0);
		for (line in 0...consoleText.vTiles - 1)
			consoleText.textAt(0, line, '');
	}

	function help(params:Array<String>) {
		for (cmd in commands) {
			if (params.length == 0 || params.indexOf(cmd.cmd) != -1) {
				println('${cmd.cmd}');
				println('  ${cmd.help}');
				println(' ');
			}
		}
		println('----');
		println('Press ESC to close console');
	}

	public function addCommand(cmd:String, ?help:String, cb:ConsoleCommandFunc) {
		commands[cmd] = {
			cmd: cmd,
			help: help,
			callback: cb
		};
	}

	public function execute(cmdStr:String) {
		var parts = cmdStr.split(' ').map(s -> s.trim()).filter(s -> s != '');

		var cmd = parts.shift();

		if (commands[cmd] != null) {
			commands[cmd].callback(parts);
		} else {
			println('Unknown command \'$cmd\'');
		}
	}

	public function println(s:String) {
		var lines:Array<String> = [];

		while (s.length > consoleText.hTiles) {
			lines.push(s.substr(0, consoleText.hTiles));
			s = s.substr(consoleText.hTiles);
		}

		if (s != '')
			lines.push(s);

		for (line in lines)
			log.push(line);

		var line = consoleText.vTiles - 2;
		var index = log.length - 1;

		while (index >= 0 && line >= 0) {
			consoleText.textAt(0, line, log[index]);
			index--;
			line--;
		}
	}

	override function update(dt:Float) {
		if (blinkTimer >= BLINK_TIME) {
			blinkTimer = blinkTimer - BLINK_TIME;
			blink = !blink;
			updateInput();
		} else
			blinkTimer += dt;
	}
}
