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

#include "minc2.h"

#define TESTRPT(msg, val) (error_cnt++, fprintf(stderr, \
                                  "Error reported on line #%d, %s: %d\n", \
                                  __LINE__, msg, val))
static int error_cnt = 0;

#define NDIMS 3

#define CX 40
#define CY 40
#define CZ 40

/* "Close enough for government work" */
#define EPSILON 1e-13
#define NEARLY_EQUAL(x, y) (fabs(x - y) < EPSILON)


#define VALID_MAX 100
#define VALID_MIN 0
#define REAL_MIN -2.0
#define REAL_MAX 2.0

int
main(void)
{
    int result;
    double voxel[NDIMS];
    double world[NDIMS];
    double new_voxel[NDIMS];
    misize_t coords[NDIMS];
    misize_t count[NDIMS] = {1,1,1};
    midimhandle_t hdims[NDIMS];
    int i, j, k;
    int n;
    mihandle_t hvol;
    double v1, v2;
    double r1, r2, r3;
    double cosines[3];

    result = micreate_dimension("xspace", MI_DIMCLASS_SPATIAL, 
                                MI_DIMATTR_REGULARLY_SAMPLED, CX, &hdims[0]);

    result = micreate_dimension("yspace", MI_DIMCLASS_SPATIAL, 
                                MI_DIMATTR_REGULARLY_SAMPLED, CY, &hdims[1]);

    result = micreate_dimension("zspace", MI_DIMCLASS_SPATIAL, 
                                MI_DIMATTR_REGULARLY_SAMPLED, CZ, &hdims[2]);


    /* Set cosines, steps, and starts to reference values.
     */
    cosines[0] = 0.999815712665963;
    cosines[1] = 0.019197414051189;
    cosines[2] = 0;
    miset_dimension_cosines(hdims[0], cosines);
    miset_dimension_start(hdims[0], -75.4957607345912);
    miset_dimension_separation(hdims[0], 1.0);

    cosines[0] = -0.019197414051189;
    cosines[1] = 0.999815712665963;
    cosines[2] = 0;
    miset_dimension_cosines(hdims[1], cosines);
    miset_dimension_start(hdims[1], 160.392861750822);
    miset_dimension_separation(hdims[1], -1.0);

    cosines[0] = 0;
    cosines[1] = 0;
    cosines[2] = 1;
    miset_dimension_cosines(hdims[2], cosines);
    miset_dimension_start(hdims[2], 121.91304);
    miset_dimension_separation(hdims[2], -1.0);
    result = micreate_volume("tst-convert.mnc", NDIMS, hdims, MI_TYPE_UINT,
                             MI_CLASS_REAL, NULL, &hvol);
    if (result < 0) {
        TESTRPT("micreate_volume error", result);
    }

    result = micreate_volume_image(hvol);
    if (result < 0) {
        TESTRPT("micreate_volume_image error", result);
    }

    /* Valid values are from 0 to 100 */
    miset_volume_valid_range(hvol, VALID_MAX, VALID_MIN);

    /* "real" values are -2.0 to 2.0 */
    miset_volume_range(hvol, REAL_MAX, REAL_MIN);

    for (i = 0; i < CX; i++) {
        for (j = 0; j < CY; j++) {
            for (k = 0; k < CZ; k++) {
                coords[0] = i;
                coords[1] = j;
                coords[2] = k;

                voxel[0] = coords[0];
                voxel[1] = coords[1];
                voxel[2] = coords[2];

                miconvert_voxel_to_world(hvol, voxel, world);
        
                miconvert_world_to_voxel(hvol, world, new_voxel);

                for (n = 0; n < 3; n++) {
                    if (!NEARLY_EQUAL(voxel[n], new_voxel[n])) {
                        fprintf(stderr, "%d %f %f\n", 
                                n, voxel[n], new_voxel[n]);
                        TESTRPT("conversion error", 0);
                    }
                }

                v1 = (VALID_MAX * (voxel[0] + voxel[1] + voxel[2]) 
                      / ((CX-1)+(CY-1)+(CZ-1)));

                result = miset_voxel_value_hyperslab(hvol, MI_TYPE_DOUBLE,
                                                     coords, count, &v1);

                if (result < 0) {
                    TESTRPT("miset_voxel_value_hyperslab error", result);
                }

                /* Get a voxel value.
                 */
                result = miget_voxel_value_hyperslab(hvol, MI_TYPE_DOUBLE,
                                                     coords, count, &v1);
                if (result < 0) {
                    TESTRPT("miget_voxel_value_hyperslab error", result);
                }

                /* Convert from voxel to real.
                 */
                result = miconvert_voxel_to_real(hvol, coords, 3, v1, &r1);
                if (result < 0) {
                    TESTRPT("miconvert_voxel_to_real error", result);
                }

                /* Double check the conversion.
                 */
                r3 = (v1 - VALID_MIN) / (VALID_MAX - VALID_MIN);
                r3 = (r3 * (REAL_MAX - REAL_MIN)) + REAL_MIN;

                if (!NEARLY_EQUAL(r3, r1)) {
                    TESTRPT("real value mismatch", 0);
                }

                /* Convert it back to voxel.
                 */
                result = miconvert_real_to_voxel(hvol, coords, 3, r1, &v2);
                if (result < 0) {
                    TESTRPT("miconvert_real_to_voxel error", result);
                }

                /* Compare to the value read via the ICV
                 */
                result = miget_real_value(hvol, coords, 3, &r2);
                if (result < 0) {
                    TESTRPT("miget_real_value error", result);
                }

                if (!NEARLY_EQUAL(v1, v2)) {
                    fprintf(stderr, "v1 %f v2 %f\n", v1, v2);
                    TESTRPT("Voxel value mismatch", 0);
                }
                if (!NEARLY_EQUAL(r1, r2)) {
                    fprintf(stderr, "r1 %f r2 %f\n", r1, r2);
                    TESTRPT("Real value mismatch", 0);
                }

                result = miget_voxel_value(hvol, coords, 3, &v1);
                if (result < 0) {
                    TESTRPT("miget_voxel_value error", result);
                }

                if (!NEARLY_EQUAL(v1, v2)) {
                    fprintf(stderr, "v1 %f v2 %f\n", v1, v2);
                    TESTRPT("Voxel value mismatch", 0);
                }
            }
        }
    }

    world[MI2_X] = -80;
    world[MI2_Y] = 150;
    world[MI2_Z] = 120;

    result = miset_world_origin(hvol, world);

    if (result != MI_NOERROR) {
        TESTRPT("miset_world_origin error", result);
    }

    voxel[MI2_X] = voxel[MI2_Y] = voxel[MI2_Z] = 0;

    result = miconvert_voxel_to_world(hvol, voxel, world);
    if (result != MI_NOERROR) {
        TESTRPT("miconvert_voxel_to_world error", result);
    }

    if (!NEARLY_EQUAL(world[MI2_X], -80.0) || 
        !NEARLY_EQUAL(world[MI2_Y], 150.0) || 
        !NEARLY_EQUAL(world[MI2_Z], 120.0)) {
        fprintf(stderr, "%f %f %f\n", world[0], world[1], world[2]);
        TESTRPT("conversion error", 0);
    }

    miclose_volume(hvol);
    if (error_cnt == 0) {
        printf("No errors\n");
    }
    else {
        printf("%d error%s\n", error_cnt, error_cnt == 1 ? "" : "s");
    }
    return (error_cnt);
}
