/* Text conversion from one charset to another.

   Copyright (C) 2001 Walery Studennikov <despair@sama.ru>

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include "../config.h"

#ifdef HAVE_CHARSET

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <iconv.h>

#include <stdarg.h>

#include "global.h"
#include "charsets.h"

/* Some declaration of funcs */
int checkIfCodepageUTF(int _cp);
char *from_UTC4INTERNAL_to_cp(int _cp, int c);
char *from_cp1_to_cp2(int _cp1,int _cp2, char *c);
int from_cp_to_UTC4INTERNAL(int _cp, char *c);

int n_codepages = 0;

struct codepage_desc *codepages;

unsigned char conv_displ[256];
unsigned char conv_input[256];

int glbl_cpsource, glbl_cpdisplay;
int glbl_cpsource_isUTF, glbl_cpdisplay_isUTF;

int
load_codepages_list (void)
{
    int result = -1;
    FILE *f;
    char *fname;
    char buf[256];
    extern char *mc_home;
    extern char *mc_home_alt;
    extern int display_codepage;
    char *default_codepage = NULL;

    fname = concat_dir_and_file (mc_home, CHARSETS_INDEX);
    if (!(f = fopen (fname, "r"))) {
        g_free (fname); fname = NULL;
        fname = concat_dir_and_file (mc_home_alt, CHARSETS_INDEX);
        if (!(f = fopen (fname, "r"))) {
	    fprintf (stderr, _("Warning: file %s not found\n"), fname);
        }
	g_free (fname); fname = NULL;
	return -1;
    }
    g_free (fname); fname = NULL;

    for (n_codepages = 0; fgets (buf, sizeof (buf), f);)
	if (buf[0] != '\n' && buf[0] != '\0' && buf[0] != '#')
	    ++n_codepages;
    rewind (f);

    codepages = g_new0 (struct codepage_desc, n_codepages + 1);

    for (n_codepages = 0; fgets (buf, sizeof buf, f);) {
	/* split string into id and cpname */
	char *p = buf;
	int buflen = strlen (buf);

	if (*p == '\n' || *p == '\0' || *p == '#')
	    continue;

	if (buflen > 0 && buf[buflen - 1] == '\n')
	    buf[buflen - 1] = '\0';
	while (*p != '\t' && *p != ' ' && *p != '\0')
	    ++p;
	if (*p == '\0')
	    goto fail;

	*p++ = 0;

	while (*p == '\t' || *p == ' ')
	    ++p;
	if (*p == '\0')
	    goto fail;

	if (strcmp (buf, "default") == 0) {
	    default_codepage = g_strdup (p);
	    continue;
	}

	codepages[n_codepages].id = g_strdup (buf);
	codepages[n_codepages].name = g_strdup (p);
	++n_codepages;
    }

    if (default_codepage) {
	display_codepage = get_codepage_index (default_codepage);
	g_free (default_codepage);
    }

    result = n_codepages;
  fail:
    fclose (f);
    return result;
}

void
free_codepages_list (void)
{
    if (n_codepages > 0) {
	int i;
	for (i = 0; i < n_codepages; i++) {
	    g_free (codepages[i].id);
	    g_free (codepages[i].name);
	}
	n_codepages = 0;
	g_free (codepages);
	codepages = 0;
    }
}

const char *
get_codepage_id (int n)
{
    return (n < 0) ? OTHER_8BIT : codepages[n].id;
}

int
get_codepage_index (const char *id)
{
    int i;
    if (strcmp (id, OTHER_8BIT) == 0)
	return -1;
    for (i = 0; codepages[i].id; ++i)
	if (strcmp (id, codepages[i].id) == 0)
	    return i;
    return -1;
}

char
translate_character (iconv_t cd, char c)
{
    char outbuf[4], *obuf;
    size_t ibuflen, obuflen, count;

    ICONV_CONST char *ibuf = &c;
    obuf = outbuf;
    ibuflen = 1;
    obuflen = 4;

    count = iconv (cd, &ibuf, &ibuflen, &obuf, &obuflen);
    if (count != ((size_t) -1) && ibuflen == 0)
	return outbuf[0];

    return UNKNCHAR;
}

char errbuf[255];

/*
 * FIXME: This assumes that ASCII is always the first encoding
 * in mc.charsets
 */
#define CP_ASCII 0

const char *
init_translation_table (int cpsource, int cpdisplay)
{
    if (cpdisplay < 0) cpdisplay = 0;
    if (cpsource < 0) cpsource = cpdisplay;

    glbl_cpsource = cpsource;
    glbl_cpdisplay = cpdisplay;

    glbl_cpsource_isUTF = checkIfCodepageUTF(cpsource);
    glbl_cpdisplay_isUTF = checkIfCodepageUTF(cpdisplay);

    return NULL;
}

void
convert_to_display (char *str)
{
    if (!str)
	return;

    while (*str) {
	*str = conv_displ[(unsigned char) *str];
	str++;
    }
}

void
convert_from_input (char *str)
{
    if (!str)
	return;

    while (*str) {
	*str = conv_input[(unsigned char) *str];
	str++;
    }
}

extern int errno;

int convert_to_display_c (int c) {
    char *tmp_w1, *tmp_w2;;
    int ret;
    if (
	    (glbl_cpdisplay < 0 ) || 
	    (glbl_cpsource < 0) ||
	    (glbl_cpdisplay == glbl_cpsource)
	) return c;

    if (! glbl_cpdisplay_isUTF){
	if (glbl_cpsource_isUTF){
	    tmp_w1 = from_UTC4INTERNAL_to_cp(glbl_cpsource, c);
	    tmp_w2 = from_cp1_to_cp2(glbl_cpsource, glbl_cpdisplay,tmp_w1);
	    g_free(tmp_w1); tmp_w1 = NULL;
	    ret = from_cp_to_UTC4INTERNAL(glbl_cpsource, tmp_w2);
	    g_free(tmp_w2); tmp_w2 = NULL;
	    return ret;
	}
	tmp_w1 = from_cp1_to_cp2(glbl_cpsource, glbl_cpdisplay, (char *)&c);
	ret = (int) *tmp_w1; ret&=255;
	g_free(tmp_w1);
	return ret;
    }
    
    tmp_w1 = g_malloc(sizeof(int)+1);
    if (tmp_w1 == NULL) 
       return -1;
    
    memset(tmp_w1, 0, 5);
    memcpy(tmp_w1, &c, sizeof(int));
    tmp_w1[1] = '\0';

    tmp_w2 = from_cp1_to_cp2(glbl_cpsource, glbl_cpdisplay,tmp_w1);
    g_free(tmp_w1);
    ret = from_cp_to_UTC4INTERNAL(glbl_cpdisplay, tmp_w2);

    g_free(tmp_w2);
    return ret;
}

int convert_from_input_c (int c){
    char *tmp_w1, *tmp_w2;
    char byte;
    int cp2 = glbl_cpdisplay;
    int cp1 = glbl_cpsource;

    static char buff[7];
    static int buffOffset=0;
    int ret;

    if (
	    (cp2 < 0 ) || 
	    (cp1 < 0) ||
	    (cp2 == cp1)
	) return c;

    if (! glbl_cpdisplay_isUTF){
	tmp_w1 = (char *) &c;
	tmp_w2 = from_cp1_to_cp2(cp2, cp1,tmp_w1);
	byte = *tmp_w2;
	ret = byte; ret&=255;
        g_free(tmp_w2);
        return ret;

    }

    if (buffOffset == 0){
	memset(buff,0,7);
    }
    
    buff[buffOffset++] = (char) c;
    if (buffOffset == 7) buffOffset = 0;
    
    if ( mbstowcs (NULL, (char *) &buff,1) == (size_t) -1){
	return 0;
    }
    buffOffset = 0;

    tmp_w2 = from_cp1_to_cp2(cp2, cp1,buff);
    ret = (int) *tmp_w2; ret&=255;
    g_free(tmp_w2);
    return ret;
}

/* --------------------------------------------------------------------------- */
int checkIfCodepageUTF(int _cp){
    char *cp_name;
    if (_cp < 0 ) _cp = 0;
    cp_name = codepages[_cp].id;
    if ( 
	(! strncmp(cp_name,"UTF-8",5)) || 
	(! strncmp(cp_name,"utf-8",5)) 
    ) return 1;
    return 0;
}

char *from_UTC4INTERNAL_to_cp(int _cp, int c){
    iconv_t cd;
    char *outbuf, *obuf;
    char *ibuf;
    size_t ibuflen, obuflen, count;
// FIXME: автоматом переключиться на UCS-4LE (big endian). 
// FIXME: У кого такая архитектура - тот хай сам голову ломает
// FIXME: ... или шлёт мне патчи.
    cd = iconv_open (codepages[_cp].id, "UCS-4LE");
    if (cd == (iconv_t) - 1) {
	return g_strdup(".");
    }

    ibuf = (char *) &c;
    ibuflen = sizeof(int);

    obuflen = 7; //max utf size + zero
    outbuf = obuf = g_malloc(obuflen); 
    if (outbuf == NULL) return g_strdup(".");
    memset((void *)outbuf, 0,obuflen);

    count = iconv (cd, &ibuf, &ibuflen, &obuf, &obuflen);
    iconv_close (cd);
    if (count != ((size_t) -1) && ibuflen == 0){
	return outbuf;
    }
    g_free(outbuf);
    return g_strdup(".");
}
char *from_cp1_to_cp2(int _cp1,int _cp2, char *c){
    iconv_t cd;
    char *outbuf, *obuf;
    char *ibuf = c;
    size_t ibuflen, obuflen, count;
    
    if (c == NULL) return g_strdup("");
    if (
	    (_cp2 < 0 ) || 
	    (_cp1 < 0) ||
	    (_cp2 == _cp1)
	) return g_strdup(c);
    
    
    cd = iconv_open (codepages[_cp2].id, codepages[_cp1].id);
    if (cd == (iconv_t) - 1) {
	return g_strdup(c);
    }
    ibuflen = strlen(ibuf);
    obuflen = ibuflen*6 + 1; //num input chars * utf size + zero
    
    outbuf = obuf = g_malloc(obuflen); 
    if (outbuf == NULL) return g_strdup(c);
    memset((void *)outbuf, 0,obuflen);
    
    count = iconv (cd, &ibuf, &ibuflen, &obuf, &obuflen);
    iconv_close (cd);
    if (count != ((size_t) -1) && ibuflen == 0){
	return outbuf;
    }
    g_free(outbuf);
    return g_strdup(c);
}
int from_cp_to_UTC4INTERNAL(int _cp, char *c){
    iconv_t cd;
    int outbuf;
    char *obuf;
    char *ibuf;
    size_t ibuflen, obuflen, count;
    
    if (c == NULL) return UNKNCHAR;
    
// FIXME: автоматом переключиться на UCS-4LE (big endian). 
// FIXME: У кого такая архитектура - тот хай сам голову ломает
// FIXME: ... или шлёт мне патчи.
    cd = iconv_open ("UCS-4LE", codepages[_cp].id);
    if (cd == (iconv_t) - 1) {
	return UNKNCHAR;
    }

    ibuf = c;
    ibuflen = strlen(ibuf);
    obuflen = sizeof(int);
    
    obuf = (char *) &outbuf;

    count = iconv (cd, &ibuf, &ibuflen, &obuf, &obuflen);
    iconv_close (cd);
    if (count != ((size_t) -1) && ibuflen == 0){
	return outbuf;
    }
    return UNKNCHAR;
}
int one_from_sourceCP_to_UTC4INTERNAL(char *c){
    return from_cp_to_UTC4INTERNAL(glbl_cpsource,c);
}
int one_from_displayCP_to_UTC4INTERNAL(char *c){
    return from_cp_to_UTC4INTERNAL(glbl_cpdisplay,c);
}
char *convert_from_input_w (char *str) {
    return from_cp1_to_cp2(glbl_cpsource, glbl_cpdisplay, str);
}

char *convert_to_display_w (char *str){
    return from_cp1_to_cp2(glbl_cpdisplay, glbl_cpsource, str);
}
/* * * * */
void f_log(const char *fmt, ... ){
    FILE *fd;
    va_list ap;
    va_start(ap, fmt);
    fd = fopen("_f_log.txt","a+");
    if (fd == NULL )
      exit (-1);

    vfprintf(fd,fmt,ap);
    fflush(fd);
    fclose(fd);
    va_end(ap);
}
void f_logb(void *data, int len){
    FILE *fd;
    fd = fopen("_f_logb.txt","a+");
    if (fd == NULL )
      exit (-1);
    fwrite(data,1,len,fd);
    fflush(fd);
    fclose(fd);
}

/* * * * */
#endif				/* HAVE_CHARSET */
