#include <stdio.h>
#include <stdlib.h>
#include <check.h>
#include <math.h>

#include "libharkio3.h"

#define EPS 0.00001

harkio_Position** goodPositions(void)
{
    harkio_Position **p = (harkio_Position**) malloc(sizeof(harkio_Position*) * 5);
    int i;

    for (i=0; i<5; i++) {
        float coord[3] = {0.1*i, 0.2*i, 0.3*i};
        p[i] = harkio_Position_new(i, Cartesian, coord, "/home");
    }
    return p;
}

harkio_Position** goodNLenPositions(int len)
{
    harkio_Position **p = (harkio_Position**) malloc(sizeof(harkio_Position*) * len);
    int i;

    for (i=0; i<len; i++) {
        float coord[3] = {0.2*i, 0.3*i, 0.4*i};
        p[i] = harkio_Position_new(i, Cartesian, coord, "/home");
    }
    return p;
}

void goodPositions_destroy(harkio_Position*** p, int length)
{
    int i;
    for (i=0; i<length; i++) {
        harkio_Position_delete((*p) + i);
    }
    free(*p);
    *p = NULL;
    return;
}

START_TEST(test_new)
{
    fprintf(stderr, "START test_new\n");
    char *alltypes[] = {"tsp", "impulse", "noise", "microphone", "soundsource" };
    harkio_Position **p;
    harkio_Positions *ps;
    int i;

    p = goodPositions();
    ps = harkio_Positions_new("tsp", 1, 5, p);
    ck_assert(NULL != ps);
    harkio_Positions_delete(&ps);
    goodPositions_destroy(&p,5);

    // NULL given as pos
    ps = harkio_Positions_new("tsp", 1, 0, NULL);
    ck_assert(NULL != ps);
    harkio_Positions_delete(&ps);

    // all types
    for (i=0; i<5; i++) {
        p = goodPositions();
        ps = harkio_Positions_new(alltypes[i], 1, 5, p);
        ck_assert(NULL != ps);

        ck_assert_str_eq(alltypes[i], ps->type);
        harkio_Positions_delete(&ps);
        goodPositions_destroy(&p,5);
    }

    // wrong type
    p = goodPositions();
    ps = harkio_Positions_new("wrong", 1, 5, p);
    ck_assert(NULL == ps);
    harkio_Positions_delete(&ps);
    goodPositions_destroy(&p,5); // typeがまちがっていれば、pは割当たらない

    p = goodPositions();
    ps = harkio_Positions_new(NULL, 1, 5, p);
    ck_assert(ps->type == NULL);
    harkio_Positions_delete(&ps);
    goodPositions_destroy(&p,5);

    // wrong size
    p = goodPositions();
    ps = harkio_Positions_new("tsp", 1, -1, p);
    ck_assert(NULL == ps);
    harkio_Positions_delete(&ps); // sizeがまちがっていれば、pは割当たらない
    goodPositions_destroy(&p,5);


    fprintf(stderr, "END test_new\n");
}
END_TEST

START_TEST(test_newFromXMLbuffer)
{
    fprintf(stderr, "START test_newFromXMLbuffer\n");
    int i;

    static const char* positions_xml_cart = "<positions type=\"tsp\" coordinate=\"cartesian\">"
                                            "<position x=\"1.000\" y=\"2.000\" z=\"3.000\" id=\"0\" path=\"/home/tsp00.wav\"/>"
                                            "</positions>";

    static const char* positions_xml_pola = "<positions type=\"tsp\" coordinate=\"polar\">"
                                            "<position azimuth=\"1.000\" elevation=\"2.000\" radius=\"3.000\" id=\"0\" path=\"/home/tsp00.wav\"/>"
                                            "</positions>";

    harkio_Positions* poses;

    poses = harkio_Positions_newFromXMLbuffer(
                (char*)positions_xml_cart,
                strlen(positions_xml_cart),
                NULL
            );
    ck_assert(poses != NULL);

    for (i = 0; i < poses->size; i++) {
        harkio_Position* pos = poses->pos[i];
        ck_assert(pos != NULL);
        // fprintf(stderr, "x=%f, y=%f, z=%f\n", pos->coord[0], pos->coord[1], pos->coord[2]);
        ck_assert(fabs(1.0 - pos->coord[0]) < HARK_EPS);
        ck_assert(fabs(2.0 - pos->coord[1]) < HARK_EPS);
        ck_assert(fabs(3.0 - pos->coord[2]) < HARK_EPS);
        ck_assert(poses != NULL);
    }
    harkio_Positions_delete(&poses);



    poses = harkio_Positions_newFromXMLbuffer(
                (char*)positions_xml_pola,
                strlen(positions_xml_pola),
                NULL
            );
    ck_assert(poses != NULL);
    for (i = 0; i < poses->size; i++) {
        harkio_Position* pos = poses->pos[i];
        ck_assert(pos != NULL);
        ck_assert(fabs(1.0 - pos->coord[0]) < HARK_EPS);
        ck_assert(fabs(2.0 - pos->coord[1]) < HARK_EPS);
        ck_assert(fabs(3.0 - pos->coord[2]) < HARK_EPS);
        // fprintf(stderr, "x=%f, y=%f, z=%f\n", pos->coord[0], pos->coord[1], pos->coord[2]);
        ck_assert(poses != NULL);
    }
    harkio_Positions_delete(&poses);


    fprintf(stderr, "END test_newFromXMLbuffer\n");
}
END_TEST

START_TEST(test_delete)
{
    fprintf(stderr, "START test_delete\n");
    harkio_Position **p = goodPositions();
    harkio_Positions *ps = harkio_Positions_new("tsp", 1, 5, p);
    harkio_Positions_delete(&ps);
    ck_assert(ps == NULL);
    harkio_Positions_delete(&ps);
    ck_assert(ps == NULL);
    harkio_Positions_delete(NULL);
    goodPositions_destroy(&p,5);
    fprintf(stderr, "END test_delete\n");
}
END_TEST

START_TEST(test_append)
{
    fprintf(stderr, "START test_append\n");
    int i;
    harkio_Position **p;
    harkio_Positions *ps;
    float coord[3] = {100, 101, 102};
    harkio_Position *tmpp = harkio_Position_new(100, Cartesian, coord, "/etc");

    // append some to zero initialized poses
    p = goodPositions();
    ps = harkio_Positions_new("tsp", 1, 0, NULL);
    for (i=0; i<5; i++) {
        harkio_Positions_append(ps, p[i]);
    }
    free(p);
    p = NULL;

    harkio_Positions_append(ps, tmpp);

    ck_assert_int_eq(ps->size, 6);
    ck_assert_str_eq(ps->pos[5]->path, "/etc");
    ck_assert_int_eq(ps->pos[5]->id, 100);
    ck_assert(fabs(coord[0] - ps->pos[5]->coord[0]) < EPS);
    ck_assert(fabs(coord[1] - ps->pos[5]->coord[1]) < EPS);
    ck_assert(fabs(coord[2] - ps->pos[5]->coord[2]) < EPS);

    harkio_Positions_print(ps);
    harkio_Positions_delete(&ps);


    ps = harkio_Positions_new("tsp", 1, 0, NULL);
    for (i=0; i<100; i++) {
        harkio_Positions_append(ps, harkio_Position_new(-1, Cartesian, coord, "/etc"));
    }
    for (i=0; i<100; i++) {
        ck_assert_int_eq(harkio_Positions_getById(ps, i)->id, i);
    }

    ck_assert(EXIT_FAILURE == harkio_Positions_append(NULL, tmpp));
    ck_assert(EXIT_FAILURE == harkio_Positions_append(ps, NULL));
    harkio_Positions_delete(&ps);

    // try to append some to non-zero initialized poses (not allowed)
    p = goodPositions();
    ps = harkio_Positions_new("tsp", 1, 5, p);
    ck_assert(EXIT_FAILURE == harkio_Positions_append(ps, tmpp));
    harkio_Positions_delete(&ps);
    goodPositions_destroy(&p,5);

    fprintf(stderr, "END test_append\n");
}
END_TEST

START_TEST(test_removeById)
{
    fprintf(stderr, "START test_removeById\n");
    int ret, i;
    harkio_Position **p = goodPositions();
    harkio_Position **p1 = goodPositions();
    harkio_Positions *ps1 = harkio_Positions_new("tsp", 1, 0, NULL);
    for (i=0; i<5; i++) {
        harkio_Positions_append(ps1, p1[i]);
    }
    free(p1);
    p1=NULL;
    int size = ps1->size;

    // first remove
    ret = harkio_Positions_removeById(ps1, 0);
    ck_assert_int_eq(ret, EXIT_SUCCESS);
    ck_assert_int_eq(size - 1, ps1->size);

    ck_assert(NULL == harkio_Positions_getById(ps1, 0));
    for (i=1; i<5; i++) {
        harkio_Position_print(p[i]);
        harkio_Position_print(harkio_Positions_getById(ps1, i));
        ck_assert(harkio_Position_isEqual(p[i], harkio_Positions_getById(ps1, i)));
    }
    // second remove
    ret = harkio_Positions_removeById(ps1, 2);
    ck_assert_int_eq(ret, EXIT_SUCCESS);

    // non-existing remove
    ret = harkio_Positions_removeById(ps1, 0);
    ck_assert_int_eq(ret, EXIT_FAILURE);
    ck_assert_int_eq(size - 2, ps1->size);

    // over the size
    ret = harkio_Positions_removeById(ps1, 100);
    ck_assert_int_eq(ret, EXIT_FAILURE);
    ck_assert_int_eq(size - 2, ps1->size);

    // try to remove some to non-zero initialized poses (not allowed)
    harkio_Position **p2 = goodPositions();
    harkio_Positions *ps2 = harkio_Positions_new("tsp", 1, 5, p2);
    ck_assert(harkio_Positions_removeById(ps2, 0) == EXIT_FAILURE);

    // NULL
    ck_assert(harkio_Positions_removeById(NULL, 0) == EXIT_FAILURE);

    harkio_Positions_delete(&ps1);
    harkio_Positions_delete(&ps2);
    goodPositions_destroy(&p,5);
    goodPositions_destroy(&p2,5);
    fprintf(stderr, "END test_removeById\n");
}
END_TEST

START_TEST(test_removeByCoord)
{
    fprintf(stderr, "START test_removeByCoord\n");
    harkio_Position **p;
    harkio_Positions *ps;
    int i;
    int result;
    float tolerance = 0.0001;

    float coord1[3] = {0.1, 0.2, 0.3};
    float coord2[3] = {0.1, 0.2, 0.35};


    p = goodPositions();
    ps = harkio_Positions_new("tsp", 1, 0, NULL);
    for (i=0; i<5; i++) {
        harkio_Positions_append(ps, p[i]);
    }
    free(p);
    p = NULL;

    int size = ps->size;
    result = harkio_Positions_removeByCoord(ps, Cartesian, coord1, tolerance);
    ck_assert_int_eq(result, EXIT_SUCCESS);
    ck_assert_int_eq(ps->size, size - 1);

    result = harkio_Positions_removeByCoord(ps, Cartesian, coord2, tolerance);
    ck_assert_int_eq(result, EXIT_FAILURE);
    ck_assert_int_eq(ps->size, size - 1);
    harkio_Positions_delete(&ps);

    // try to remove some to non-zero initialized poses (not allowed)
    p = goodPositions();
    ps = harkio_Positions_new("tsp", 1, 5, p);
    result = harkio_Positions_removeByCoord(ps, Cartesian, coord1, tolerance);
    ck_assert_int_eq(result, EXIT_FAILURE);
    harkio_Positions_delete(&ps);
    goodPositions_destroy(&p,5);

    fprintf(stderr, "END test_removeByCoord\n");
}
END_TEST

START_TEST(test_getById)
{
    fprintf(stderr, "START test_getById\n");
    harkio_Position **p = goodPositions();
    harkio_Positions *ps = harkio_Positions_new("tsp", 1, 5, p);
    int i;
    for (i=0; i<ps->size; i++)
        ck_assert(p[i] == harkio_Positions_getById(ps, i));
    harkio_Positions_delete(&ps);
    goodPositions_destroy(&p,5);

    ck_assert(harkio_Positions_getById(NULL, 0) == NULL);

    fprintf(stderr, "END test_getById\n");
}
END_TEST

START_TEST(test_getByCoord)
{
    harkio_Position **p = goodPositions();
    harkio_Positions *ps = harkio_Positions_new("tsp", 1, 5, p);
    float tolerance = 0.0001;

    float coord1[3] = {0.1, 0.2, 0.3};
    float coord2[3] = {0.1, 0.2, 0.35};

    ck_assert(harkio_Positions_getByCoord(ps, Cartesian, coord1, tolerance) == p[1]);
    ck_assert(harkio_Positions_getByCoord(ps, Cartesian, coord2, tolerance) == NULL);

    ck_assert(harkio_Positions_getByCoord(NULL, Cartesian, coord1, tolerance) == NULL);

    ck_assert(harkio_Positions_getByCoord(ps, Polar, coord1, tolerance) == NULL);

    harkio_Positions_delete(&ps);
    goodPositions_destroy(&p,5);
}
END_TEST

START_TEST(test_compare)
{
    harkio_Position **pa = goodPositions();
    harkio_Position **pb = goodPositions();
    harkio_Position **pc = goodNLenPositions(5);
    harkio_Position **pd = goodNLenPositions(3);
    harkio_Position **pe = goodPositions();
    harkio_Position **pf = goodPositions();
    harkio_Position **pg = goodPositions();

    harkio_Positions *a = harkio_Positions_new("tsp", 1, 5, pa);
    harkio_Positions *b = harkio_Positions_new("tsp", 1, 5, pb);
    harkio_Positions *c = harkio_Positions_new("tsp", 1, 5, pc);
    harkio_Positions *d = harkio_Positions_new("tsp", 1, 3, pd);
    harkio_Positions *e = harkio_Positions_new("noise", 1, 5, pe);
    harkio_Positions *f = harkio_Positions_new(NULL, 1, 5, pf);
    harkio_Positions *g = harkio_Positions_new("tsp", 2, 5, pg);

    ck_assert_int_eq(harkio_Positions_isEqual(a, b), 1);
    ck_assert_int_eq(harkio_Positions_isEqual(a, c), 0);
    ck_assert_int_eq(harkio_Positions_isEqual(a, d), 0);

    ck_assert_int_eq(harkio_Positions_isEqual(a, NULL), 0);
    ck_assert_int_eq(harkio_Positions_isEqual(NULL, a), 0);
    ck_assert_int_eq(harkio_Positions_isEqual(a, e), 0);
    ck_assert_int_eq(harkio_Positions_isEqual(a, f), 0);
    ck_assert_int_eq(harkio_Positions_isEqual(a, g), 0);


    harkio_Positions_delete(&a);
    harkio_Positions_delete(&b);
    harkio_Positions_delete(&c);
    harkio_Positions_delete(&d);
    harkio_Positions_delete(&e);
    harkio_Positions_delete(&f);
    harkio_Positions_delete(&g);
    goodPositions_destroy(&pa,5);
    goodPositions_destroy(&pb,5);
    goodPositions_destroy(&pc,5);
    goodPositions_destroy(&pd,3);
    goodPositions_destroy(&pe,5);
    goodPositions_destroy(&pf,5);
    goodPositions_destroy(&pg,5);
}
END_TEST

START_TEST(test_print)
{
    harkio_Positions *ps = harkio_Positions_new("tsp", 1, 5, goodPositions());
    harkio_Positions_print(ps);
    goodPositions_destroy(&(ps->pos),5);
    harkio_Positions_delete(&ps);
}
END_TEST

START_TEST(test_smallSetGet)
{

    harkio_Positions *ps = harkio_Positions_new("tsp", 1, 5, goodPositions());

    ck_assert_int_eq(strcmp("tsp", harkio_Positions_getType(ps)), 0);
    ck_assert_int_eq(harkio_Positions_getSize(ps), 5);
    ck_assert_int_eq(harkio_Positions_getFrame(ps), 1);

    ck_assert(harkio_Positions_getType(NULL) == NULL);
    ck_assert_int_eq(harkio_Positions_getSize(NULL), -1);
    ck_assert_int_eq(harkio_Positions_getFrame(NULL), -1);

    goodPositions_destroy(&(ps->pos),5);
    harkio_Positions_delete(&ps);
}
END_TEST

Suite* str_suite(void)
{
    Suite *suite = suite_create("libharkio3_positions");

    TCase *tcase1 = tcase_create("new/delete");
    tcase_add_test(tcase1, test_new);
    tcase_add_test(tcase1, test_newFromXMLbuffer);
    tcase_add_test(tcase1, test_delete);
    suite_add_tcase(suite, tcase1);

    TCase *tcase2 = tcase_create("manipulation");
    tcase_add_test(tcase2, test_append);
    tcase_add_test(tcase2, test_getById);
    tcase_add_test(tcase2, test_getByCoord);
    tcase_add_test(tcase2, test_removeById);
    tcase_add_test(tcase2, test_removeByCoord);
    tcase_add_test(tcase2, test_smallSetGet);
    suite_add_tcase(suite, tcase2);

    TCase *tcase4 = tcase_create("utilities");
    tcase_add_test(tcase4, test_print);
    tcase_add_test(tcase4, test_compare);
    suite_add_tcase(suite, tcase4);

    return suite;
}

int main(int argc, char *argv[])
{
    int number_failed;
    Suite *suite = str_suite();
    SRunner *runner = srunner_create(suite);
    srunner_run_all(runner, CK_NORMAL);
    number_failed = srunner_ntests_failed(runner);
    srunner_free(runner);
    return number_failed;
}
