/*
Unit tests for geometry module.
*/

#include <stdio.h>
#include <math.h>
#include "minunit.h"
#include "../lib/geometry.h"
#include "../lib/constants.h"

/* To store fail messages */
char msg[1000];


static char * test_split_tess()
{
    TESSEROID tess = {1, 2, 4, -1, 1, 5, 7},
              expect[] = {{1, 2, 3, -1, 0, 5, 6}, {1, 3, 4, -1, 0, 5, 6},
                        {1, 2, 3, 0, 1, 5, 6}, {1, 3, 4, 0, 1, 5, 6},
                        {1, 2, 3, -1, 0, 6, 7}, {1, 3, 4, -1, 0, 6, 7},
                        {1, 2, 3, 0, 1, 6, 7}, {1, 3, 4, 0, 1, 6, 7}},
              res[8];
    int i, n;

    n = split_tess(tess, 2, 2, 2, res);
    sprintf(msg, "splitting in %d instead of 8", n);
    mu_assert(n == 8, msg);
    for(i = 0; i < 8; i++)
    {
        sprintf(msg, "failed for split %d: %g %g %g %g %g %g %g", i, res[i].w,
                res[i].e, res[i].s, res[i].n, res[i].r1, res[i].r2,
                res[i].density);
        mu_assert(res[i].w == expect[i].w && res[i].e == expect[i].e &&
                  res[i].s == expect[i].s && res[i].n == expect[i].n &&
                  res[i].r1 == expect[i].r1 && res[i].r2 == expect[i].r2 &&
                  res[i].density == expect[i].density, msg);
    }
    return 0;
}


static char * test_split_uneven_tess()
{
    TESSEROID tess = {1, 2, 4, -1, 1, 5, 7},
              expect[] = {{1, 2, 3, -1, 0, 5, 7}, {1, 3, 4, -1, 0, 5, 7},
                        {1, 2, 3, 0, 1, 5, 7}, {1, 3, 4, 0, 1, 5, 7}},
              res[4];
    int i, n;

    n = split_tess(tess, 2, 2, 1, res);
    sprintf(msg, "splitting in %d instead of 4", n);
    mu_assert(n == 4, msg);
    for(i = 0; i < 4; i++)
    {
        sprintf(msg, "failed for split %d: %g %g %g %g %g %g %g", i, res[i].w,
                res[i].e, res[i].s, res[i].n, res[i].r1, res[i].r2,
                res[i].density);
        mu_assert(res[i].w == expect[i].w && res[i].e == expect[i].e &&
                  res[i].s == expect[i].s && res[i].n == expect[i].n &&
                  res[i].r1 == expect[i].r1 && res[i].r2 == expect[i].r2 &&
                  res[i].density == expect[i].density, msg);
    }
    return 0;
}

static char * test_prism_volume()
{
    PRISM prisms[4] = {
        {0,0,1,0,1,0,1,0,0,0},
        {0,0,2,0,1,0,1,0,0,0},
        {0,0,2,0,2,0,2,0,0,0},
        {0,1,2,-1,1,2,3,0,0,0}};
    double pvolumes[4] = {1, 2, 8, 2};
    double res;
    int i;

    for(i = 0; i < 4; i++)
    {
        res = prism_volume(prisms[i]);
        sprintf(msg, "(prism %d) expected %g, got %g", i, pvolumes[i], res);
        mu_assert(res == pvolumes[i], msg);
    }

    return 0;
}


static char * test_tess_volume()
{
    TESSEROID tesses[4] = {{0,0,360,-90,90,0,1}, {0,0,360,0,90,0,1},
                                 {0,180,360,0,90,0,1}, {0,0,90,-90,0,0,1}};
    double tvolumes[4] = {4.188790205, 2.094395102, 1.047197551, 0.523598776};
    double res;
    int i;

    for(i = 0; i < 4; i++)
    {
        res = tess_volume(tesses[i]);
        sprintf(msg, "(tess %d) expected %g, got %g", i, tvolumes[i], res);
        mu_assert_almost_equals(res, tvolumes[i], pow(10, -8), msg);
    }

    return 0;
}


static char * test_tess_total_mass()
{
    TESSEROID tesses[4] = {{1,0,360,-90,90,0,1}, {1,0,360,0,90,0,1},
                                 {1,180,360,0,90,0,1}, {1,0,90,-90,0,0,1}};
    double tvolumes[4] = {4.188790205, 2.094395102, 1.047197551, 0.523598776};
    double res, expect;
    int i;

    res = tess_total_mass(tesses, 4);

    for(expect = 0, i = 0; i < 4; i++)
    {
        expect += tvolumes[i];
    }

    sprintf(msg, "(tess %d) expected %g, got %g", i, expect, res);
    mu_assert_almost_equals(res, expect, pow(10, -6), msg);

    return 0;
}


static char * test_tess_range_mass()
{
    TESSEROID tesses[4] = {{1,0,360,-90,90,0,1}, {-1,0,360,0,90,0,1},
                                 {-1,180,360,0,90,0,1}, {1,0,90,-90,0,0,1}};
    double tvolumes[4] = {4.188790205, 2.094395102, 1.047197551, 0.523598776};
    double res, expect;

    res = tess_range_mass(tesses, 4, 0, 1);
    expect = tvolumes[0] + tvolumes[3];
    sprintf(msg, "Expected %g, got %g", expect, res);
    mu_assert_almost_equals(res, expect, pow(10, -6), msg);

    return 0;
}


static char * test_tess2prism()
{
    int i;
    double expect, res;
    double lons[4] = {0.5, 185, 180, -2.5},
           lats[4] = {0.5, 82.5, -80, 4},
           rs[4] = {6001000, 6301000, 6000000, 6505000},
           zs[4] = {1000, 1000, 500000, 5000};
    PRISM prism;
    TESSEROID tesses[4] = {
        {1,0,1,0,1,6000000,6001000},
        {1,180,190,80,85,6300000,6301000},
        {1,160,200,-90,-70,5500000,6000000},
        {1,-10,5,-7,15,6500000,6505000}};

    for(i = 0; i <  4; i++)
    {
        tess2prism(tesses[i], &prism);
        /* check the volume */
        res = prism_volume(prism);
        expect = tess_volume(tesses[i]);
        sprintf(msg, "(tess %d) expected volume %g, got %g", i, expect, res);
        mu_assert_almost_equals((double)(res - expect)/expect, 0., 0.01, msg);
        /* check the mass */
        res *= prism.density;
        expect *= tesses[i].density;
        sprintf(msg, "(tess %d) expected mass %g, got %g", i, expect, res);
        mu_assert_almost_equals((double)(res - expect)/expect, 0., 0.01, msg);
        /* check the coordinates */
        res = prism.lon;
        expect = lons[i];
        sprintf(msg, "(tess %d) expected lon %g, got %g", i, expect, res);
        mu_assert_almost_equals((double)(res - expect)/expect, 0., 0.0001, msg);
        res = prism.lat;
        expect = lats[i];
        sprintf(msg, "(tess %d) expected lat %g, got %g", i, expect, res);
        mu_assert_almost_equals((double)(res - expect)/expect, 0., 0.0001, msg);
        res = prism.r;
        expect = rs[i];
        sprintf(msg, "(tess %d) expected r %g, got %g", i, expect, res);
        mu_assert_almost_equals((double)(res - expect)/expect, 0., 0.0001, msg);
        res = prism.z1;
        expect = 0.;
        sprintf(msg, "(tess %d) expected z1 %g, got %g", i, expect, res);
        mu_assert_almost_equals(res, 0., 0.000000000000001, msg);
        res = prism.z2;
        expect = zs[i];
        sprintf(msg, "(tess %d) expected z2 %g, got %g", i, expect, res);
        mu_assert_almost_equals((double)(res - expect)/expect, 0., 0.0001, msg);
    }

    return 0;
}


static char * test_tess2prism_flatten()
{
    double expect, res;
    PRISM prism;
    int i;
    TESSEROID tesses[4] = {
        {1,0,1,0,1,6000000,6001000},
        {1,180,190,80,85,6300000,6301000},
        {1,160,200,-90,-70,5500000,6000000},
        {1,-10,5,-7,15,6500000,6505000}};

    for(i = 0; i <  4; i++)
    {
        tess2prism_flatten(tesses[i], &prism);
        res = prism_volume(prism)*prism.density;
        expect = tess_volume(tesses[i])*tesses[i].density;
        sprintf(msg, "(tess %d) expected mass %g, got %g", i, expect, res);
        mu_assert_almost_equals((double)(res - expect)/expect, 0., 0.01, msg);
    }

    return 0;
}


static char * test_tess2sphere()
{
    double expect, res;
    SPHERE sphere;
    int i;
    TESSEROID tesses[4] = {
        {1,0,1,0,1,6000000,6001000},
        {1,180,190,80,85,6300000,6301000},
        {1,160,200,-90,-70,5500000,6000000},
        {1,-10,5,-7,15,6500000,6505000}};

    for(i = 0; i <  4; i++)
    {
        tess2sphere(tesses[i], &sphere);
        res = sphere_volume(sphere);
        expect = tess_volume(tesses[i]);
        sprintf(msg, "(tess %d) expected volume %g, got %g", i, expect, res);
        mu_assert_almost_equals(res/expect, 1., 0.01, msg);
    }

    return 0;
}


static char * test_prism2sphere()
{
    double expect, res;
    SPHERE sphere;
    int i;
    PRISM prisms[4] = {
        {1,0,1000,0,2000,100,2000,0,0,0},
        {1,-500,200,300,500,-1000,4000,0,0,0},
        {1,-10000000,5000000,5000000,8000000,0,3000000,0,0,0},
        {1,-1000000,50000,500000,800000,0,300000,0,0,0}};

    for(i = 0; i <  4; i++)
    {
        prism2sphere(prisms[i], 0., 0., 0., &sphere);
        res = sphere_volume(sphere);
        expect = prism_volume(prisms[i]);
        sprintf(msg, "(prism %d) expected volume %g, got %g", i, expect, res);
        mu_assert_almost_equals(res/expect, 1., 0.001, msg);
    }

    return 0;
}


int geometry_run_all()
{
    int failed = 0;
    failed += mu_run_test(test_prism_volume, "prism_volume return correct results");
    failed += mu_run_test(test_tess_volume, "tess_volume return correct results");
    failed += mu_run_test(test_tess_total_mass, "tess_total_mass returns correct result");
    failed += mu_run_test(test_tess_range_mass, "tess_range_mass returns correct result");
    failed += mu_run_test(test_tess2prism, "tess2prism produces prism with right volume");
    failed += mu_run_test(test_tess2prism_flatten,
                "tess2prism_flatten produces prism with right mass");
    failed += mu_run_test(test_tess2sphere,
                "tess2sphere produces sphere with right volume");
    failed += mu_run_test(test_prism2sphere,
                "prism2sphere produces sphere with right volume");
    failed += mu_run_test(test_split_tess, "split_tess returns correct results for 2, 2, 2 split");
    failed += mu_run_test(test_split_uneven_tess, "split_tess returns correct results for 2, 2, 1 split");
    return failed;
}
