///<reference path="ecmascript-api.d.ts" />
///<reference path="console.d.ts" />
///<reference path="node.d.ts" />
import path = require("path");
import fs = require("fs");
import events = require("events");
import child_process = require("child_process");


class Paths {
	bin: string;
	base: string;
	src: string;
	ts: string;
	js: string;
	doc: string;
	lib: string;
	constructor() {
		var path = <path>require("path");
		this.bin = process.cwd();
		this.base = path.join(this.bin, "..");
		this.src = path.join(this.base, "src");
		this.ts = path.join(this.src, "ts");
		this.js = path.join(this.src, "js");
		this.doc = path.join(this.base, "doc");
		this.lib = path.join(this.base, "lib");
	}
}
class Config {
	paths: Paths;
	constructor() {
		this.paths = new Paths();
	}
}

class Task extends events.EventEmitter {
	name: string;
	constructor(public config: Config, name?: string) {
		super();
		if (name === undefined) {
			if (this["constructor"] && this["constructor"].name)
				this.name = this["constructor"].name;
			else
				this.name = "Undefined Task";
		} else
			this.name = name;

		this.init();
	}

	run(): boolean {
		this.emit("done");
		return true;
	}

	init() {
	}
}

class CompileTask extends Task {
	old_dir: string;
	dist: string;
	comment: boolean;
	declaration: boolean;

	run() {
		this.old_dir = process.cwd();
		process.chdir(this.config.paths.ts);
		var cp = <child_process>require("child_process");
		var cmd = "tsc @build.txt";
		if (this.comment)
			cmd += " -c";

		if (this.dist)
			cmd += " -out \"" + this.dist + "\"";

		if (this.declaration)
			cmd += " --declaration";

		cp.exec(cmd, (error, stdout, stderr) => {
			process.chdir(this.old_dir);
			if (error) {
				console.error("build error: "+error);
				this.emit("error", error);
				return;
			}

			this.emit("done");
		});
		return true;
	}
}

//TODO: 後で。jsdocでも通すか？でも型情報が失われちゃうんだよな。
class DocumentTask extends Task {
	run() {
		//nothing todo
		this.emit("done");
		return true;
	}
}

class UnlinkTask extends Task {
	target: string;
	targets: string[];
	_exit: boolean;
	cnt: number;

	run() {
		this.targets = this.targets ? this.targets : [this.target];
		this.cnt = 0;
		this.targets.forEach((target: string) => {
			this.process(target);
		});
		return true;
	}
	process(path: string) {
		fs.unlink(path, (error) => {
			if (this._exit)
				return;

			if (error) {
				this._exit = true;
				console.error("Can not delete "+path);
				this.emit("error", error);
			} else {
				this.cnt++;
				if (this.cnt == this.targets.length) {
					this._exit = true;
					this.emit("done");
				}
				//this.emit("done");
			}
		});
	}
}

class CopyTask extends Task {
	src: string;
	dist: string;
	remove: boolean;
	run() {
		var fs = <fs>require("fs");
		var rd = fs.createReadStream(this.src);
		var wr = fs.createWriteStream(this.dist);
		rd.on("error", (error) => {
			this.emit("error", error);
		});
		wr.on("error", (error) => {
			this.emit("error", error);
		});
		wr.on("close", (ex) => {
			if (! this.remove) {
				this.emit("done");
				return;
			}
			var unlink = new UnlinkTask(this.config);
			unlink.target = this.src;
			unlink.on("error", (error) => {
				this.emit("error", error);
			});
			unlink.on("done", () => {
				this.emit("done");
			});
			unlink.run();
		});
		rd.pipe(wr);
		return true;
	}
}

class MinifiedTask extends Task {
	src: string;
	dist: string;

	run() {
		var UglifyJS = require("uglify-js");
		var result = UglifyJS.minify(
			this.src
		);

		var fs = <fs>require("fs");
		var wr = fs.createWriteStream(this.dist);
		wr.on("error", (error) => {
			this.emit("error", error);
		});
		wr.on("close", (ex) => {
			this.emit("done");
		});

		wr.write(result.code);
		wr.end();
		
		return true;
	}
}

class DeployTask extends Task {
	sub_tasks: Task[];
	_exit: boolean;
	run() {
		var fs = <fs>require("fs");
		this.sub_tasks = [];
		var jgame_js = new CopyTask(this.config);
		jgame_js.src = path.join(this.config.paths.js, "jgame.js");
		jgame_js.dist = path.join(this.config.paths.base, "jgame.js");

		var jgame_d_ts = new CopyTask(this.config);
		jgame_d_ts.src = path.join(this.config.paths.ts, "jgame.d.ts");
		jgame_d_ts.dist = path.join(this.config.paths.lib, "jgame.d.ts");
		jgame_d_ts.remove = true;

		var jgame_min_js = new MinifiedTask(this.config);
		jgame_min_js.src = path.join(this.config.paths.js, "jgame.js");
		jgame_min_js.dist = path.join(this.config.paths.base, "jgame.min.js");

		this.sub_tasks.push(jgame_js);
		this.sub_tasks.push(jgame_d_ts);
		this.sub_tasks.push(jgame_min_js);

		for (var i = 0; i < this.sub_tasks.length; i++)
			this.processTask(this.sub_tasks[i]);

		return true;
	}

	processTask(task: Task) {
		task.on("error", (error) => {
			if (this._exit)
				return;
			this._exit = true;
			var mes = task.name + " error: " + error;
			console.error(mes);
			this.emit("error", mes);
		});

		task.on("done", (error) => {
			console.log("done!"+task.name+","+task["src"]);
			if (this._exit)
				return;
			this.sub_tasks.splice(this.sub_tasks.indexOf(task), 1);
			if (this.sub_tasks.length == 0) {
				this._exit = true;
				this.emit("done");
			}
		});

		return task.run();
	}
}

class TaskManager extends Task {
	tasks: Task[];
	taskIndex: number;

	init() {
		this.tasks = [];
	}

	addTask(task: Task) {
		this.tasks.push(task);
	}

	_finishTask(task: Task) {
		console.timeEnd(task.name);
		console.log("--------------------");
		console.log(" ");
		this.nextTask();
	}

	nextTask(): boolean {
		if (this.tasks.length == this.taskIndex) {
			this.emit("done", true);
			return false;
		}
		
		var task = this.tasks[this.taskIndex++];
		console.time(task.name);
		console.log("-----" + task.name + "-----");
		task.on("done", () => {
			this._finishTask(task);
		});
		task.on("error", () => {
			console.error(task.name + " is error");
			this._finishTask(task);
		});
		return task.run();
	}

	run() {
		this.taskIndex = 0;
		if (this.tasks.length == 0) {
			//this.emit("error", "no task/");
			throw "no task";
		}
		this.nextTask();
		return true;
	}
}

var main = () => {
	var argv = require("argv");
	argv.info("jgame.js builder\nSupported only \"node build deploy\" command.");
	argv.version("1.0");
	var args = argv.run();
	var mode = args.targets.length > 0 ? args.targets[0] : null;

	var modes = {
		deploy: []
	};
	if (!mode || !modes[mode]) {
		argv.help();
		return;
	}

	var config = new Config();
	var compiler = new CompileTask(config, "TypeScript Compiler");
	var define = new CompileTask(config, "Define Builder");
	define.dist = path.join(config.paths.ts, "jgame.js");
	define.declaration = true;
	define.comment = true;
	modes.deploy.push(compiler);
	modes.deploy.push(define);
	modes.deploy.push(new DeployTask(config, "Publisher"));
	modes.deploy.push(new DocumentTask(config, "Document Generator"));
	var cleaner = new UnlinkTask(config, "Cleaner");
	cleaner.targets = [
		path.join(config.paths.ts, "jgame.js")
	];
	modes.deploy.push(cleaner);

	var manager = new TaskManager(config);
	for (var i = 0; i < modes[mode].length; i++) {
		var task: Task = modes[mode][i];
		manager.addTask(task);
	}

	manager.run();
	manager.on("done", () => {
		console.error("finished.");
	});
	manager.on("error", () => {
		console.error("stopped.");
	});
};
main();