#include "acro.h"

srec *scores = NULL, *gscores = NULL;
prec *player = NULL;
vrec *voter = NULL;
grec *game = NULL;

int Acro_Init(IrcCommandDll **intp, Function_ptr *global_table)
{
	initialize_module("Acromania");
	add_module_proc(RAW_PROC, "acro", "PRIVMSG", NULL, 0, 0, acro_main, NULL);
	add_module_proc(COMMAND_PROC, "acro", "scores", NULL, 0, 0, put_scores, NULL);

	read_scores();
	if (!game)
		game = init_acro(game);
	put_it("BitchX Acromania dll v0.9b by By-Tor loaded...");
	return 0;
}

static int acro_main (char *comm, char *from, char *userhost, char **args)
{
	if (*args[1] && !strncasecmp(args[1], "acro ", 5) && !strcasecmp(args[0], get_server_nickname(from_server)))
	{
		PasteArgs(args, 1);
		switch(game->progress) {
			case 0:
				send_to_server("notice %s :Sorry, no game in progress.", from);
				break;
			case 1:
				if (valid_acro(game, args[1]+5))
					player = take_acro(game, player, from, userhost, args[1]+5);
				else
					send_to_server("PRIVMSG %s :Invalid acronym", from);
				break;
			case 2:
				voter = take_vote(game, voter, player, from, userhost, args[1]+5);
				break;
		}
		return 1;
	}
	if (*args[0] == '#' && *args[1] && !my_stricmp(args[1], "start"))
	{
		if (game && game->progress)
		{
			send_to_server("PRIVMSG %s :Game already in progress, %s. Puzzle is: %s", from, from, game->nym);
			return 0;
		}
		make_acro(game);
		game->progress = 1;
		send_to_server("PRIVMSG %s :Round %d", args[0], game->round);
		send_to_server("PRIVMSG %s :The acronym for this round is %s. You have 60 seconds.", args[0], game->nym);
		send_to_server("PRIVMSG %s :/msg %s \"acro <your answer>\"", args[0], get_server_nickname(from_server));
		add_timer(0, "Acro", 60 * 1000, 1, warn_acro, m_sprintf("%s", args[0]), NULL, -1, "acro");
	}
	return 0;
}


BUILT_IN_DLL(put_scores)
{
	srec *stmp = NULL;
	if (scores) {
		put_it("Start Normal scores");
		for (stmp = scores; stmp; stmp = stmp->next)
			put_it("Nick: %s\tScore: %lu", stmp->nick, stmp->score);
		put_it("End scores");
	}
/*
	if (gscores) {
		put_it("GLOBAL SCORES START HERE!"); 
		for (stmp = gscores; stmp; stmp = stmp->next)
			put_it("Nick: %s\tScore %lu", stmp->nick, stmp->score);
		put_it("GLOBAL SCORES END HERE!"); 
	}
*/
}

int warn_acro(void *arg, char *subarg)
{
	char *chan = arg;

	send_to_server("PRIVMSG %s :30 seconds! Puzzle is: %s", chan, game->nym);
	add_timer(0, "Acro", 30 * 1000, 1, start_vote, m_sprintf("%s", chan), NULL, -1, "acro");
	return 0;
}

int start_vote(void *arg, char *subarg)
{
	char *chan = arg;

	if (game->players >= MINPLAYERS)
	{
		send_to_server("PRIVMSG %s :Time's up, lets vote!\r\nPRIVMSG %s :/msg %s \"acro #\" to vote", chan, chan, get_server_nickname(from_server));
		game->progress = 2;
		show_acros(player, chan);
		add_timer(0, "Acro", 30 * 1000, 1, warn_vote, m_sprintf("%s", chan), NULL, -1, "acro");
	}
	else if (game->extended < EXTENSIONS)
	{
		send_to_server("PRIVMSG %s :Aww, too few players! Puzzle is: %s", chan, game->nym);
		add_timer(0, "Acro", 30 * 1000, 1, start_vote, m_sprintf("%s", chan), NULL, -1, "acro");
		game->extended++;
	}
	else
	{
		send_to_server("PRIVMSG %s :Not enough players, ending game...", chan);
		free_round(&player, &voter);
		game->players = 0;
		game->progress = 0;
	}
	return 0;
}
 
int warn_vote(void *arg, char *subarg)
{
	char *chan = arg;

	send_to_server("PRIVMSG %s :30 seconds left to vote!", chan);
	add_timer(0, "Acro", 30 * 1000, 1, end_voting, m_sprintf("%s", chan), NULL, -1, "acro");
	return 0;
}

int end_voting(void *arg, char *subarg)
{
	char *chan = arg;

	put_it("END_VOTING");
	send_to_server("PRIVMSG %s :Voting complete, sorting scores...", chan);
	gscores = end_vote(voter, player, gscores);
	scores = end_vote(voter, player, scores);
	write_scores(gscores);
	show_scores(game, scores, gscores, chan);
	free_round(&player, &voter);
	if (player)
	{
		put_it("Player was non-null!!");
		player = NULL;
	}
	if (voter)
	{
		put_it("voter was non-null!!");
		voter = NULL;
	}
	if (game->round < game->rounds)
	{
		init_acro(game);
		send_to_server("PRIVMSG %s :Round %d", chan, game->round);
		send_to_server("PRIVMSG %s :The acronym for this round is %s. You have 60 seconds.", chan, game->nym);
		send_to_server("PRIVMSG %s :/msg %s \"acro <your answer>\"", chan, get_server_nickname(from_server));
		add_timer(0, "Acro", 60 * 1000, 1, warn_acro, m_sprintf("%s", chan), NULL, -1, "acro");
	}		
	else
	{
		game->round = 1;
		game->progress = 0;
		free_score(&scores);
		new_free(&game->nym);
		init_acro(game);
	}
	return 0;
}

grec *init_acro(grec *gtmp)
{
	if (!gtmp)
		gtmp = (grec *)new_malloc(sizeof(grec));
	if (gtmp->nym)
	{
		gtmp->progress = 1;
		gtmp->round++;
		gtmp->players = 0;
		gtmp->extended = 0;
		new_free(&gtmp->nym);
		make_acro(gtmp);
	}
	else
	{	
		gtmp->progress = 0;
		gtmp->round = 1;
		gtmp->rounds = ROUNDS;
		gtmp->players = 0;
		gtmp->extended = 0;
		gtmp->top = TOP;
		gtmp->maxlen = MAXLEN;
		gtmp->nym = NULL;
	}
	return gtmp;
}

void make_acro(grec *acro)
{
	int i, r;
	char *p;
	if (acro->nym)
		new_free(&acro->nym);
	r = MINLENGTH+(int)((float) (1+MAXLENGTH-MINLENGTH)*random()/(RAND_MAX+1.0));
	p = acro->nym = (char *)new_malloc(r+1);
	for (i = 0; i < r; i++)
		*p++ = letters[(int)((float)(strlen(letters))*random()/(RAND_MAX+1.0))];
	return;
}

int valid_acro(grec *acro, char *p)
{
	int spaces = 0, len = 0, gotsp = 1;
	if (!p)
		return 0;
	if (!acro)
		return 0;
	for (; *p; p++)
	{
		if (isalpha(*p))
		{
			len++;
			if (gotsp && (toupper(*p) != acro->nym[spaces]))
				return 0;
			gotsp = 0;
		}
		else if (*p == 32)
		{
			if (!gotsp)
			{
				spaces++;
				gotsp = 1;
			}
		}
		else return 0;
	}
	if ((len > strlen(acro->nym)) && (spaces+1 == strlen(acro->nym)))
		return 1;
	return 0;
}

prec *take_acro(grec *acro, prec *players, char *nick, char *host, char *stuff)
{
	prec *tmp, *last = NULL;
	if (!players)
	{
		players = new_malloc(sizeof(prec));
		players->nick = m_strdup(nick);
		players->host = m_strdup(host);
		players->acro = m_strdup(stuff);
		send_to_server("PRIVMSG %s :Answer set to \"%s\"\r\nPRIVMSG %s :You are player #%d", nick, stuff, nick, ++acro->players);
		return players;
	}
	for (tmp = players; tmp; tmp = tmp->next)
	{
		if (tmp->host && !strcasecmp(host, tmp->host))
		{
			if (tmp->acro && !strcasecmp(stuff, tmp->acro))
			{
				send_to_server("PRIVMSG %s :Your answer is alreay \"%s\"", nick, stuff);
				return players;
			}
			else if (tmp->last && !strcasecmp(stuff, tmp->last))
			{
				RESIZE(tmp->acro, char, strlen(stuff)+1);
				strcpy(tmp->acro, stuff);
				send_to_server("PRIVMSG %s :Answer changed to \"%s\"", nick, stuff);
				new_free(&tmp->last);
				return players;
			}
			else {
				tmp->last = m_strdup(stuff);
				send_to_server("PRIVMSG %s :You already submitted an answer, submit once more to change.", nick);
				return players;
			}
		}
		last = tmp;
	}
	if (acro->players < MAXPLAYERS && last)
	{
		tmp = last->next = new_malloc(sizeof(prec));
		tmp->nick = m_strdup(nick);
		tmp->host = m_strdup(host);
		tmp->acro = m_strdup(stuff);
		send_to_server("PRIVMSG %s :Answer set to \"%s\"\r\nPRIVMSG %s :You are player #%d", nick, stuff, nick, ++acro->players);
	}
	else
		send_to_server("PRIVMSG %s :Sorry, too many players.", nick);
	return players;
}

vrec *take_vote(grec *acro, vrec *voters, prec *players, char *nick, char *host, char *num)
{
	vrec *tmp = NULL, *last = voters;
	int i;
  if (atoi(num) > acro->players || atoi(num) < 1)
	{
		send_to_server("PRIVMSG %s :No such answer...", nick);
		return voters;
	}
	for (i = 1; i < atoi(num); i++)
		players = players->next;
	if (players->nick && nick && !strcasecmp(players->nick, nick))
	{
		send_to_server("PRIVMSG %s :You can't vote for yourself.", nick);
		return voters;
	}
	if (!voters)
	{
		voters = new_malloc(sizeof(vrec));
		voters->nick = m_strdup(nick);
		voters->host = m_strdup(host);
		voters->vote = atoi(num)-1;
		send_to_server("PRIVMSG %s :Vote recorded...", nick);
		return voters;
	}
	for (tmp = voters; tmp; tmp = tmp->next)
	{
		if ((tmp->nick && !strcasecmp(tmp->nick, nick)) || (tmp->host && !strcasecmp(tmp->host, host)))
		{
			send_to_server("PRIVMSG %s :You already voted.", nick);
			return voters;
		}
		last = tmp;
	}
	if (last && !last->next)
	{
		last = last->next = new_malloc(sizeof(vrec));
		last->nick = m_strdup(nick);
		last->host = m_strdup(host);
		last->vote = atoi(num)-1;
		send_to_server("PRIVMSG %s :Vote recorded...", nick);
	}
	return voters;
}

void read_scores(void)
{
	FILE *infile;
	srec *record;
	unsigned long score;
	char nick[64];

	infile = fopen(SCOREFILE, "r");
	if (infile == NULL)
		return;

	while (fscanf(infile, " %63[^ ,] , %lu", nick, &score) == 2)
	{
		record = new_malloc(sizeof(srec));
		record->nick = m_strdup(nick);
		record->score = score;
		add_to_list((List **)&gscores, (List *)record);
	}

	fclose(infile);
}

int write_scores(srec *tmp)
{
	FILE *sf;

	if (!tmp)
		return 0;

	sf = fopen(SCOREFILE, "w");
	if (!sf)
		return 0;

	for (; tmp; tmp = tmp->next)
		if (tmp->score > 0)
			fprintf(sf, "%s,%lu\n", tmp->nick, tmp->score);

	fclose(sf);
	return 1;
}

/*
 * Here we take the votes from the current round and translate them into
 * scores for the players. gscores is the score per game, while scores
 * is the overall score for all games... So we call it with a ptr to the
 * score record we want to update and it makes it more useful :)
 * The dual for () loop is probably inefficient, but its all I can think of
 * right now ;), besides we aren't talking about 10,000 members here

 * Eventually I'll convert this to support variable arg lengths and thus
 * update for multiple scores with one call...
 * This function is a mess right now, I'll clean it up later.
*/

srec *end_vote(vrec *voters, prec *players, srec *stmp)
{
	int i, gotscore;
	vrec *tmp = NULL;
	prec *tmp2 = NULL;
	srec *tmp3 = NULL, *last;
	if (!stmp && voters && players)
		stmp = new_malloc(sizeof(srec));
	for (tmp = voters; tmp; tmp = tmp->next)
	{
		gotscore = 0;
		tmp2 = players;
		for (i = 0; i < tmp->vote; i++)
			tmp2 = tmp2->next;
		if (stmp && !stmp->nick)
		{
			stmp->nick = m_strdup(tmp2->nick);
			stmp->score = 1;
			continue;
		}
		for (tmp3 = last = stmp; tmp3; tmp3 = tmp3->next)
		{
			if (tmp2->nick && tmp3->nick && !strcasecmp(tmp2->nick, tmp3->nick))
			{
				tmp3->score++;
				gotscore = 1;
				break;
			}
			last = tmp3;
		}
		if (!gotscore)
		{
			tmp3 = last->next = new_malloc(sizeof(srec));
			tmp3->nick = m_strdup(tmp2->nick);
			tmp3->score = 1;
		}
	}
	return stmp;
}

/* 
 * Here we sort deeze babys ... The return values are "opposite" so we can
 * sort in descending order instead of ascending... Stupid declarations had 
 * me going for a while, no wonder it didnt sort right at first! :)
*/

static int comp_score(const void *a, const void *b)
{
  srec * const *one = a;
  srec * const *two = b;

  if ((*one)->score > (*two)->score)
    return -1;
  if ((*one)->score < (*two)->score)
    return 1;
  else
    return strcasecmp((*one)->nick, (*two)->nick);
}

srec *sort_scores(srec *stmp)
{
	size_t n = 0;
	srec *tmp;
	srec **sort, **ctmp;
	
	if (!stmp->next)
		return stmp;

	for (tmp = stmp; tmp; tmp = tmp->next)
		n++;
	ctmp = sort = (srec **)new_malloc(n * sizeof sort[0]);

	put_it("START SORTING");
	put_scores(NULL, NULL, NULL, NULL, NULL);

	for (tmp = stmp; tmp; tmp = tmp->next)
		*ctmp++ = tmp;
	qsort(sort, n, sizeof sort[0], comp_score);

	tmp = sort[0];
	for (ctmp = &sort[1]; ctmp < &sort[n]; ctmp++)
	{
		tmp->next = *ctmp;
		tmp = *ctmp;
	}
	tmp->next = NULL;

	tmp = sort[0];
	new_free(&sort);
	put_scores(NULL, NULL, NULL, NULL, NULL);
	put_it("END SCORES");
 	return tmp;
}

void show_acros(prec *players, char *chan)
{
	prec *tmp;
	int i = 1;
	if (!players)
		return;
	for (tmp = players; tmp; tmp = tmp->next)
		send_to_server("PRIVMSG %s :%2d: %s", chan, i++, tmp->acro);
} 

void show_scores(grec *acro, srec *score, srec *gscore, char *chan)
{
	int i;
	if (score)
		score = sort_scores(score);
	if (gscore && (acro->round >= acro->rounds))
		gscore = sort_scores(gscore);
	if (acro->round < acro->rounds)
		send_to_server("PRIVMSG %s :Scores for round %d\r\nPRIVMSG %s :Nick        Score\r\nPRIVMSG %s :-----------------", chan, acro->round, chan, chan);
	else
		send_to_server("PRIVMSG %s :Game over, tallying final scores...\r\nPRIVMSG %s :   Game Score          Overall Score\r\nPRIVMSG %s :Nick        Score    Nick        Score\r\nPRIVMSG %s :-----------------    -----------------", chan, chan, chan, chan);
	for (i = 0; i < acro->top && (score || gscore); i++)
	{
		if ((acro->round < acro->rounds) && score)
		{
			send_to_server("PRIVMSG %s :%-9s    %lu", chan, score->nick, score->score);
			score = score->next;
		}
		else if (acro->round == acro->rounds && (score || gscore)) 
		{
			if (!score && gscore)
			{
				send_to_server("PRIVMSG %s :                     %-9s   %lu", chan, gscore->nick, gscore->score);
				gscore = gscore->next;
			}
			else if (score && !gscore)
			{
				send_to_server("PRIVMSG %s :%-9s    %lu", chan, score->nick, score->score);
				score = score->next;
			}
			else if (gscore && score)
			{
				send_to_server("PRIVMSG %s :%-9s    %-5lu   %-9s    %lu", chan, score->nick, score->score, gscore->nick, gscore->score);
				gscore = gscore->next;
				score = score->next;
			}
		}
	}
} 

void free_round(prec **players, vrec **voters)
{
	prec *ptmp, *ptmp2;
	vrec *vtmp, *vtmp2;
	if (players && *players) {
		for (ptmp = *players; ptmp;)
		{
			if (ptmp->nick)
				new_free(&ptmp->nick);
			if (ptmp->host)
  			new_free(&ptmp->host);
  		if (ptmp->acro)
  			new_free(&ptmp->acro);
  		if (ptmp->last)
  			new_free(&ptmp->last);
  		if (ptmp->next)
  			ptmp2 = ptmp->next;
  		else
  			ptmp2 = NULL;
  		new_free(&ptmp);
  		ptmp = ptmp2;
  	}
  	*players = NULL;
  }
  if (voters && *voters) {
  	for (vtmp = *voters; vtmp;)
  	{
  		if (vtmp->nick)
  			new_free(&vtmp->nick);
  		if (vtmp->host)
  			new_free(&vtmp->host);
  		if (vtmp->next)
  			vtmp2 = vtmp->next;
  		else
  			vtmp2 = NULL;
  		new_free(&vtmp);
  		vtmp = vtmp2;
  	}
  	*voters = NULL;
  }
}

void free_score(srec **score)
{
	srec *stmp, *stmp2;
	for (stmp = *score; stmp;)
	{
		if (stmp->nick)
			new_free(&stmp->nick);
		if (stmp->next)
			stmp2 = stmp->next;
		else
			stmp2 = NULL;
		new_free(&stmp);
		stmp = stmp2;
	}
	*score = NULL;
}
