#include <alloca.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <newt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/kd.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <zlib.h>

#include "cpio.h"
#include "install.h"
#include "kickstart.h"
#include "lang.h"
#include "log.h"

extern int testing;

struct aString {
    unsigned int hash;
    short length;
    char * str;
} ;

struct aString * strings = NULL;
int numStrings = 0, allocedStrings = 0;

static int loadFont(char * font);

static int aStringCmp(const void * a, const void * b) {
    const struct aString * first = a;
    const struct aString * second = b;

    if (first->hash < second->hash)
	return -1;
    else if (first->hash == second->hash)
	return 0;

    return 1;
}

char * translateString(char * str) {
    unsigned int sum = 0, xor = 0;
    int len = 0;
    char * chptr;
    struct aString * match;
    struct aString key;

    for (chptr = str; *chptr; chptr++) {
	sum += *chptr;
	xor ^= *chptr;
	len++;
    }

    key.hash = (sum << 16) | ((xor & 0xFF) << 8) | (len & 0xFF);

    match = bsearch(&key, strings, numStrings, sizeof(*strings), aStringCmp);
    if (!match)
	return str;

    return match->str;
}

struct langInfo {
    char * lang, * key, * font, * lc_all;
} ;

/* FONT LIST STARTS */
static const struct langInfo languages[] = {
	{ "English", "en", NULL, "en_US" },
	{ "Czech", "cs", "lat2-16", "cs_CZ" },
	{ "Danish", "da", "lat1-16", "da_DK" },
	{ "Finnish", "fi", "lat1-16", "fi_FI" },
	{ "French", "fr", "lat1-16", "fr_FR" },
	{ "German", "de", "lat1-16", "de_DE" },
	{ "Italian", "it", "lat1-16", "it_IT" },
	{ "Norwegian", "no", "lat1-16", "no_NO" },
	{ "Romanian", "ro", "lat2-16", "ro_RO" },
	{ "Serbian", "sr", "lat2-16", "sr_YU" },
	{ "Slovak", "sk", "lat2-16", "sk_SK" },
	{ "Swedish", "se", "lat1-16", "sv_SE" },
	{ "Turkish", "tr", "latin5-16", "tr_TR" } ,
};
/* FONT LIST ENDS */
const int numLanguages = sizeof(languages) / sizeof(struct langInfo);

int chooseLanguage(void) {
    int choice = 0;
    char ** langs;
    int i;
    int ksargc;
    char ** ksargv;

    if (strings) {
	free(strings), strings = NULL;
	numStrings = allocedStrings = 0;
    }

    if (kickstart && !ksGetCommand(KS_CMD_LANG, NULL, &ksargc, &ksargv)) {
	if (ksargc < 2) {
	    logMessage("no argument passed to keyboard kickstart command");
	} else {
	    for (choice = 0; choice < numLanguages; choice++)
		if (!strcmp(languages[choice].key, ksargv[1])) break;
	    if (choice == numLanguages) {
		logMessage("unknown language %s", ksargv[1]);
	    } else if (!choice) {
		return INST_OKAY;
	    } else {
		setenv("LANG", languages[choice].key, 1);
		setenv("LC_ALL", languages[choice].lc_all, 1);
		setenv("LINGUAS", languages[choice].key, 1);
		setDefaultLanguage(1);
		return INST_OKAY;
	    }
	}
    }

    langs = alloca(sizeof(*langs) * (numLanguages + 1));
    for (i = 0; i < numLanguages; i++)
	langs[i] = languages[i].lang;

    langs[i] = NULL;

    if (getenv("LANG")) {
	for (choice = 0; choice < numLanguages; choice++)
	    if (!strcmp(languages[choice].key, getenv("LANG"))) break;
	if (choice == numLanguages) choice = 0;
    }

    /*loadFont("default8x16");*/

    newtWinMenu("Choose a Language", "What language should be used "
		"during the installation process?", 40, 5, 5, 8,
		langs, &choice, "Ok", NULL);

    if (!choice) {
	/* stick with the default (English) */
	unsetenv("LANG");
	unsetenv("LC_ALL");
	unsetenv("LINGUAS");
	return 0;
    }

    setenv("LANG", languages[choice].key, 1);
    setenv("LC_ALL", languages[choice].lc_all, 1);
    setenv("LINGUAS", languages[choice].key, 1);
    setDefaultLanguage(1);
    if (languages[choice].font)
	loadFont(languages[choice].font);

    return 0;
}

void setDefaultLanguage(int stage) {
    while (stage) {
	loadLanguageStage(stage, NULL);
	stage--;
    }
}

void loadLanguageStage(int which, char * file) {
    char filename[200];
    gzFile stream;
    int fd, hash, rc;
    char * key = getenv("LANG");

    if (!key || !strcmp(key, "en")) {
	if (strings) {
	    free(strings), strings = NULL;
	    numStrings = allocedStrings = 0;
	}
	return;
    }

    if (!file) {
	file = filename;
	if (testing)
	    sprintf(filename, "install%d.tr", which);
	else
	    sprintf(filename, "/etc/install%d.tr", which);
    }

    stream = gzopen(file, "r");

    if (!stream) {
	newtWinMessage("Error", "Ok", "Cannot open %s: %s. Installation will "
			"proceed in English.", file, strerror(errno));
	return ;
    }
    
    sprintf(filename, "%s%d.tr", key, which);

    rc = installCpioFile(stream, filename, "/tmp/translation", 1);
    gzclose(stream);
    if (rc || access("/tmp/translation", R_OK)) {
	newtWinMessage("Error", "Ok", "Cannot get translation file %s.\n", 
			filename);
	return;
    }
    
    fd = open("/tmp/translation", O_RDONLY);
    if (fd < 0) {
	newtWinMessage("Error", "Ok", "Failed to open /tmp/translation: %s\n", 
			strerror(errno));
	return;
    }

    while (read(fd, &hash, 4) == 4) {
	if (allocedStrings == numStrings) {
	    allocedStrings += 10;
	    strings = realloc(strings, sizeof(*strings) * allocedStrings);
	}

	strings[numStrings].hash = ntohl(hash);
	read(fd, &strings[numStrings].length, 2);
	strings[numStrings].length = ntohs(strings[numStrings].length);
	strings[numStrings].str = malloc(strings[numStrings].length + 1);
	read(fd, strings[numStrings].str, strings[numStrings].length);
	strings[numStrings].str[strings[numStrings].length] = '\0';
	numStrings++;
    }

    close(fd);
    unlink("/tmp/translation");

    qsort(strings, numStrings, sizeof(*strings), aStringCmp);
}

static int loadFont(char * fontFile) {
    char font[8192];
    char map[E_TABSZ];
    int fd;
    gzFile stream;
    int rc;

    if (!testing) {
	stream = gzopen("/etc/fonts.cgz", "r");
	if (!stream) {
	    newtWinMessage("Error", "Ok", 
			"Cannot open fonts: %s", strerror(errno));
	    return INST_ERROR;
	}

	rc = installCpioFile(stream, fontFile, "/tmp/font", 1);
	gzclose(stream);
	if (rc || access("/tmp/font", R_OK)) {
	    return INST_ERROR;
	}

	fd = open("/tmp/font", O_RDONLY);
	read(fd, font, sizeof(font));
	read(fd, map, sizeof(map));
	close(fd);

	if (ioctl(1, PIO_FONT, font))
	    logMessage("PIO_FONT failed: %s", strerror(errno)); 

	if (ioctl(1, PIO_SCRNMAP, map))
	    logMessage("PIO_SCRNMAP failed: %s", strerror(errno)); 
    }

    if (!strcmp(fontFile, "lat1-16") || !strcmp(fontFile, "lat2-16")) {
	setenv("TERM", "linux-lat", 1);
    } else {
	setenv("TERM", "linux", 1);
    }

    return 0;
}

int writeLangInfo(char * rootPath) {
    char * lang = getenv("LANG");
    int i;
    FILE * f;
    char * path;

    if (testing || !lang) return 1;

    path = alloca(strlen(rootPath) + 40);
    sprintf(path, "%s/etc/sysconfig/i18n", rootPath);
    f = fopen(path, "w");
    fprintf(f, "LANG=%s\n", lang);
    fprintf(f, "LINGUAS=%s\n", lang);

    for (i = 0; i < numLanguages; i++)
	if (!strcmp(languages[i].key, lang)) break;
    
    if (i < numLanguages && languages[i].lc_all) {
	fprintf(f, "LC_ALL=%s\n", languages[i].lc_all);
    }

    if (i < numLanguages && languages[i].font) {
	fprintf(f, "SYSFONT=%s\n", languages[i].font);
    }

    if (i < numLanguages && languages[i].font &&
           (!strcmp(languages[i].font, "lat1-16") || 
		!strcmp(languages[i].font, "lat2-16"))) {
	fprintf(f, "SYSTERM=linux-lat\n");
    }

    fclose(f);

    return 0;
}
