/*
 * Copyright (C) 2009-2012, 2014 Red Hat, Inc.
 *
 * 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.
 *
 * Authors:
 *   Daniel P. Berrange <berrange@redhat.com>
 */

#include <config.h>

#include <stdlib.h>
#include <osinfo/osinfo.h>
#include <check.h>

struct ISOInfo {
    gchar *shortid;
    gchar *filename;
    OsinfoMedia *media;
    GHashTable *langs;
};

static void free_iso(struct ISOInfo *info)
{
    if (!info)
        return;

    g_free(info->filename);
    g_free(info->shortid);
    if (info->media)
        g_object_unref(info->media);
    if (info->langs)
        g_hash_table_unref(info->langs);
    g_free(info);
}

static gboolean load_langs(GFile *file, struct ISOInfo *info, GError **error)
{
    char *path;
    GKeyFile *lang_file;
    GStrv langs;
    GStrv it;

    path = g_file_get_path(file);
    if (path == NULL)
        return FALSE;
    if (!g_str_has_suffix(path, ".txt")) {
        g_free(path);
        return FALSE;
    }
    strcpy(&path[strlen(path) - sizeof(".txt") + 1], ".lng");
    lang_file = g_key_file_new();
    if (!g_key_file_load_from_file(lang_file, path, G_KEY_FILE_NONE, NULL)) {
        g_key_file_free(lang_file);
        g_free(path);
        return FALSE;
    }
    langs = g_key_file_get_string_list(lang_file, "general", "l10n-language",
                                       NULL, NULL);
    if (langs == NULL) {
        g_key_file_free(lang_file);
        g_free(path);
        return FALSE;
    }

    for (it = langs; (it != NULL) && (*it != NULL); it++) {
        g_hash_table_add(info->langs, g_strdup(*it));
    }

    g_strfreev(langs);
    g_key_file_free(lang_file);
    g_free(path);

    return TRUE;
}

static struct ISOInfo *load_iso(GFile *file, const gchar *shortid, const gchar *name, GError **error)
{
    struct ISOInfo *info = g_new0(struct ISOInfo, 1);
    GFileInputStream *fis = NULL;
    GDataInputStream *dis = NULL;
    gchar *line;
    const gchar *arch;
    gint64 vol_size = -1, blk_size = -1;

    if (!(fis = g_file_read(file, NULL, error)))
        goto error;

    if (!(dis = g_data_input_stream_new(G_INPUT_STREAM(fis))))
        goto error;

    info->filename = g_strdup(name);
    info->shortid = g_strdup(shortid);
    info->langs = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
    if (strstr(name, "amd64") ||
             strstr(name, "x64") ||
             strstr(name, "x86_64") ||
             strstr(name, "64bit") ||
             strstr(name, "64-bit"))
        arch = "x86_64";
    else if (strstr(name, "i386") ||
             strstr(name, "i586") ||
             strstr(name, "i686") ||
             strstr(name, "x86") ||
             strstr(name, "32bit") ||
             strstr(name, "32-bit"))
        arch = "i386";
    else if (strstr(name, "ppc") ||
             strstr(name, "powerpc"))
        arch = "ppc";
    else if (strstr(name, "hppa"))
        arch = "hppa";
    else if (strstr(name, "sparc"))
        arch = "sparc";
    else if (strstr(name, "ia64"))
        arch = "ia64";
    else if (strstr(name, "s390x"))
        arch = "s390x";
    else if (strstr(name, "aarch64") ||
             strstr(name, "arm64"))
        arch = "aarch64";
    else if (strstr(name, "armv7l") ||
             strstr(name, "armv7hl") ||
             strstr(name, "armhf"))
        arch = "armv7l";
    else {
        g_debug("Unknown arch in %s", name);
        arch = "i386";
    }

    info->media = osinfo_media_new(name, arch);

    while ((line = g_data_input_stream_read_line(dis, NULL, NULL, error)) != NULL) {
        const gchar *key = NULL;
        char *value = NULL;

        if (g_str_has_prefix(line, "Volume id: ")) {
            key = OSINFO_MEDIA_PROP_VOLUME_ID;
            value = line + strlen("Volume id: ");
        } else if (g_str_has_prefix(line, "Publisher id: ")) {
            key = OSINFO_MEDIA_PROP_PUBLISHER_ID;
            value = line + strlen("Publisher id: ");
        } else if (g_str_has_prefix(line, "System id: ")) {
            key = OSINFO_MEDIA_PROP_SYSTEM_ID;
            value = line + strlen("System id: ");
        } else if (g_str_has_prefix(line, "Application id: ")) {
            key = OSINFO_MEDIA_PROP_APPLICATION_ID;
            value = line + strlen("Application id: ");
        } else if (g_str_has_prefix(line, "Logical block size is: ")) {
            blk_size = (gint64) atoll(line + strlen("Logical block size is: "));
        } else if (g_str_has_prefix(line, "Volume size is: ")) {
            vol_size = atoll(line + strlen("Volume size is: "));
        }

        if (key != NULL && value != NULL && value[0] != '\0')
            osinfo_entity_set_param(OSINFO_ENTITY(info->media), key, value);

        g_free(line);
    }

    if (vol_size > 0 && blk_size > 0)
        osinfo_entity_set_param_int64(OSINFO_ENTITY(info->media),
                                      OSINFO_MEDIA_PROP_VOLUME_SIZE,
                                      vol_size * blk_size);

    if (*error)
        goto error;

    load_langs(file, info, error);

 cleanup:
    if (fis)
        g_object_unref(fis);

    if (dis)
        g_object_unref(dis);

    return info;

 error:
    free_iso(info);
    info = NULL;
    goto cleanup;
}


static GList *load_distro(GFile *dir, const gchar *shortid, GError **error) {
    GFileEnumerator *children = g_file_enumerate_children(dir,
                                                          "standard::*",
                                                          G_FILE_QUERY_INFO_NONE,
                                                          NULL,
                                                          error);
    GFileInfo *childinfo;
    GList *ret = NULL;

    if (!children)
        return NULL;

    while ((childinfo = g_file_enumerator_next_file(children, NULL, error)) != NULL) {
        if (g_file_info_get_file_type(childinfo) != G_FILE_TYPE_REGULAR) {
            g_object_unref(childinfo);
            continue;
        }

        if (!g_str_has_suffix(g_file_info_get_name(childinfo), ".txt")) {
            g_object_unref(childinfo);
            continue;
        }

        GFile *child = g_file_get_child(dir, g_file_info_get_name(childinfo));
        struct ISOInfo *iso = load_iso(child,
                                       shortid,
                                       g_file_info_get_name(childinfo),
                                       error);
        g_object_unref(child);
        g_object_unref(childinfo);

        if (!iso)
            goto error;

        ret = g_list_append(ret, iso);
    }

    if (*error)
        goto error;

 cleanup:
    g_object_unref(children);

    return ret;

 error:
    g_list_foreach(ret, (GFunc)free_iso, NULL);
    g_list_free(ret);
    ret = NULL;
    goto cleanup;
}


static GList *load_distros(GFile *dir, GError **error)
{
    GFileEnumerator *children = g_file_enumerate_children(dir,
                                                          "standard::*",
                                                          G_FILE_QUERY_INFO_NONE,
                                                          NULL,
                                                          error);
    GFileInfo *childinfo;
    GList *ret = NULL;

    if (!children)
        return NULL;

    while ((childinfo = g_file_enumerator_next_file(children, NULL, error)) != NULL) {
        if (g_file_info_get_file_type(childinfo) != G_FILE_TYPE_DIRECTORY) {
            g_object_unref(childinfo);
            continue;
        }

        GFile *child = g_file_get_child(dir, g_file_info_get_name(childinfo));
        GList *isos = load_distro(child, g_file_info_get_name(childinfo), error);

        g_object_unref(child);
        g_object_unref(childinfo);

        if (!isos && *error)
            goto error;
        ret = g_list_concat(ret, isos);
    }

    if (*error)
        goto error;

 cleanup:
    g_object_unref(children);

    return ret;

 error:
    g_list_foreach(ret, (GFunc)free_iso, NULL);
    g_list_free(ret);
    ret = NULL;
    goto cleanup;
}


static GList *load_isos(const gchar *vendor, GError **error)
{
    gchar *path = g_strdup_printf(SRCDIR "/tests/isodata/%s", vendor);
    GList *ret = NULL;
    GFile *f = g_file_new_for_path(path);

    ret = load_distros(f, error);

    g_object_unref(f);
    g_free(path);
    return ret;
}


static void test_langs(struct ISOInfo *info)
{
    GList *langs;
    GList *it;

    /* exit early if there was no associated .lng file */
    if (g_hash_table_size(info->langs) == 0)
        return;

    langs = osinfo_media_get_languages(info->media);

    for (it = langs; it != NULL; it = it->next) {
        fail_unless(g_hash_table_contains(info->langs, it->data),
                    "%s not a known language for ISO %s",
                    it->data, info->filename);
        g_hash_table_remove(info->langs, it->data);
    }
    g_list_free(langs);
    fail_unless(g_hash_table_size(info->langs) == 0,
                "some languages were not identified on ISO %s",
                info->filename);
}

static void test_one(const gchar *vendor)
{
    OsinfoLoader *loader = osinfo_loader_new();
    OsinfoDb *db = osinfo_loader_get_db(loader);
    GList *isos = NULL;
    GList *tmp;
    GError *error = NULL;

    fail_unless(OSINFO_IS_LOADER(loader), "Loader is not a LOADER");
    fail_unless(OSINFO_IS_DB(db), "Db is not a DB");

    osinfo_loader_process_default_path(loader, &error);
    fail_unless(error == NULL, error ? error->message : "none");

    isos = load_isos(vendor, &error);

    fail_unless(isos != NULL, "ISOs must not be NULL");

    tmp = isos;
    while (tmp) {
        struct ISOInfo *info  = tmp->data;
        gboolean matched = osinfo_db_identify_media(db, info->media);
        OsinfoOs *os;

        fail_unless(matched, "ISO %s was not matched by OS %s",
                    info->filename, info->shortid);

        g_object_get(info->media, "os", &os, NULL);
        const gchar *shortid = osinfo_product_get_short_id(OSINFO_PRODUCT(os));
        fail_unless(g_str_equal(shortid, info->shortid),
                    "ISO %s matched OS %s instead of expected %s",
                    info->filename, shortid, info->shortid);
        g_object_unref(G_OBJECT(os));
        test_langs(info);

        tmp = tmp->next;
    }

    g_list_foreach(isos, (GFunc)free_iso, NULL);
    g_list_free(isos);

    g_object_unref(loader);
}

START_TEST(test_fedora)
{
    test_one("fedora");
}
END_TEST

START_TEST(test_rhel)
{
    test_one("rhel");
}
END_TEST

START_TEST(test_ubuntu)
{
    test_one("ubuntu");
}
END_TEST

START_TEST(test_debian)
{
    test_one("debian");
}
END_TEST

START_TEST(test_windows)
{
    test_one("windows");
}
END_TEST

START_TEST(test_freebsd)
{
    test_one("freebsd");
}
END_TEST

START_TEST(test_openbsd)
{
    test_one("openbsd");
}
END_TEST

START_TEST(test_opensuse)
{
    test_one("opensuse");
}
END_TEST

START_TEST(test_centos)
{
    test_one("centos");
}
END_TEST

START_TEST(test_gnome)
{
    test_one("gnome");
}
END_TEST

START_TEST(test_altlinux)
{
    test_one("altlinux");
}
END_TEST

START_TEST(test_mageia)
{
    test_one("mageia");
}
END_TEST

START_TEST(test_sles)
{
    test_one("sles");
}
END_TEST

START_TEST(test_sled)
{
    test_one("sled");
}
END_TEST

static Suite *
list_suite(void)
{
    Suite *s = suite_create("List");
    TCase *tc = tcase_create("Core");
    tcase_set_timeout(tc, 20);

    tcase_add_test(tc, test_fedora);
    tcase_add_test(tc, test_rhel);
    tcase_add_test(tc, test_ubuntu);
    tcase_add_test(tc, test_debian);
    tcase_add_test(tc, test_windows);
    tcase_add_test(tc, test_freebsd);
    tcase_add_test(tc, test_openbsd);
    tcase_add_test(tc, test_opensuse);
    tcase_add_test(tc, test_centos);
    tcase_add_test(tc, test_gnome);
    tcase_add_test(tc, test_altlinux);
    tcase_add_test(tc, test_mageia);
    tcase_add_test(tc, test_sles);
    tcase_add_test(tc, test_sled);
    suite_add_tcase(s, tc);
    return s;
}

int main(void)
{
    int number_failed;
    Suite *s = list_suite();
    SRunner *sr = srunner_create(s);

    /* Make sure we catch unexpected g_warning() */
    g_log_set_always_fatal(G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING);

    /* Upfront so we don't confuse valgrind */
    osinfo_entity_get_type();
    osinfo_db_get_type();
    osinfo_device_get_type();
    osinfo_platform_get_type();
    osinfo_os_get_type();
    osinfo_list_get_type();
    osinfo_devicelist_get_type();
    osinfo_platformlist_get_type();
    osinfo_oslist_get_type();
    osinfo_filter_get_type();

    srunner_run_all(sr, CK_ENV);
    number_failed = srunner_ntests_failed(sr);
    srunner_free(sr);

    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}
/*
 * Local variables:
 *  indent-tabs-mode: nil
 *  c-indent-level: 4
 *  c-basic-offset: 4
 * End:
 */
