/* Copyright (C) 2018-2021  Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301, USA.
 */


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

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

#include <igloo/cs.h>
#include <igloo/tap.h>
#include <igloo/error.h>

struct vector {
    const char *in;
    const char *out;
};

static const struct vector skip_spaces[] = {
    {"a", "a"},
    {" a", "a"},
    {"  a", "a"},
    {"a ", "a "},
    {"a b", "a b"}
};


static const struct vector to_lower[] = {
    {"a", "a"},
    {"A", "a"},
    {"0", "0"},
    {"ab", "ab"},
    {"Ab", "ab"},
    {"AB", "ab"},
    {"0b", "0b"},
    {"0B", "0b"}
};
static const struct vector to_upper[] = {
    {"a", "A"},
    {"A", "A"},
    {"0", "0"},
    {"ab", "AB"},
    {"Ab", "AB"},
    {"AB", "AB"},
    {"0b", "0B"},
    {"0B", "0B"}
};
static const struct vector to_lower_first[] = {
    {"a", "a"},
    {"A", "a"},
    {"0", "0"},
    {"ab", "ab"},
    {"Ab", "ab"},
    {"AB", "aB"},
    {"0b", "0b"},
    {"0B", "0B"}
};
static const struct vector to_upper_first[] = {
    {"a", "A"},
    {"A", "A"},
    {"0", "0"},
    {"ab", "Ab"},
    {"Ab", "Ab"},
    {"AB", "AB"},
    {"0b", "0b"},
    {"0B", "0B"}
};

static void test_vectors(const struct vector *vectors, size_t len, const char *func, igloo_error_t (*check)(char *in, char **out))
{
    size_t i;

    for (i = 0; i < len; i++) {
        const struct vector *v = &(vectors[i]);
        char buffer[80];
        char *in;
        char *out;
        bool res;

        if (v->in) {
            if (strlen(v->in) >= sizeof(buffer)) {
                igloo_tap_bail_out("ERROR: Vector is bigger than buffer");
                return;
            }

            strncpy(buffer, v->in, sizeof(buffer));
            in = buffer;
        } else {
            in = NULL;
        }

        igloo_tap_test_success(func, check(in, &out));

        if (!v->out && !out) {
            res = true;
        } else if (v->out && out && strcmp(v->out, out) == 0) {
            res = true;
        } else {
            res = false;
        }

        igloo_tap_test("output match", res);
    }
}

static igloo_error_t skip_spaces_impl(char *in, char **out)
{
    *out = in;
    return igloo_cs_skip_spaces((const char **)out);
}

static void test_skip_spaces(void)
{
    test_vectors(skip_spaces, sizeof(skip_spaces)/sizeof(*skip_spaces), "igloo_cs_skip_spaces", skip_spaces_impl);
}

static igloo_error_t to_lower_impl(char *in, char **out)
{
    *out = in;
    return igloo_cs_to_lower(in);
}

static void test_to_lower(void)
{
    test_vectors(to_lower, sizeof(to_lower)/sizeof(*to_lower), "igloo_cs_to_lower", to_lower_impl);
}

static igloo_error_t to_upper_impl(char *in, char **out)
{
    *out = in;
    return igloo_cs_to_upper(in);
}

static void test_to_upper(void)
{
    test_vectors(to_upper, sizeof(to_upper)/sizeof(*to_upper), "igloo_cs_to_upper", to_upper_impl);
}

static igloo_error_t to_lower_first_impl(char *in, char **out)
{
    *out = in;
    return igloo_cs_to_lower_first(in);
}

static void test_to_lower_first(void)
{
    test_vectors(to_lower_first, sizeof(to_lower_first)/sizeof(*to_lower_first), "igloo_cs_to_lower_first", to_lower_first_impl);
}

static igloo_error_t to_upper_first_impl(char *in, char **out)
{
    *out = in;
    return igloo_cs_to_upper_first(in);
}

static void test_to_upper_first(void)
{
    test_vectors(to_upper_first, sizeof(to_upper_first)/sizeof(*to_upper_first), "igloo_cs_to_upper_first", to_upper_first_impl);
}

static void test_to_bool(void)
{
    static const struct {
        const char *in;
        const bool res;
    } vectors[] = {
        {"true", true},
        {"1", true},
        {"yes", true},
        {"false", false},
        {"0", false},
        {"no", false},
    };
    bool res;
    size_t i;
    char testname[80];

    for (i = 0; i < (sizeof(vectors)/sizeof(*vectors)); i++) {
        snprintf(testname, sizeof(testname), "igloo_cs_to_bool str=\"%s\"", vectors[i].in);
        res = !vectors[i].res;
        igloo_tap_test_success(testname, igloo_cs_to_bool(vectors[i].in, &res));
        igloo_tap_test("res match", res == vectors[i].res);
    }

    igloo_tap_test_error("igloo_cs_to_bool str=\"\"", igloo_ERROR_ILLSEQ, igloo_cs_to_bool("", &res));
    igloo_tap_test_error("igloo_cs_to_bool str=\"a\"", igloo_ERROR_ILLSEQ, igloo_cs_to_bool("a", &res));
    igloo_tap_test_error("igloo_cs_to_bool str=\"5\"", igloo_ERROR_ILLSEQ, igloo_cs_to_bool("5", &res));
    igloo_tap_test_error("igloo_cs_to_bool str=\"-1\"", igloo_ERROR_ILLSEQ, igloo_cs_to_bool("-1", &res));
}

static void test_to_int(void)
{
    static const struct {
        const char *in;
        const int res;
    } vectors[] = {
        {"0", 0},
        {"1", 1},
        {"-1", -1},
        {" 0", 0},
        {" 1", 1},
        {" -1", -1},
        {" 0 ", 0},
        {" 1 ", 1},
        {" -1 ", -1},
        {"256", 256},
        {"-256", -256},
        {"32767", 32767},
        {"-32768", -32768}
    };
    int res;
    size_t i;
    char testname[80];

    for (i = 0; i < (sizeof(vectors)/sizeof(*vectors)); i++) {
        snprintf(testname, sizeof(testname), "igloo_cs_to_int str=\"%s\"", vectors[i].in);
        res = ~vectors[i].res;
        igloo_tap_test_success(testname, igloo_cs_to_int(vectors[i].in, &res));
        igloo_tap_test("res match", res == vectors[i].res);
    }

    igloo_tap_test_error("igloo_cs_to_int str=\"\"", igloo_ERROR_ILLSEQ, igloo_cs_to_int("", &res));
    igloo_tap_test_error("igloo_cs_to_int str=\"a\"", igloo_ERROR_ILLSEQ, igloo_cs_to_int("a", &res));
    igloo_tap_test_error("igloo_cs_to_int str=\"0g\"", igloo_ERROR_ILLSEQ, igloo_cs_to_int("0g", &res));
    igloo_tap_test_error("igloo_cs_to_int str=\"0xg\"", igloo_ERROR_ILLSEQ, igloo_cs_to_int("0xg", &res));
}

static void test_to_uint(void)
{
    static const struct {
        const char *in;
        const unsigned int res;
    } vectors[] = {
        {"0", 0},
        {"1", 1},
        {" 0", 0},
        {" 1", 1},
        {" 0 ", 0},
        {" 1 ", 1},
        {"256", 256},
        {"32767", 32767}
    };
    unsigned int res;
    size_t i;
    char testname[80];

    for (i = 0; i < (sizeof(vectors)/sizeof(*vectors)); i++) {
        snprintf(testname, sizeof(testname), "igloo_cs_to_uint str=\"%s\"", vectors[i].in);
        res = ~vectors[i].res;
        igloo_tap_test_success(testname, igloo_cs_to_uint(vectors[i].in, &res));
        igloo_tap_test("res match", res == vectors[i].res);
    }

    igloo_tap_test_error("igloo_cs_to_uint str=\"\"", igloo_ERROR_ILLSEQ, igloo_cs_to_uint("", &res));
    igloo_tap_test_error("igloo_cs_to_uint str=\"a\"", igloo_ERROR_ILLSEQ, igloo_cs_to_uint("a", &res));
    igloo_tap_test_error("igloo_cs_to_uint str=\"0g\"", igloo_ERROR_ILLSEQ, igloo_cs_to_uint("0g", &res));
    igloo_tap_test_error("igloo_cs_to_uint str=\"0xg\"", igloo_ERROR_ILLSEQ, igloo_cs_to_uint("0xg", &res));
    igloo_tap_test_error("igloo_cs_to_uint str=\"-1\"", igloo_ERROR_RANGE, igloo_cs_to_uint("-1", &res));
}


static void test_api_errors(void) {
    const char *null_pointer = NULL;
    bool rb;
    int rsi;
    unsigned int rui;
    char *rs;

    igloo_tap_test_error("igloo_cs_skip_spaces str=NULL", igloo_ERROR_FAULT, igloo_cs_skip_spaces(NULL));
    igloo_tap_test_error("igloo_cs_skip_spaces *str=NULL", igloo_ERROR_FAULT, igloo_cs_skip_spaces(&null_pointer));

    igloo_tap_test_error("igloo_cs_to_bool str=NULL", igloo_ERROR_FAULT, igloo_cs_to_bool(NULL, &rb));
    igloo_tap_test_error("igloo_cs_to_bool res=NULL", igloo_ERROR_FAULT, igloo_cs_to_bool("", NULL));
    igloo_tap_test_error("igloo_cs_to_bool str=NULL, res=NULL", igloo_ERROR_FAULT, igloo_cs_to_bool(NULL, NULL));

    igloo_tap_test_error("igloo_cs_to_int str=NULL", igloo_ERROR_FAULT, igloo_cs_to_int(NULL, &rsi));
    igloo_tap_test_error("igloo_cs_to_int res=NULL", igloo_ERROR_FAULT, igloo_cs_to_int("", NULL));
    igloo_tap_test_error("igloo_cs_to_int str=NULL, res=NULL", igloo_ERROR_FAULT, igloo_cs_to_int(NULL, NULL));

    igloo_tap_test_error("igloo_cs_to_uint str=NULL", igloo_ERROR_FAULT, igloo_cs_to_uint(NULL, &rui));
    igloo_tap_test_error("igloo_cs_to_uint res=NULL", igloo_ERROR_FAULT, igloo_cs_to_uint("", NULL));
    igloo_tap_test_error("igloo_cs_to_uint str=NULL, res=NULL", igloo_ERROR_FAULT, igloo_cs_to_uint(NULL, NULL));

    igloo_tap_test_error("igloo_cs_to_lower str=NULL", igloo_ERROR_FAULT, igloo_cs_to_lower(NULL));
    igloo_tap_test_error("igloo_cs_to_upper str=NULL", igloo_ERROR_FAULT, igloo_cs_to_upper(NULL));
    igloo_tap_test_error("igloo_cs_to_lower_first str=NULL", igloo_ERROR_FAULT, igloo_cs_to_lower_first(NULL));
    igloo_tap_test_error("igloo_cs_to_upper_first str=NULL", igloo_ERROR_FAULT, igloo_cs_to_upper_first(NULL));

    rs = NULL;
    igloo_tap_test_error("igloo_cs_to_hex in=NULL, len=0", igloo_ERROR_FAULT, igloo_cs_to_hex(NULL, &rs, 0));
    igloo_tap_test("rs == NULL", rs == NULL);
    rs = NULL;
    igloo_tap_test_error("igloo_cs_to_hex in=NULL, len=1", igloo_ERROR_FAULT, igloo_cs_to_hex(NULL, &rs, 1));
    igloo_tap_test("rs == NULL", rs == NULL);
    igloo_tap_test_error("igloo_cs_to_hex res=NULL, len=0", igloo_ERROR_FAULT, igloo_cs_to_hex("", NULL, 0));
    igloo_tap_test_error("igloo_cs_to_hex res=NULL, len=1", igloo_ERROR_FAULT, igloo_cs_to_hex("a", NULL, 1));
}

int main (void) {
    igloo_tap_init();
    igloo_tap_exit_on(igloo_TAP_EXIT_ON_FIN, NULL);
    igloo_tap_group_run("skip spaces", test_skip_spaces);
    igloo_tap_group_run("to upper", test_to_upper);
    igloo_tap_group_run("to lower", test_to_lower);
    igloo_tap_group_run("to upper first", test_to_upper_first);
    igloo_tap_group_run("to lower first", test_to_lower_first);
    igloo_tap_group_run("to bool", test_to_bool);
    igloo_tap_group_run("to int", test_to_int);
    igloo_tap_group_run("to uint", test_to_uint);
    igloo_tap_group_run("API errors", test_api_errors);
    igloo_tap_fin();

    return EXIT_FAILURE; // return failure as we should never reach this point!
}
