#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#if !defined(HAVE_ICONV)
#error "csconv needs iconv"
#endif
#include "csconv.h"
#include <stdio.h>
#include <iconv.h>
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_CTYPE_H
#include <ctype.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif

struct _csconv_info {
    iconv_t cd;
};


#define skip_space(p) for(; *p != '\0' && isspace(*p); p++);

static char*
get_item(char *item_buf, char *line, char **next_ptr)
{
    char *p = line;
    size_t r = 0;
    skip_space(p);
    for(; p[r] != '\0' && isgraph(p[r]); r++) ;
    *next_ptr = p + r;
    if (!item_buf) return NULL;
    strncpy(item_buf, p, r);
    item_buf[r] = '\0';
    return item_buf;
}

static int
read_conf(
    const char *locale,
    const char *to,
    const char *from,
    char **norm_to,
    char **norm_from
)
{
    FILE *fp;
    char buf[128];
    int retval = -1;
    /* TODO */

    *norm_to = *norm_from = NULL;

    /* FIXME */
    /* should we read other configuration files 
     * such as ~/.iiim/encoding.norm ?
     */
    fp = fopen(CSCONV_DIR "/encoding.norm", "r");
    if (!fp) return -1;

    while((fgets(buf, sizeof(buf), fp) != NULL)) {
        /* TODO */
        char tmp[16];
        char *p = buf;
        int len = strlen(buf);
        /* skip comment */
        if (('#' == *p ) || ((1 < len) && ('/' == *p) && ('/' == *(p + 1)))) continue;
        if ('\n' == *p || '\0' == *p) continue;
        get_item(NULL, p, &p); /* skip os */
        if (strcmp(locale, get_item(tmp, p, &p))) continue;
        if (strcmp(to, get_item(tmp, p, &p))) continue;
        if (strcmp(from, get_item(tmp, p, &p))) continue;
        /* found */
        get_item(NULL, p, &p); /* skip locale */
        if (get_item(tmp, p, &p) != NULL) *norm_to = strdup(tmp);
        if (get_item(tmp, p, &p) != NULL) {
            *norm_from = strdup(tmp);
            retval = 0;
        }
        break;
    }
    fclose(fp);
    return retval;
}

csconv_t
csconv_open_locale(
    const char *locale,
    const char *tocode,
    const char *fromcode
)
{
    csconv_t conv = NULL;
    char *from = NULL, *to = NULL;

    if (locale == NULL) goto error;
    if ((read_conf(locale, tocode, fromcode, &to, &from) == -1)) goto error;
    conv = calloc(1, sizeof(*conv));
    if (!conv) goto error;

    conv->cd = iconv_open(to, from);
    if (conv->cd == (iconv_t) -1) goto error;
    free(from);
    free(to);
    return conv;

error:
    if (conv) free(conv);
    if (from) free(from);
    if (to) free(to);
#ifdef HAVE_ERRNO_H
    errno = EINVAL;
#endif
    return (csconv_t)-1;
}


csconv_t
csconv_open(
    const char *tocode,
    const char *fromcode
)
{
#ifdef HAVE_LOCALE_H
    /* try to find current locale */
    /* we should pass NULL as a second argument */
    return csconv_open_locale(setlocale(LC_CTYPE, NULL), tocode, fromcode);
#endif
#ifdef HAVE_ERRNO_H
    /* we cannot determine locale */
    /* set errno to EINVAL */
    errno = EINVAL;
#endif
    return (csconv_t)-1;
}

size_t
csconv(csconv_t cd,
       const char **inbuf,
       size_t *inbytesleft,
       char **putbuf,
       size_t *outbytesleft
)
{
    if (!cd || cd == (csconv_t)-1) {
#ifdef HAVE_ERRNO_H
      errno = EINVAL;
#endif
      return (size_t)-1;
    }
    return iconv(cd->cd, (ICONV_CONST char**)inbuf, inbytesleft, putbuf, outbytesleft);
}

int
csconv_close(
    csconv_t cd
)
{
    int retval = 0;
    if (!cd || cd == (csconv_t)-1) return -1;
    retval = iconv_close(cd->cd);
    free(cd);
    return retval;
}
