#include <glib.h>
#include <locale.h>
#ifdef _MSC_VER
#define _USE_MATH_DEFINES
#endif
#include <cmath>
#include "lensfun.h"

typedef struct {
    size_t      img_width, img_height;
    lfLens*     lens;
    lfModifier* mod;
} lfFixture;

// setup a standard lens
void mod_setup(lfFixture *lfFix, gconstpointer data)
{
    lfFix->lens              = new lfLens();
    lfFix->lens->Type        = LF_RECTILINEAR;
    lfFix->lens->CropFactor  = 1.0;
    lfFix->lens->AspectRatio = 1.0;

    lfLensCalibDistortion lensCalibDist = {LF_DIST_MODEL_POLY3, 12.0f, 10.8f, false, {0.1}};
    lfFix->lens->AddCalibDistortion(&lensCalibDist);

    // width and height have to be odd, so we have a non fractional center position
    lfFix->img_height = 301;
    lfFix->img_width  = 301;
}


void mod_teardown(lfFixture *lfFix, gconstpointer data)
{
    delete lfFix->lens;
}

// test to verifiy that projection center is image center
void test_mod_projection_center(lfFixture* lfFix, gconstpointer data)
{
    float in[2]  = {0, 0};
    float res[2] = {0, 0};

    // check mapping of center to center
    lfLensType geom_types [] = {LF_RECTILINEAR, LF_PANORAMIC, LF_EQUIRECTANGULAR , LF_FISHEYE, LF_FISHEYE_EQUISOLID, LF_FISHEYE_ORTHOGRAPHIC, LF_FISHEYE_THOBY, LF_UNKNOWN};
    const char* geom_names [] = {"rectilinear", "panoramic", "equirect", "fisheye", "fisheye-equisolid", "fisheye-orthographic", "fisheye-thoby", NULL};
    int  j  = 0;
    while (geom_types[j]!=LF_UNKNOWN) {

        lfFix->lens->Type = geom_types[j];

        int i = 0;
        while (geom_types[i]!=LF_UNKNOWN) {

            if(g_test_verbose())
                g_print("  ~ Conversion from %s -> %s \n", geom_names[j], geom_names[i]);

            lfFix->mod = new lfModifier (lfFix->lens, 1.0f, lfFix->img_width, lfFix->img_height);
            lfFix->mod->Initialize (
                lfFix->lens, LF_PF_U8, 12.0f,
                6.7f, 2.0f, 1.0f, geom_types[i],
                LF_MODIFY_GEOMETRY, false);

            // check if center is not influenced
            in[0] = (lfFix->img_width-1)/2;
            in[1] = (lfFix->img_height-1)/2;
            if (lfFix->mod->ApplyGeometryDistortion(in[0],in[1],1,1,res)) {
                g_assert_cmpfloat(in[0],==,res[0]);
                g_assert_cmpfloat(in[1],==,res[1]);
            }

            delete lfFix->mod;
            i++;
        }
        j++;
    }
}

// check if output becomes NaN when processing geometry conversion
void test_mod_projection_borders(lfFixture* lfFix, gconstpointer data)
{
    float in[2]  = {(float) lfFix->img_width, (float) lfFix->img_height};
    float in2[2] = {(float) (lfFix->img_width-1)/2, (float) (lfFix->img_height-1)/2};
    float res[2] = {0, 0};

    lfLensType geom_types [] = {LF_RECTILINEAR, LF_PANORAMIC, LF_EQUIRECTANGULAR, LF_FISHEYE_STEREOGRAPHIC, LF_FISHEYE, LF_FISHEYE_EQUISOLID, LF_FISHEYE_ORTHOGRAPHIC, LF_FISHEYE_THOBY, LF_UNKNOWN};
    const char* geom_names [] = {"rectilinear", "panoramic", "equirect", "fisheye-sterographic", "fisheye", "fisheye-equisolid", "fisheye-orthographic", "fisheye-thoby", NULL};
    int  j  = 0;
    while (geom_types[j]!=LF_UNKNOWN) {

        lfFix->lens->Type = geom_types[j];

        int  i  = 0;
        while (geom_types[i]!=LF_UNKNOWN) {

            if(g_test_verbose())
                g_print("  ~ Conversion from %s -> %s \n", geom_names[j], geom_names[i]);

            lfFix->mod = new lfModifier (lfFix->lens, 1.0f, lfFix->img_width, lfFix->img_height);
            lfFix->mod->Initialize (
                lfFix->lens, LF_PF_U8, 12.0f,
                6.7f, 2.0f, 1.0f, geom_types[i],
                LF_MODIFY_GEOMETRY, false);

            if (lfFix->mod->ApplyGeometryDistortion(0,0,1,1,res)) {
                g_assert_false(std::isnan(res[0]));
                g_assert_false(std::isnan(res[1]));
            }

            if (lfFix->mod->ApplyGeometryDistortion(in[0],in[1],1,1,res)) {
                g_assert_false(std::isnan(res[0]));
                g_assert_false(std::isnan(res[1]));
            }

            if (lfFix->mod->ApplyGeometryDistortion(in2[0],in2[1],1,1,res)) {
                g_assert_false(std::isnan(res[0]));
                g_assert_false(std::isnan(res[1]));
            }

            delete lfFix->mod;
            i++;
        }
        j++;
    }
}


int main (int argc, char **argv)
{

    setlocale (LC_ALL, "");

    g_test_init(&argc, &argv, NULL);

    g_test_add("/modifier/projection center", lfFixture, NULL, mod_setup, test_mod_projection_center, mod_teardown);
    g_test_add("/modifier/projection borders", lfFixture, NULL, mod_setup, test_mod_projection_borders, mod_teardown);

    return g_test_run();
}
