import ae.sys.data;
import ae.sys.datamm;
import ae.utils.array;
import ae.utils.meta;

import std.algorithm.iteration;
import std.algorithm.searching;
import std.conv;
import std.exception;
import std.file;
import std.stdio;
import std.string;

import driver;
import game;
import gamelogic;
import graph;
import packing;

static struct GraphDriver
{
	static GraphDriver create()
	{
		GraphDriver d;
		d.scores = mapFile(scoreFileName(), MmMode.read).asDataOf!PackedScore;
		return d;
	}

	TData!PackedScore scores;

	enum interactive = false;

	void log(scope string delegate() message) /*nothrow @nogc*/ {}

	Score recurse(const ref Game game) nothrow @nogc
	{
		// return expand.expand(game, &this);
		return scores[game.packGame()].unpackScore;
	}

	alias GetScore(T) = Score delegate(T) /*nothrow @nogc*/;

	// alias choice = graphChoice!GetScore;

	bool topLevel = true;
	string bestMove; Score bestScore;

	Score choice(ChoiceMode mode, T)(
		scope string delegate() description,
		scope Score delegate(T) /*nothrow @nogc*/ getScore,
		scope string delegate(T) getDescription,
		scope const(T)[] items...
	) //nothrow @nogc
	{
		alias impl = graphChoice!GetScore;

		if (topLevel)
		{
			static if (mode == ChoiceMode.best && (is(T == Move) || is(T == Bet)))
			{
				topLevel = false; scope(exit) topLevel = true;
				bestScore = 0;
				foreach (item; items)
				{
					auto score = getScore(item);
					if (score > bestScore)
					{
						bestScore = score;
						bestMove = getDescription(item);
					}
				}
				return Score.nan; // We got what we needed
			}
			else
				assert(false);
		}
		else
			return impl!(mode, T)(description, getScore, getDescription, items);
	}

	alias gameOver = graphGameOver;
}

void main()
{
	Game game;

	auto lines = "game.txt".readText.split1("\n");
	foreach (i, line; lines)
	{
		if (i % 3 == 2)
			enforce(line.length == 0, "Expected empty line");
		auto character = i % 3 == 0 ? Character.player : Character.dealer;
		Bet bet = Bet.minimum;
		if (character == Character.player && line.skipOver("M"))
			bet = Bet.maximum;
		auto cards = line.strip.split.map!(to!Card);
		// if (character == Character.player)
		// 	enforce(cards.length, "Unexpected empty line");

		foreach (card; cards)
		{
			enforce(game.deck[card], "No more cards with value %d".format(card));
			game.deck[card]--;
			if (game.deck[].sum == 0)
				game.deck = fullDeck; // shuffle
			if (character == Character.player && i + 1 == lines.length)
			{
				assert(character == Character.player);
				game.state = Game.State.playerTurn;
				game.bet = bet;
				game.hand.numCards++;
				game.hand.totalValue += card;
			}
		}
	}

	if (lines.length % 3 == 1)
	{
		loadTables();
		auto driver = GraphDriver.create();
		expand(game, &driver);

		writefln("Best move: %s (%s%%)", driver.bestMove, driver.bestScore * 100);
	}
	else
		writeln("OK (awaiting data)");
}