/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * 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:
 *
 * Copyright (C) 2010 Red Hat, Inc.
 */

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

#include "mm-charsets.h"

typedef struct {
    const char *gsm_name;
    const char *other_name;
    const char *iconv_from_name;
    const char *iconv_to_name;
    MMModemCharset charset;
} CharsetEntry;

static CharsetEntry charset_map[] = {
    { "UTF-8",   "UTF8",   "UTF-8",     "UTF-8//TRANSLIT",     MM_MODEM_CHARSET_UTF8 },
    { "UCS2",    NULL,     "UCS-2BE",   "UCS-2BE//TRANSLIT",   MM_MODEM_CHARSET_UCS2 },
    { "IRA",     "ASCII",  "ASCII",     "ASCII//TRANSLIT",     MM_MODEM_CHARSET_IRA },
    { "GSM",     NULL,     NULL,        NULL,                  MM_MODEM_CHARSET_GSM },
    { "8859-1",  NULL,     "ISO8859-1", "ISO8859-1//TRANSLIT", MM_MODEM_CHARSET_8859_1 },
    { "PCCP437", NULL,     NULL,        NULL,                  MM_MODEM_CHARSET_PCCP437 },
    { "PCDN",    NULL,     NULL,        NULL,                  MM_MODEM_CHARSET_PCDN },
    { "HEX",     NULL,     NULL,        NULL,                  MM_MODEM_CHARSET_HEX },
    { NULL,      NULL,     NULL,        NULL,                  MM_MODEM_CHARSET_UNKNOWN }
};

const char *
mm_modem_charset_to_string (MMModemCharset charset)
{
    CharsetEntry *iter = &charset_map[0];

    g_return_val_if_fail (charset != MM_MODEM_CHARSET_UNKNOWN, NULL);

    while (iter->gsm_name) {
        if (iter->charset == charset)
            return iter->gsm_name;
        iter++;
    }
    g_warn_if_reached ();
    return NULL;
}

MMModemCharset
mm_modem_charset_from_string (const char *string)
{
    CharsetEntry *iter = &charset_map[0];

    g_return_val_if_fail (string != NULL, MM_MODEM_CHARSET_UNKNOWN);

    while (iter->gsm_name) {
        if (strcasestr (string, iter->gsm_name))
            return iter->charset;
        if (iter->other_name && strcasestr (string, iter->other_name))
            return iter->charset;
        iter++;
    }
    return MM_MODEM_CHARSET_UNKNOWN;
}

static const char *
charset_iconv_to (MMModemCharset charset)
{
    CharsetEntry *iter = &charset_map[0];

    g_return_val_if_fail (charset != MM_MODEM_CHARSET_UNKNOWN, NULL);

    while (iter->gsm_name) {
        if (iter->charset == charset)
            return iter->iconv_to_name;
        iter++;
    }
    g_warn_if_reached ();
    return NULL;
}

static const char *
charset_iconv_from (MMModemCharset charset)
{
    CharsetEntry *iter = &charset_map[0];

    g_return_val_if_fail (charset != MM_MODEM_CHARSET_UNKNOWN, NULL);

    while (iter->gsm_name) {
        if (iter->charset == charset)
            return iter->iconv_from_name;
        iter++;
    }
    g_warn_if_reached ();
    return NULL;
}

gboolean
mm_modem_charset_byte_array_append (GByteArray *array,
                                    const char *string,
                                    gboolean quoted,
                                    MMModemCharset charset)
{
    const char *iconv_to;
    char *converted;
    GError *error = NULL;
    gsize written = 0;

    g_return_val_if_fail (array != NULL, FALSE);
    g_return_val_if_fail (string != NULL, FALSE);

    iconv_to = charset_iconv_to (charset);
    g_return_val_if_fail (iconv_to != NULL, FALSE);

    converted = g_convert (string,
                           g_utf8_strlen (string, -1),
                           iconv_to,
                           "UTF-8",
                           NULL,
                           &written,
                           &error);
    if (!converted) {
        if (error) {
            g_warning ("%s: failed to convert '%s' to %s character set: (%d) %s",
                       __func__, string, iconv_to,
                       error->code, error->message);
            g_error_free (error);
        }
        return FALSE;
    }

    if (quoted)
        g_byte_array_append (array, (const guint8 *) "\"", 1);
    g_byte_array_append (array, (const guint8 *) converted, written);
    if (quoted)
        g_byte_array_append (array, (const guint8 *) "\"", 1);

    g_free (converted);
    return TRUE;
}

/* From hostap, Copyright (c) 2002-2005, Jouni Malinen <jkmaline@cc.hut.fi> */

static int hex2num (char c)
{
	if (c >= '0' && c <= '9')
		return c - '0';
	if (c >= 'a' && c <= 'f')
		return c - 'a' + 10;
	if (c >= 'A' && c <= 'F')
		return c - 'A' + 10;
	return -1;
}

static int hex2byte (const char *hex)
{
	int a, b;
	a = hex2num(*hex++);
	if (a < 0)
		return -1;
	b = hex2num(*hex++);
	if (b < 0)
		return -1;
	return (a << 4) | b;
}

static char *
hexstr2bin (const char *hex, gsize *out_len)
{
    size_t len = strlen (hex);
	size_t       i;
	int          a;
	const char * ipos = hex;
	char *       buf = NULL;
	char *       opos;

	/* Length must be a multiple of 2 */
    g_return_val_if_fail ((len % 2) == 0, NULL);

	opos = buf = g_malloc0 ((len / 2) + 1);
	for (i = 0; i < len; i += 2) {
		a = hex2byte (ipos);
		if (a < 0) {
			g_free (buf);
			return NULL;
		}
		*opos++ = a;
		ipos += 2;
	}
    *out_len = len / 2;
	return buf;
}

/* End from hostap */

char *
mm_modem_charset_hex_to_utf8 (const char *src, MMModemCharset charset)
{
    char *unconverted;
    const char *iconv_from;
    gsize unconverted_len = 0;

    g_return_val_if_fail (src != NULL, NULL);
    g_return_val_if_fail (charset != MM_MODEM_CHARSET_UNKNOWN, NULL);

    iconv_from = charset_iconv_from (charset);
    g_return_val_if_fail (iconv_from != NULL, FALSE);

    unconverted = hexstr2bin (src, &unconverted_len);
    g_return_val_if_fail (unconverted != NULL, NULL);

    if (charset == MM_MODEM_CHARSET_UTF8 || charset == MM_MODEM_CHARSET_IRA)
        return unconverted;

    return g_convert (unconverted, unconverted_len, "UTF-8//TRANSLIT", iconv_from, NULL, NULL, NULL);
}

