package ;

import openfl.Assets;
import flixel.util.FlxRandom;
import haxe.xml.Fast;
using StringTools;

class Job
{
	public var id:String;
	public var name:Name;
	public var alertText:String;
	public var terminalText:String;
	public var formHash = new Map<String,String>();
	public var fieldLists = new Map<String, Array<String>>();

	var spec:Spec;
	static var kNumValsPerField = 10;

	public function new(id_:String, feedIndex:Int=0)
	{
		id = id_;
		spec = Spec.get(id);

		var feedIndexStr = Std.string(feedIndex).lpad("0", 3) + " ";

		alertText = feedIndexStr + spec.display;

		// fill in correct values
		var correctVals = new Map<String, Val>();
		for (line in spec.lines)
		{

			if (line.id == "name")
			{
				name = Name.next(FlxRandom.float() > 0.3, true);

				correctVals[line.id] = Val.fromName(name);
				correctVals["family"] = new Val("", name.family);
			}
			else
			{
				// pick a random val for the correct answer
				correctVals[line.id] = line.getRandomVal();
			}
		}

		// add rank to name
		if (correctVals.exists("rank"))
		{
			var nameAndRank = correctVals["rank"].form + " " + correctVals["name"].form;
			correctVals["name"].form = nameAndRank;
		}

		// build terminalText
		terminalText = feedIndexStr + spec.display + "\n";
		for (line in spec.lines)
		{
			var str = line.display.toUpperCase() + correctVals[line.id].term.toUpperCase();
			if (str.length > Terminal.kMaxLineWidth) str = str.substr(0, Terminal.kMaxLineWidth-3) + "...";
			terminalText += str + "\n";
		}

		// build formHash
		for (lineId in correctVals.keys())
		{
			formHash[lineId] = correctVals[lineId].form;
		}
	}

	public function getFieldList(fieldId:String, otherJobs:Array<Job>):Array<String>
	{
		var fieldList = new Array<String>();

		// add correct value if possible
		if (formHash.exists(fieldId))
		{
			fieldList.push(formHash[fieldId]);
		}

		// add correct values from other jobs
		for (otherJob in otherJobs)
		{
			if (fieldList.length >= ComboBox.kMaxItems) break;

			if (otherJob.formHash.exists(fieldId))
			{
				var str = otherJob.formHash[fieldId];
				if (fieldList.indexOf(str) < 0) fieldList.push(str);
			}
		}

			// fill in the rest with random values
//			var line = spec.findLine(lineId);
//			if (lineId == "family")
//			{
//				for (i in fieldList.length...kNumValsPerField)
//				{
//					var name = Name.next(FlxRandom.float() > 0.5, false);
//					fieldList.push(name.family);
//				}
//			}
//			else if (line != null)
//			{
//				var i = 0;
//				while (fieldList.length < kNumValsPerField)
//				{
//					var str = line.getRandomVal(spec.findLine("rank")).form;
//					if (fieldList.indexOf(str) < 0) fieldList.push(str);
//					if (++i > 100) break;
//				}
//			}

		// sort
		fieldList.sort(function(a,b) return Reflect.compare(a, b));

		return fieldList;
	}

	public function matchesFormHash(hash:Map<String,String>):Bool
	{
		// only check one-way
		for (k in hash.keys())
		{
			if (formHash.get(k) != hash[k]) return false;
		}
		return true;
	}
}

class Spec
{
	public var id:String;
	public var display:String;
	public var lines = new Array<Line>();

	public function new(fast:Fast)
	{
		id = fast.att.id;
		display = fast.att.display;

		for (d in fast.nodes.line)
		{
			var line = new Line(d);
			lines.push(line);
		}
	}

	public function findLine(lineId:String):Line
	{
		for (line in lines)
		{
			if (line.id == lineId) return line;
		}
		return null;
	}

	static var specs:Map<String, Spec>;

	@:isVar public static var allIds(get,null):List<String>;
	static function get_allIds()
	{
		if (specs == null) loadSpecs();
		return allIds;
	}

	public static function get(id:String):Spec
	{
		if (specs == null) loadSpecs();
		return specs[id];
	}

	static function loadSpecs()
	{
		var xml = Xml.parse(Assets.getText("assets/data/Jobs.xml"));
		var fast = new Fast(xml.firstElement());
		specs = new Map<String, Spec>();
		allIds = new List<String>();

		for (s in fast.nodes.job)
		{
			specs[s.att.id] = new Spec(s);
			allIds.push(s.att.id);
		}
	}
}

class Line
{
	public var id:String;
	public var display:String;
	public var dateIsFromFuture:Bool = false;
	public var dateIncludesHour:Bool = false;
	public var vals = new Array<Val>();

	public function new(fast:Fast)
	{
		id = fast.att.id;
		display = fast.att.display;
		dateIsFromFuture = fast.has.future && fast.att.future == "true";
		dateIncludesHour = fast.has.hour && fast.att.hour == "true";

		for (v in fast.nodes.val)
		{
			var val = Val.fromFast(v);
			vals.push(val);
		}
	}

	public function getRandomVal(rankLine:Line=null):Val
	{
		if (id == "date")
		{
			return Val.makeDate(dateIsFromFuture, dateIncludesHour);
		}
		else if (id == "name")
		{
			return Val.fromName(Name.next(FlxRandom.float() > 0.5, false), (rankLine != null) ? rankLine.getRandomVal().form : null);
		}
		else
		{
			// @@ use weighted random
			return vals[Std.random(vals.length)];
		}
	}
}

class Val
{
	public var term:String;
	public var form:String;

	public function new(term_:String, form_:String)
	{
		term = term_;
		form = form_;
	}

	public static function fromFast(fast:Fast):Val
	{
		if (fast.has.both)
			return new Val(fast.att.both, fast.att.both);
		else
			return new Val(fast.att.term, fast.att.form);
	}

	public static function fromName(name:Name, rank:String=null):Val
	{
		return new Val(name.full, rank != null ? (rank + " " + name.full) : name.full);
	}

	static var months = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ];
	static var numDays = [ 31, 			28, 		31, 	30, 	31, 	30,		31,		31,			30,			31,			30,		31 ];

	public static function makeDate(future:Bool, includeHour:Bool):Val
	{
		// current date is 3/21/1925
		var day = 21;
		var month = 3;
		var year = 1925;
		if (future)
		{
			// up to 31 days in the future
			day += 1 + Std.random(30);
			while (day > numDays[month-1])
			{
				day -= numDays[month-1];
				month += 1;
			}
		}
		else
		{
			// today or yesterday
			if (FlxRandom.float() > 0.5)
			{
				day -= 1;
			}
		}

		var shortDate = Std.string(month).lpad("0", 2) + "/" + Std.string(day).lpad("0", 2) + "/" + year;
		var longDate = months[month-1] + " " + day + ", " + year;

		if (includeHour)
		{
			var hour = Std.random(24);
			var shortHour = Std.string(hour).lpad("0", 2) + ":00";
			var longHour;
			if (hour == 0) longHour = "12:00AM";
			else if (hour <= 12) longHour = hour + ":00AM";
			else longHour = (hour-12) + ":00PM";

			return new Val(shortDate + " - " + shortHour, longDate + " at " + longHour);
		}
		else
		{
			return new Val(shortDate, longDate);
		}
	}
}






