/* hiscore.m,
 *
 * Handles high score stuff.  High scores come from two sources, a
 * global score file and a local one.  Always try to use the global
 * one, but if we can't read/write from it, as a last resort use the
 * local (user-owned) one.
 */

#include <errno.h>
#include <objc/Object.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "common.h"
#include "hiscore.h"


#ifndef PATH_MAX
# define PATH_MAX	512
#endif


#define HIGH_SCORE_FILENAME	"scores.txt"


high_score_t high_score_item[NUM_HIGH_SCORE_ITEMS];

static high_score_t default_scores[NUM_HIGH_SCORE_ITEMS] = {
    { "Fondue Knight",	       1116170, GLOBAL_HIGH_SCORE }, /* I'm number 1! */
    { "Domino",		       1014380, GLOBAL_HIGH_SCORE }, /* 2 */
    { "Ella Megalast",		930380, GLOBAL_HIGH_SCORE }, /* 3 */
    { "Salvador",		797200, GLOBAL_HIGH_SCORE }, /* 4 */
    { "Ladybird",		644310, GLOBAL_HIGH_SCORE }, /* 5 */
    { "Isobel",			100000, GLOBAL_HIGH_SCORE }, /* 6 */
    { "Mizake",			 75000, GLOBAL_HIGH_SCORE }, /* 7 */
    { "Naughtius Maximus",	 50000, GLOBAL_HIGH_SCORE }, /* 8 */
    { "Poor Leno",		 25000, GLOBAL_HIGH_SCORE }, /* 9 */
    { "Passive Aggressive",	   100, GLOBAL_HIGH_SCORE }  /* 10 */
};

/*------------------------------------------------------------*/

BOOL is_new_high_score(int score)
{
    int i;

    for (i = 0; i < NUM_HIGH_SCORE_ITEMS; i++)
	if (score > high_score_item[i].score)
	    return YES;

    return NO;
}

void update_high_score(const char *name, int score,
		       enum HIGH_SCORE_SOURCE source)
{
    int i, j;

    /* Find the item to replace. */
    for (i = 0; i < NUM_HIGH_SCORE_ITEMS-1; i++) 
	if (score > high_score_item[i].score)
	    break;

    /* Free the lowest high_score. */
    if (high_score_item[NUM_HIGH_SCORE_ITEMS-1].name)
	free(high_score_item[NUM_HIGH_SCORE_ITEMS-1].name);

    /* Shuffle everything down. */
    for (j = NUM_HIGH_SCORE_ITEMS-1; j > i; j--) {
	high_score_item[j].name   = high_score_item[j-1].name;
	high_score_item[j].score  = high_score_item[j-1].score;
	high_score_item[j].source = high_score_item[j-1].source;
    }

    high_score_item[i].name   = strdup(name);
    high_score_item[i].score  = score;
    high_score_item[i].source = source;
}

/*--------------------------------------------------------------*/

char *local_high_score_filename(char *str, size_t sz)
{
    char *home = getenv(HOME_PATH_ENV);

    snprintf(str, sz, "%s/%s/%s",
	     home, RAIDEM_CONFIG_PATHNAME, HIGH_SCORE_FILENAME);
    return str;
}

FILE *open_local_high_score_file(char *mode)
{
    char filename[PATH_MAX];
    local_high_score_filename(filename, sizeof filename);
    return fopen(filename, mode);
}

FILE *open_global_high_score_file(char *mode)
{
#ifdef __MINGW__
    char filename[PATH_MAX];
    snprintf(filename, sizeof filename, "%s/%s",
	     raid_binary_directory, HIGH_SCORE_FILENAME);
#else  /* Linux */
    char *filename = "/var/games/raidem/" HIGH_SCORE_FILENAME;
#endif

    return fopen(filename, mode);
}

/*--------------------------------------------------------------*/

static void strip(char *str)
{
    int l = strlen(str);

    if (str[l-2] == '\r')		/* DOS */
	str[l-2] = '\0';
    else if (str[l-1] == '\n')
	str[l-1] = '\0';
}

static void write_scores(FILE *fp, enum HIGH_SCORE_SOURCE source)
{
    int i;

    for (i = 0; i < NUM_HIGH_SCORE_ITEMS; i++) {
	/* Don't write scores which were read from the global score
	   file into the local score file. */
	if (source == LOCAL_HIGH_SCORE &&
	    high_score_item[i].source == GLOBAL_HIGH_SCORE)
	    continue;

	putw(high_score_item[i].score, fp);
	fputs(high_score_item[i].name, fp);
	putc('\n', fp);
    }

}

void write_high_scores(void)
{
    char buf[PATH_MAX];
    FILE *fp;

    /* Try global score file. */
    fp = open_global_high_score_file("w");
    if (fp) {
	/* Delete ~/.raidem/score.txt.  */
        int unlnk = unlink(local_high_score_filename(buf, sizeof buf));

	if ((unlnk == 0) ||
            (unlnk == -1 && errno == ENOENT)) {
	    write_scores(fp, GLOBAL_HIGH_SCORE);
	    fclose(fp);
	    return;
	}

	fclose(fp);
    }

    /* Create ~/.raidem if it doesn't exist. */
    {
	char *home = getenv(HOME_PATH_ENV);
	struct stat stats;

	snprintf(buf, sizeof buf, "%s/%s", home, RAIDEM_CONFIG_PATHNAME);
	if (stat(buf, &stats) != 0) {
#ifdef __MINGW__
	    int mk = mkdir(buf);
#else  /* Linux */
	    int mk = mkdir(buf, S_IREAD|S_IWRITE|S_IEXEC);
#endif

	    if (mk != 0) {
		fprintf(stderr, "Error creating %s to write %s into.\n",
			buf, HIGH_SCORE_FILENAME);
		return;
	    }
	}
    }

    /* Try local score file, but don't write scores which were read
       from the global score file. */
    fp = open_local_high_score_file("w");
    if (fp) {
	write_scores(fp, LOCAL_HIGH_SCORE);
	fclose(fp);
	return;
    }
    else {
	fprintf(stderr, "Error opening " HIGH_SCORE_FILENAME " for writing.\n"
		"Any new scores will be lost.\n");
    }
}

static BOOL merge_high_scores(FILE *fp, enum HIGH_SCORE_SOURCE source)
{
    int i;

    for (i = 0; i < NUM_HIGH_SCORE_ITEMS; i++) {
	char temp[32];			/* Should only need 21. */
	int score;

	score = getw(fp);
	if (score == EOF)
	    return YES;

	if (fgets(temp, sizeof temp, fp) == NULL)
	    return NO;

	strip(temp);
	if (is_new_high_score(score))
	    update_high_score(temp, score, source);
    }

    return YES;
}

void read_high_scores(void)
{
    FILE *fp;
    int i;

    for (i = 0; i < NUM_HIGH_SCORE_ITEMS; i++) {
	high_score_item[i].name   = NULL;
	high_score_item[i].score  = -1;
	high_score_item[i].source = SOURCE_UNKNOWN;
    }

    /* Try global scores file */
    fp = open_global_high_score_file("r");
    if (fp) {
	merge_high_scores(fp, GLOBAL_HIGH_SCORE);
	fclose(fp);
    }

    /* Try local score file */
    fp = open_local_high_score_file("r");
    if (fp) {
	merge_high_scores(fp, LOCAL_HIGH_SCORE);
	fclose(fp);
    }

    i = 0;
    while (high_score_item[NUM_HIGH_SCORE_ITEMS-1].score < 0) {
	/* Append default scores until we have 10. */
	update_high_score(default_scores[i].name,
			  default_scores[i].score,
			  default_scores[i].source);
	i++;
    }
}
