#include "../unity.h"
#include "lcut/lcut.h"

/* This file contains some unit tests which don't naturally fit into
 * the main suite of tests represented by runtests.lex and ../../grammar/testcases.csv.
 */

static const UnitExpression* parse_expression(const char* str) 
{
    const UnitExpression* tempue = unity_parse_string(str, UNITY_SYNTAX_FITS);
    if (tempue == NULL) {
        fprintf(stderr, "!!! Invalid test case: can't parse %s\n", str);
        exit(1);
    }
    return tempue;
}

void tc_basic_functionality(lcut_tc_t *tc, void *data) 
{
    const Unit* u;
    const UnitDef* ud;
    const FunctionDef* fd;

    const UnitExpression* ue;

    LCUT_INT_EQUAL(tc, UNITY_SYNTAX_FITS, unity_identify_parser("fits"));
    LCUT_STR_EQUAL(tc, "fits", unity_get_syntax_name(UNITY_SYNTAX_FITS));

    ue = parse_expression("km.Mg/s");
    LCUT_TRUE(tc, unity_check_expression(ue, UNITY_SYNTAX_FITS, UNITY_CHECK_ALL));

    u = unity_get_unit_by_index(ue, 0);
    ud = unity_get_unitdef_from_unit(u);
    LCUT_STR_EQUAL(tc, "http://qudt.org/vocab/unit#Meter", unity_get_unit_uri(ud));
    LCUT_STR_EQUAL(tc, "Metre", unity_get_unit_name(ud));
    LCUT_STR_EQUAL(tc, "http://qudt.org/vocab/quantity#Length", unity_get_unit_type(ud));
    LCUT_STR_EQUAL(tc, "L ", unity_get_unit_dimension(ud));
    LCUT_ASSERT(tc, "should be null", unity_get_unit_description(ud) == NULL);
    LCUT_ASSERT(tc, "should be null", unity_get_unit_latex_form(ud) == NULL);

    ud = unity_get_unitdef_from_string("s", UNITY_SYNTAX_FITS);
    LCUT_STR_EQUAL(tc, "http://qudt.org/vocab/unit#SecondTime", unity_get_unit_uri(ud));
    LCUT_STR_EQUAL(tc, "Second", unity_get_unit_name(ud));
    LCUT_STR_EQUAL(tc, "http://qudt.org/vocab/quantity#Time", unity_get_unit_type(ud));
    LCUT_STR_EQUAL(tc, "T ", unity_get_unit_dimension(ud));
    LCUT_ASSERT(tc, "should be null", unity_get_unit_description(ud) == NULL);
    LCUT_ASSERT(tc, "should be null", unity_get_unit_latex_form(ud) == NULL);
    LCUT_ASSERT(tc, "should be null", unity_get_functiondef_from_unit(u) == NULL);
    
    unity_free_expression(ue);

    // ----

    ue = parse_expression("km/week");

    LCUT_TRUE(tc, unity_check_expression(ue, UNITY_SYNTAX_FITS, UNITY_CHECK_CONSTRAINTS));
    LCUT_TRUE(tc, !unity_check_expression(ue, UNITY_SYNTAX_FITS, UNITY_CHECK_RECOGNISED));
    u = unity_get_unit_by_index(ue, 1);
    ud = unity_get_unitdef_from_unit(u);
    LCUT_ASSERT(tc, "should be null", ud == NULL);
    LCUT_ASSERT(tc, "should be null", unity_get_functiondef_from_unit(u) == NULL);

    unity_free_expression(ue);

    // ----

    ue = parse_expression("log(km)/xfn(week)");

    LCUT_TRUE(tc, unity_check_expression(ue, UNITY_SYNTAX_FITS, UNITY_CHECK_CONSTRAINTS));
    LCUT_TRUE(tc, !unity_check_expression(ue, UNITY_SYNTAX_FITS, UNITY_CHECK_RECOGNISED));
    u = unity_get_unit_by_index(ue, 0);
    fd = unity_get_functiondef_from_unit(u);
    LCUT_ASSERT(tc, "get_functiondef_from_unit should have worked", fd != NULL);
    if (fd != NULL) {
        LCUT_STR_EQUAL(tc, "Common Logarithm (base 10)", fd->description);
        LCUT_STR_EQUAL(tc, "\\log", fd->latex_form);
        LCUT_ASSERT(tc, "should be null", unity_get_unitdef_from_unit(u) == NULL);
    } else {
        // ooops -- just skip
    }

    u = unity_get_unit_by_index(ue, 1);
    LCUT_ASSERT(tc, "should be null", unity_get_functiondef_from_unit(u) == NULL);

    fd = unity_get_functiondef_from_string("log", UNITY_SYNTAX_FITS);
    LCUT_STR_EQUAL(tc, "Common Logarithm (base 10)", fd->description);

    unity_free_expression(ue);

    // ----

    ue = parse_expression("log(km)/log(week)");

    LCUT_TRUE(tc, unity_check_expression(ue, UNITY_SYNTAX_FITS, UNITY_CHECK_CONSTRAINTS));
    LCUT_TRUE(tc, !unity_check_expression(ue, UNITY_SYNTAX_FITS, UNITY_CHECK_RECOGNISED));

    unity_free_expression(ue);
}

void tc_ordering(lcut_tc_t *tc, void *data)
{
    const char* testcases[][2] = {
        { "km.Mg/(pc.s)",		" 0: 0 6/g/1 3/m/1 0/pc/-1 0/s/-1" },
        { "km2/(pc2.s.Mg-1)",		" 1: 0 6/g/1 3/m/2 0/s/-1 0/pc/-2" },
        { "km.s", 			" 2: 0 3/m/1 0/s/1" },
        { "km-1.s",			" 3: 0 0/s/1 3/m/-1" },
        { "km.km2",			" 4: 0 3/m/1 3/m/2" },
        { "km-2.km-1",			" 5: 0 3/m/-1 3/m/-2" },
        { "boo.km",			" 6: 0 3/m/1 0/boo/1" },
        { "boo.wibble",			" 7: 0 0/boo/1 0/wibble/1" },
        { "log(km).km",			" 8: 0 3/m/1 log(3/m/1)" },
        { "log(wibble).km",		" 9: 0 3/m/1 log(0/wibble/1)" },
        { "log(km).log(s)",		"10: 0 log(3/m/1) log(0/s/1)" },
        { "log(km).log(boo)",		"11: 0 log(3/m/1) log(0/boo/1)" },
        { "log(wibble).log(boo)",	"12: 0 log(0/boo/1) log(0/wibble/1)" },
        { "xfn(km).log(km)",		"13: 0 log(3/m/1) xfn(3/m/1)" },
        { "xfn(s).xfn(km)",		"14: 0 xfn(3/m/1) xfn(0/s/1)"  },
        { "xfn(boo).xfn(km)",		"15: 0 xfn(3/m/1) xfn(0/boo/1)" },
        { "xfn(boo).xfn(wibble)",	"16: 0 xfn(0/boo/1) xfn(0/wibble/1)" },
        { NULL, NULL, },
    };
    int i;

    for (i=0; 1; i++) {
        const char *input = testcases[i][0];
        const char *expected = testcases[i][1];
        const UnitExpression* ue;
#define BUFLEN 1024
        char buffer[BUFLEN+1];
        
        if (input == NULL) {
            break;
        }

        ue = parse_expression(input);
        snprintf(buffer, BUFLEN, "%2d: ", i);
        unity_write_formatted(&buffer[4], BUFLEN-4, ue, UNITY_SYNTAX_DEBUG);
        LCUT_STR_EQUAL(tc, expected, buffer);
        unity_free_expression(ue);
#undef BUFLEN
    }
}

void tc_equality(lcut_tc_t *tc, void *data)
{
    const UnitExpression* ue1 = parse_expression("km.s/boo");
    const UnitExpression* ue2 = parse_expression("km.s");
    const UnitExpression* ue3 = parse_expression("km.s/boo2");
    const UnitExpression* ue4 = parse_expression("10+2 km.s/boo");
    const UnitExpression* ue5 = parse_expression("boo-1.s.km1");


    LCUT_TRUE(tc, !unity_equal_expression_p(ue1, ue2));
    LCUT_TRUE(tc, !unity_equal_expression_p(ue1, ue3));
    LCUT_TRUE(tc, !unity_equal_expression_p(ue1, ue4));
    //fprintf(stderr, "ue1=%s\n", unity_write_formatted(buffer, BUFLEN, ue1, "debug"));
    //fprintf(stderr, "ue5=%s\n", unity_write_formatted(buffer, BUFLEN, ue5, "debug"));
    LCUT_TRUE(tc, unity_equal_expression_p(ue1, ue5));

    LCUT_TRUE(tc, unity_get_unit_by_index(ue1, -1) == NULL);
    LCUT_TRUE(tc, unity_get_unit_by_index(ue1, 3) == NULL);
    
    LCUT_TRUE(tc, unity_equal_unit_p(unity_get_unit_by_index(ue1, 0),
                                     unity_get_unit_by_index(ue5, 2)));
    LCUT_TRUE(tc, unity_equal_unit_p(unity_get_unit_by_index(ue1, 1),
                                     unity_get_unit_by_index(ue5, 1)));
    LCUT_TRUE(tc, unity_equal_unit_p(unity_get_unit_by_index(ue1, 2),
                                     unity_get_unit_by_index(ue5, 0)));
}

int main() {
    lcut_ts_t   *suite = NULL;

    LCUT_TEST_BEGIN("Unity C tests", NULL, NULL);

    LCUT_TS_INIT(suite, "Unity C tests", NULL, NULL);
    LCUT_TC_ADD(suite, "Basic functionality", tc_basic_functionality, NULL, NULL, NULL);
    LCUT_TC_ADD(suite, "Ordering", tc_ordering, NULL, NULL, NULL);
    LCUT_TC_ADD(suite, "Equality", tc_equality, NULL, NULL, NULL);
    LCUT_TS_ADD(suite);

    LCUT_TEST_RUN();
    LCUT_TEST_REPORT();
    LCUT_TEST_END();

    LCUT_TEST_RESULT();
}
