#include "player.h"

#define will_beat(play) ("\001\002\000"[play])
#define will_lose_to(play) ("\002\000\001"[play])

static int do_history(void) {
	int best,best_length,i,j,num;

	num = my_history[0];
	best = best_length = 0;
	for (i = num - 1; i > best_length; --i) {
		for (j = 0; j < i 
		 && my_history[num - j] == my_history[i - j]
		 && opp_history[num - j] == opp_history[i - j]; ++j) ;
		if (j > best_length) {
			best_length = j;
			best = i;
		}
	}

	return best;
}

struct stats {
	int sum[3];
};

static void reset_stats(struct stats *st) {
	st->sum[0] = st->sum[1] = st->sum[2] = 0;
}

static void add_stats(struct stats *st,int i) {
	st->sum[i]++;
}

static int max_stats(const struct stats *st,int *score) {
	int i,j = -1;
	for (i = 0; i < 3; ++i)
		if (st->sum[i] > *score) {
			*score = st->sum[i];
			j = i;
		}

	return j;
}

struct predict {
	struct stats my,opp;
	int my_last,opp_last;
};

static void reset_predict(struct predict *pred) {
	reset_stats(&pred->my);
	reset_stats(&pred->opp);
	pred->my_last = pred->opp_last = -1;
}

/* last: opponent's last move (-1 if none)
 | opp:  algorithm's prediction of opponent's next move
 | my:   what the algorithm would predict for our next move */
static void do_predict(struct predict *pred,int last,int my,int opp) {
	if (-1 != last) {
		add_stats(&pred->my,(3 + last - pred->my_last) % 3);
		add_stats(&pred->opp,(3 + last - pred->opp_last) % 3);
	}

	pred->my_last = my;
	pred->opp_last = opp;
}

static void scan_predict(struct predict *pred,int *best,int *move) {
	int i;
	i = max_stats(&pred->opp,best);
	if (-1 != i) *move = will_beat((pred->opp_last + i) % 3);
	i = max_stats(&pred->my,best);
	if (-1 != i) *move = will_beat((pred->my_last + i) % 3);
}

struct iocaine
{
	struct predict pr_history,pr_freq,pr_random;
	struct stats my_stats,opp_stats;
};

static int iocaine(struct iocaine *i) {
	const int num = my_history[0];
	const int last = (num > 0) ? opp_history[num] : -1;
	const int guess = biased_roshambo(1.0/3.0,1.0/3.0);

	if (0 == num) {
		reset_predict(&i->pr_history);
		reset_predict(&i->pr_freq);
		reset_predict(&i->pr_random);
		reset_stats(&i->my_stats);
		reset_stats(&i->opp_stats);
	} else {
		add_stats(&i->my_stats,my_history[num]);
		add_stats(&i->opp_stats,opp_history[num]);
	}

	{
		const int best = do_history();
		if (0 == best)
			do_predict(&i->pr_history,last,guess,guess);
		else
			do_predict(&i->pr_history,last,
			           my_history[best + 1],opp_history[best + 1]);
	}

	do_predict(&i->pr_random,last,guess,guess);

	{
		int my_best = -1,opp_best = -1;
		int my_freq = max_stats(&i->my_stats,&my_best);
		int opp_freq = max_stats(&i->opp_stats,&opp_best);
		do_predict(&i->pr_freq,last,
		           (-1 == my_freq) ? guess : my_freq,
		           (-1 == opp_freq) ? guess : opp_freq);
	}

	{
		int score = -1,move = -1;
		scan_predict(&i->pr_history,&score,&move);
		scan_predict(&i->pr_freq,&score,&move);
		scan_predict(&i->pr_random,&score,&move);
		return move;
	}
}

int iocainebot(void)
{
	static struct iocaine p;
	return iocaine(&p);
}
