/*****************************************************************************/
/*									     */
/*									     */
/*	X patience version 2 -- module messages.c			     */
/*									     */
/*	Internationalisation and keyboard translation/customisation	     */
/*	written by Michael Bischoff (mbi@mo.math.nat.tu-bs.de)		     */
/*	04-Apr-1994							     */
/*	see COPYRIGHT.xpat2 for Copyright details			     */
/*									     */
/*									     */
/*****************************************************************************/
#include "xpatgame.h"

#include <limits.h>
#ifndef _POSIX_NAME_MAX
#  undef _POSIX_SOURCE		/* seems that we have no POSIX system! */
#else
#  include "unistd.h"		/* for access() */
#endif

static const char
    n_ace[] = "Ace", n_deuce[] = "Deuce", n_three[] = "Three",
    n_four[] = "Four", n_five[] = "Five", n_six[] = "Six", n_seven[] = "Seven",
    n_eight[] = "Eight", n_nine[] = "Nine", n_ten[] = "Ten", n_jack[] = "Jack",
    n_queen[] = "Queen", n_king[] = "King", n_joker[] = "Joker",
    n_diamonds[] = "Diamonds", n_hearts[] = "Hearts", n_spades[] = "Spades",
    n_clubs[] = "Clubs";

const char *US_rank_name[] = {
    n_ace, n_deuce, n_three, n_four, n_five, n_six, n_seven, n_eight, n_nine,
    n_ten, n_jack, n_queen, n_king };
const char *US_suit_name[] = {
    n_clubs, n_spades, n_hearts, n_diamonds };

const char *xpat_messages[] = {
    "Quit game?",
    "Another game?",
    "Restart game?",
    n_clubs,
    n_spades,
    n_hearts,
    n_diamonds,
    n_joker,
    n_ace,
    n_deuce,
    n_three,
    n_four,
    n_five,
    n_six,
    n_seven,
    n_eight,
    n_nine,
    n_ten,
    n_jack,
    n_queen,
    n_king,
    "stack",
    "slot",
    "the talon",
    "tmp",
    "the stock",
    "No hint was given!",
    "You can move %P (%R of %S) to %P.",
    "No hints!",
    "(wrapping)",
    "Bookmark set.",
    "You won!",
    "No moves to stack possible.",
    "Saving of game FAILED.",
    "Loading the game FAILED.",
    "Could not open file.",
    "Could not open file.",
    "Could not write header.",
    "Could not read header.",
    "Could not write moves.",
    "Could not read moves.",
    "Saving the game succeeded.",
    "Loading the game succeeded.",
    "Magic match failed.",
    "No flip left.",
    "flipping",
    "new cards",
    "No new cards!",
    "No source pile selected.",
    "Cards moved to stack.",
    "Move not possible.",
    "Undo not possible.",
    "Undo!",
    "Undo (cheat!)",
    "Redo not possible.",
    "Redo!",
    "Redo (& uncheat)",
    "Invalid move",
    "Empty or invalid source pile",
    "source pile selected",
    "not so many slots",
    "aborted",
    "Unknown command:",
    "no move possible",
    "Xpat2 version",
    "Seed is %ld, cheat count %d. ",
    "Score %d of %d.",
    "(No score for these rules)",
    "",
    "Help on keys",
    "Help on %s rules",
    "help.keys",
    "",
    "",
    "",
    "",
    "help.Spider",	/* we limit the filenames to 14 characters. */
    "help.Gypsy",	/* Not nice, but POSIX conforming */
    "help.Klondike",
    "help.Seahaven",
    "help.FreeCell",	/* 80 */
    "help.Delight",
    "hlpMonteCarlo",
    "hlpMidnightOil",
    "hlpCalculation",
    "hlpmodCanfield",
    "help.Michaels",
    "help.Canfield",
    "help.Cotillion",
    "hlpBakersDozen",
    "help.Oonsoo",
    "help.BlueMoon",
    "Please wait, loading cards...",
    "Welcome to XPat2 version %s!",
    "",
    "Rotate up selected.",
    "Rotate down selected.",
    "",
    "Card must be visible.",
    "For this action, a slot is required.",
    "In these rules, the cards cannot be shuffled.",	/* 100 */
    "No shuffles left.",
    "One shuffle left.",
    "%d shuffles left.",
    "In these rules, the deck cannot be flipped.",
    "No flip left.",
    "One flip left.",
    "%d flips left.",
    "This function is not available in these rules.",
    "No slot rotations left.",
    "One slot rotation left.",
    "%d slot rotations left.",
    "No ``deal cards'' in these rules.",
    "No more cards on the deck.",
    "One last card on the deck.",
    "%d cards left on deck.",
    "Starting position.",
    "One move done.",
    "%d moves done.",
    "",
    "Not a single point of",
    "One point of",
    "Score: %d points of",
};

void *malloc_(size_t n) {
    void *p;
    if (!(p = malloc(n))) {
	fprintf(stderr, "out of memory\n");
	exit(EXIT_FAILURE);
    }
    return p;
}

char *strsav(const char *txt) {
    char *p = malloc_(1 + strlen(txt));
    strcpy(p, txt);
    return p;
}

/* map standard language settings to historic xpat ones... */
const char *get_lang(void) {
    static char iso_code[3] = { 0 };
    static char *langdir = NULL;
    char buff[256];
    if (langdir)
	return langdir;		/* directory was checked before */

    /* first call, read environment */
    langdir = getenv("LANG");
    if (!langdir)
	langdir = "";		/* set to empty */
    if (!*langdir)
	return langdir;		/* nothing to check */

    /* there is something set. Check values as defined */
    if (strlen(langdir) + strlen(LIBDIR) + 2 > sizeof(buff)) {
	fprintf(stderr, "xpat2: path too long for LIBDIR + LANG\n(LIBDIR = %s, LANG = %s)\n",
		LIBDIR, langdir);
	exit(EXIT_FAILURE);
    }
    /* buff has sufficient size */
    sprintf(buff, "%s/%s", LIBDIR, langdir);

    if (strlen(langdir) < 2)
	return langdir;		/* no idea how to change, keep it! */

#ifdef _POSIX_SOURCE
    /* on posix-systems, we can check if the directory is accessible */
    if (!access(buff, R_OK))	/* check access to directory */
	return langdir;		/* directory(?) seems to be readable, keep it */
#endif
    /* access denied or some other error.
       Try to map iso-codes to xpat names */

    iso_code[0] = toupper(langdir[0]);
    iso_code[1] = toupper(langdir[1]);
    iso_code[2] = '\0';
    /* map the existing translations (iso3166) to well-known pathnames */
    if (!strcmp(iso_code, "DE"))
	return langdir = "german";
    if (!strcmp(iso_code, "IT"))
	return langdir = "italian";
    if (!strcmp(iso_code, "FR"))
	return langdir = "french";
    if (!strcmp(iso_code, ""))
	return langdir = "german";
    if (!strcmp(iso_code, "RU") || !strcmp(iso_code, "BY"))
	return langdir = "russian";
#ifdef _POSIX_SOURCE
    /* directory wasn't readable, reset it */
    return langdir = "";
#else
    /* access has not been checked, keep directory */
    return langdir;
#endif
}

FILE *lang_fopen(const char *filename) {
    char pathname[256];
    const char *langdir = get_lang();
    FILE *fp;
    if (strlen(filename) + strlen(langdir) + strlen(LIBDIR) + 3 <= sizeof(pathname)) {
      /* buffer has sufficient size */
      if (*langdir) {
	  sprintf(pathname, "%s/%s/%s", LIBDIR, langdir, filename);
	  if ((fp = fopen(pathname, "r")))
	      return fp;
      }
    }
    if (strlen(filename) + strlen(LIBDIR) + 2 <= sizeof(pathname)) {
        sprintf(pathname, "%s/%s", LIBDIR, filename);
	if ((fp = fopen(pathname, "r")))
	    return fp;
    }
    return NULL;
}

void read_message_file(const char *filename) {
    FILE *fp;
    int i;
    char line[256];
    if (!(fp = lang_fopen(filename)))
	return;		/* Xt philosophy: ignore error */
    for (i = 0; i < sizeof(xpat_messages) / sizeof(const char *); ++i) {
	char *p;
    again:
	if (!fgets(line, sizeof(line), fp))
	    break;	/* EOF */
	if (*line == '#')
	    goto again;
	if ((p = strrchr(line, '\n')))
	    *p = '\0';
	if (*line)	/* empty lines => keep old text */
	    xpat_messages[i] = strsav(line);
    }
    fclose(fp);
}

static void cmd_None(void) {}

static void replace_binding(struct key_action *p, const char *name) {
    int i;
    static struct translator {
	const char *name;
	void (*func)(void);
    } translator[] = {
	{ "None",		cmd_None },
	{ "rq_LeavePat",	rq_LeavePat },
	{ "rq_RestartGame",	rq_RestartGame },
	{ "rq_AnotherGame",	rq_AnotherGame },
	{ "ResetHints",		cmd_ResetHints },
	{ "DoHint",		cmd_DoHint },
	{ "NextHint",		cmd_NextHint },
	{ "PreviousHint",	cmd_PreviousHint },
	{ "CancelSelection",	cmd_CancelSelection },
	{ "AllMoves",		cmd_AllMoves },
	{ "UndoMove",		cmd_UndoMove },
	{ "RedoMove",		cmd_RedoMove },
	{ "DealCards",		cmd_DealCards },
	{ "LeavePat",		cmd_LeavePat },
	{ "ShowScore",		cmd_ShowScore },
	{ "AnotherGame",	cmd_AnotherGame },
	{ "RestartGame",	cmd_RestartGame },
	{ "ReplayGame",		cmd_ReplayGame },
	{ "Layout",		cmd_Layout },
	{ "SaveGame",		cmd_SaveGame },
	{ "ShowVersion",	cmd_ShowVersion },
	{ "MinWindow",		cmd_MinWindow },
	{ "PreferredWindow",	cmd_PreferredWindow },
	{ "Info",		cmd_Info },
	{ "ToStack",		cmd_ToStack },
	{ "OneToStack",		cmd_OneToStack },	/* selected card(s) */
	{ "AllToStack",		cmd_AllToStack },	/* all possible */
	{ "DropBookmark",	cmd_DropBookmark },
	{ "GotoBookmark",	cmd_GotoBookmark },
	{ "RotateUp",		cmd_RotateUp },
	{ "RotateDown",		cmd_RotateDown },
	{ "Cancel",		cmd_Cancel },
	{ "Confirm",		cmd_Confirm }
    };
    for (i = 0; i < sizeof(translator) / sizeof(translator[0]); ++i)
	if (!strcmp(name, translator[i].name)) {
	    p->action = translator[i].func;
	    return;
	}
    fprintf(stderr, "WARNING: no function corresponds to \"%s\"\n", name);
    p->action = cmd_None;
}

static struct key_action *global_bindings = NULL;

void add_keybinding(struct key_action **cp, const char *cmd, const char *function) {
    /* a NULL pointer for cmd is a wildcard */
    int done = 0;
    struct key_action *p;

    if (!cp)
	return;		/* bindings for unimplemented rulesets */
    while (*cp) {
	p = *cp;
	if (!cmd || !strcmp(p->string, cmd)) {
	    /* replace this! */
	    replace_binding(p, function);
	    done = 1;
	}
	cp = &(p->next);
    }
    if (!done && cmd) {
	/* didn't find previous command */
	/* add a new entry */
	p = *cp = malloc_(sizeof(struct key_action));
	p->next = NULL;
	p->string = strsav(cmd);
	replace_binding(p, function);
    }
}


void read_keyboard_file(const char *filename) {
    FILE *fp;
    char line[256];
    char buff[32], cmd[2];
    struct key_action **cp = &global_bindings;

    cmd[1] = '\0';	/* 1-char commands currently */
    if (!(fp = lang_fopen(filename))) {
	fprintf(stderr, "FAILED to open keyboard file \"%s\"\n", filename);
	return;		/* Xt philosophy: ignore error */
    }
    while (fgets(line, sizeof(line), fp)) {
	char *p;
	if ((p = strrchr(line, '\n')))
	    *p = '\0';
	if (!*line)
	    continue;
	if (!strncmp(line, "#c", 2))	/* comment */
	    continue;
	if (!strncmp(line, "#rules ", 7)) {	/* switch to other rules */
	    struct rules *rp;
	    p = line+7;
	    if (!(rp = getrules(p)))
		cp = NULL;
	    else
		cp = &(rp->local_bindings);
	    continue;
	}
	if (!strncmp(line, "#include ", 9)) {	/* include */
	    read_keyboard_file(line+9);
	    continue;
	}
	if (!strncmp(line, "#x", 2) && strlen(line) < 32) { /* hex number */
	    int c;
	    sscanf(line+2, "%x %s", &c, buff);
	    cmd[0] = c;
	    add_keybinding(cp, cmd, buff);
	    continue;
	}
	/* else assume string<space>commandname */
	if (strlen(line) < 32 && (p = strchr(line+1, ' '))) {
	    *p = '\0';
	    add_keybinding(cp, line, p+1);
	    continue;
	}
	fprintf(stderr, "Warning: cannot parse line in keys file:%s\n", line);
    }
    fclose(fp);
}


void key_pressed(char *str) {
    struct key_action *p;

    /* search for local binding */
    for (p = rules.local_bindings; p; p = p->next)
	if (!strcmp(p->string, str)) {
	    (*p->action)();
	    return;
	}

    /* search for global binding */
    for (p = global_bindings; p; p = p->next)
	if (!strcmp(p->string, str)) {
	    (*p->action)();
	    return;
	}

    /* not found. break up the string to smaller pieces */
    if (strlen(str) > 1) {
	char s[2];
	while (*str) {
	    s[0] = *str++;
	    s[1] = '\0';
	    key_pressed(s);
	}
    } else	/* last resort: */
	/* use hardcoded entries. Note: These can be overridden by assigning them "None" before. */

    switch (str[0]) {
    case '\014':
	cmd_ResetHints();	/* complicated to save */
	refresh_screen();
	break;
    case 27:	/* abort cmd */
	cmd_CancelSelection();
	show_message(TXT_ABORTED);
	return;
    }
    if (str[0]) {
	const char *rulechange = "FGHKSIMOBCR";
	const char *rulename[] = { "Free Cell", "Gypsy", "Seahaven", "Klondike",
				       "Spider", "id", "mc", "mo", "bd", "Canfield", "rc" };
	const char *s;

	if ((s = strchr(rulechange, str[0]))) {
	    change_rules(rulename[s-rulechange]);
	    return;
	}
    }
}
