import core.stdc.stdlib;

import std.conv;
import std.exception;
import std.format;
import std.getopt;
import std.range;
import std.stdio;
import std.variant;

import day1;
import day2;
import day3;
import day4;
import day5;
import day6;
import day7;
import day8;
import day9;
import day10;
import day11;
import day12;
import day13;
import day14;
import day15;
import day16;
import dayutil;

immutable string progName = "aoc-2020";

version(Unix) {
	extern(C) int isatty(int);
}

Variant function(int, File, bool, string[])[] programs = [
	&day1.run,
	&day2.run,
	&day3.run,
	&day4.run,
	&day5.run,
	&day6.run,
	&day7.run,
	&day8.run,
	&day9.run,
	&day10.run,
	&day11.run,
	&day12.run,
	&day13.run,
	&day14.run,
	&day15.run,
	&day16.run,
];

void printUsage(string message = null) {
	import core.runtime;
	string name = Runtime.args[0];
	stderr.writeln("USAGE: %s [OPTIONS...] DAY PART [INPUT_FILE] (to run a specific day)".format(name));
	stderr.writeln("   OR: %s [OPTIONS...] all                   (to run each day)".format(name));
	stderr.writeln();
	stderr.writeln("       DAY             = int between 1 and 25 (inclusive), specifiying the day to run");
	stderr.writeln("       PART            = int between 1 and 2  (inclusive), specifiying the part to run");
	stderr.writeln("       INPUT_FILE      = file to read from. '-' for stdin. If not specified, use in/[DAY].txt");
	stderr.writeln();
	stderr.writeln("OPTIONS:");
	stderr.writeln("       --bigboy, -B    = use bigboy challenges");
	stderr.writeln("       --benchmark, -b = record time it took to execute ");
	if (message != null) {
		stderr.writeln();
		stderr.writeln(message);
	}
	exit(-1);
}

File getDefaultFile(int day, bool bigboy) {
	if (bigboy) {
		return File("in/bigboy/%d.txt".format(day), "rb");
	} else {
		return File("in/%d.txt".format(day), "rb");
	}
}

void main(string[] args) {
	bool bigboy = false;
	bool benchmark = false;

	try {
		auto opts = getopt(args,
			"bigboy|B", &bigboy,
			"benchmark|b", &benchmark);
		if (opts.helpWanted) {
			printUsage();
		}
	} catch (GetOptException e) {
		printUsage(e.msg);
	}
	if (args.length < 2) {
		printUsage();
	}

	bool runAll = false;
	if (args[1] == "all") {
		/+version(Posix) {
			bool tty = cast(bool) isatty(stdout.fileNo);
		} else {+/
			bool tty = false;
		//}
		runAll = true;
		writeln(tty);
		writeln("STATUS\tDAY\tPART\tRESULT");
		foreach (dayNo, day; programs.enumerate(1)) {
			File file = getDefaultFile(dayNo, bigboy);
			foreach(part; 1..3) {
				if (tty) {
					write("RUNNING\t%d\t%d\t...\r".format(dayNo, part));
					stdout.flush();
				}
				Variant result = day(part, file, bigboy, []);
				writeln("DONE   \t%d\t%d\t%s   ".format(dayNo, part, std.conv.to!string(result)));
				file.rewind();
			}
		}
	} else {
		if (args.length < 3) {
			printUsage();
		}

		int day;
		try {
			day = to!int(args[1]);
		} catch (ConvException e) {
			printUsage("DAY is not an integer");
		}

		if (day <= 0 || day > programs.length) {
			printUsage("DAY must be between 1 and %d".format(programs.length));
		}

		int part;
		try {
			part = to!int(args[2]);
		} catch (ConvException e) {
			printUsage("PART is not an integer");
		}

		File file;
		try {
			if (args.length < 4)  {
				file = getDefaultFile(day, bigboy);
			} else if(args[3] == "-") {
				file = stdin;
			} else {
				file = File(args[3], "rb");
			}
		} catch (ErrnoException e) {
			printUsage("Error %d while opening input file: %s".format(e.errno, e.message));

		}
		try {
			Variant result = programs[day - 1](part, file, bigboy, args[3..$]);
			writeln(result);
		} catch(ArgumentException e) {
			printUsage(e.msg);
		} catch(Exception e) {
			stderr.writeln("Fatal error occurred: " ~ e.msg);
			exit(-1);
		}
	}
}
