/*
 *  Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
 *  Copyright (C) 2011-2012 - DIGITEO - Manuel Juliachs
 *
 *  This file must be used under the terms of the CeCILL.
 *  This source file is licensed as described in the file COPYING, which
 *  you should have received as part of this distribution.  The terms
 *  are also available at
 *  http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt
 *
 */

#include <cstring>

#include "ColorComputer.hxx"
#include "DecompositionUtils.hxx"
#include "Plot3DDecomposer.hxx"

extern "C"
{
#include "getGraphicObjectProperty.h"
#include "graphicObjectProperties.h"
}

Plot3DDecomposer* Plot3DDecomposer::decomposer = NULL;

void Plot3DDecomposer::fillVertices(char* id, float* buffer, int bufferLength, int elementsSize, int coordinateMask, double* scale, double* translation, int logMask)
{
    double* x = NULL;
    double* y = NULL;
    double* z = NULL;

    int numX = 0;
    int* piNumX = &numX;
    int numY = 0;
    int* piNumY = &numY;

    Plot3DDecomposer* decomposer = get();

    getGraphicObjectProperty(id, __GO_DATA_MODEL_X__, jni_double_vector, (void**) &x);
    getGraphicObjectProperty(id, __GO_DATA_MODEL_Y__, jni_double_vector, (void**) &y);
    getGraphicObjectProperty(id, __GO_DATA_MODEL_Z__, jni_double_vector, (void**) &z);

    getGraphicObjectProperty(id, __GO_DATA_MODEL_NUM_X__, jni_int, (void**) &piNumX);
    getGraphicObjectProperty(id, __GO_DATA_MODEL_NUM_Y__, jni_int, (void**) &piNumY);

    decomposer->fillGridVertices(buffer, bufferLength, elementsSize, coordinateMask, scale, translation, logMask, x, y, z, numX, numY);
}

double Plot3DDecomposer::getZCoordinate(double* z, int numX, int numY, int i, int j)
{
    double zij = 0.;

    zij = z[numX*j+i];

    return zij;
}

double Plot3DDecomposer::getZCoordinate(double* z, int numX, int numY, int i, int j, int logUsed)
{
    double zij = 0.;

    zij = z[numX*j+i];

    if (logUsed)
    {
        zij = DecompositionUtils::getLog10Value(zij);
    }

    return zij;
}

void Plot3DDecomposer::getFacetTriangles(double* x, double* y, double* z, int numX, int numY, int i, int j,
                                         int* facetVertexIndices, int* triangleVertexIndices)
{
    double vertices[4][3];

    /* Gets the facet's vertices: (i,j), (i+1,j), (i+1,j+1), (i,j+1) */
    getFacetCoordinates(x, y, z, numX, numY, i, j, vertices);

    /* Decomposes the facet into two triangles and outputs their indices */
    DecompositionUtils::getDecomposedQuadTriangleIndices(vertices, facetVertexIndices, triangleVertexIndices);
}

/*
 * To do: compute and return color indices instead of directly looking
 * up colors from the colormap.
 */
void Plot3DDecomposer::fillColors(char* id, float* buffer, int bufferLength, int elementsSize)
{
    char* parentFigure = NULL;
    char* parent = NULL;

    double* z = NULL;
    double* colormap = NULL;


    int numX = 0;
    int* piNumX = &numX;
    int numY = 0;
    int* piNumY = &numY;
    int colormapSize = 0;
    int* piColormapSize = &colormapSize;

    Plot3DDecomposer* decomposer = get();

    getGraphicObjectProperty(id, __GO_DATA_MODEL_NUM_X__, jni_int, (void**) &piNumX);
    getGraphicObjectProperty(id, __GO_DATA_MODEL_NUM_Y__, jni_int, (void**) &piNumY);

    getGraphicObjectProperty(id, __GO_DATA_MODEL_Z__, jni_double_vector, (void**) &z);

    getGraphicObjectProperty(id, __GO_PARENT__, jni_string, (void**) &parent);

    /* Temporary: to avoid getting a null parent_figure property when the object is built */
    if (strcmp(parent, "") == 0)
    {
        return;
    }

    getGraphicObjectProperty(id, __GO_PARENT_FIGURE__, jni_string, (void**) &parentFigure);

    if (parentFigure == NULL)
    {
        return;
    }

    getGraphicObjectProperty(parentFigure, __GO_COLORMAP__, jni_double_vector, (void**) &colormap);
    getGraphicObjectProperty(parentFigure, __GO_COLORMAP_SIZE__, jni_int, (void**) &piColormapSize);

    decomposer->fillNormalizedZGridColors(buffer, bufferLength, elementsSize, colormap, colormapSize, z, numX, numY);

    releaseGraphicObjectProperty(__GO_COLORMAP__, colormap, jni_double_vector, colormapSize);
}

int Plot3DDecomposer::fillIndices(char* id, int* buffer, int bufferLength, int logMask)
{
    double* x = NULL;
    double* y = NULL;
    double* z = NULL;

    int numX = 0;
    int* piNumX = &numX;
    int numY = 0;
    int* piNumY = &numY;

    int numberIndices = 0;

    Plot3DDecomposer* decomposer = get();

    getGraphicObjectProperty(id, __GO_DATA_MODEL_NUM_X__, jni_int, (void**) &piNumX);
    getGraphicObjectProperty(id, __GO_DATA_MODEL_NUM_Y__, jni_int, (void**) &piNumY);

    /* 0 indices if 0 points */
    if (numX == 0 || numY == 0)
    {
        return 0;
    }

    getGraphicObjectProperty(id, __GO_DATA_MODEL_X__, jni_double_vector, (void**) &x);
    getGraphicObjectProperty(id, __GO_DATA_MODEL_Y__, jni_double_vector, (void**) &y);
    getGraphicObjectProperty(id, __GO_DATA_MODEL_Z__, jni_double_vector, (void**) &z);

    /* The per-node value flag is set to 1, as grid values amount to z coordinates and are not relevant anyway. */
    numberIndices = decomposer->fillTriangleIndices(buffer, bufferLength, logMask, x, y, z, z, 1, numX, numY);

    return numberIndices;
}

int Plot3DDecomposer::getWireIndicesSize(char* id)
{
    int numX = 0;
    int* piNumX = &numX;
    int numY = 0;
    int* piNumY = &numY;

    getGraphicObjectProperty(id, __GO_DATA_MODEL_NUM_X__, jni_int, (void**) &piNumX);
    getGraphicObjectProperty(id, __GO_DATA_MODEL_NUM_Y__, jni_int, (void**) &piNumY);

    if (numX == 0 || numY == 0)
    {
        return 0;
    }

    return 2*2*(numX-1)*(numY-1) + 2*(numX-1) + 2*(numY-1);
}

/*
 * To be optimized:
 * -a lot of work performed redundantly with NgonGridDataDecomposer::fillIndices, ought to be merged
 *  with it.
 */
int Plot3DDecomposer::fillWireIndices(char* id, int* buffer, int bufferLength, int logMask)
{
    double* x = NULL;
    double* y = NULL;
    double* z = NULL;

    int numX = 0;
    int* piNumX = &numX;
    int numY = 0;
    int* piNumY = &numY;

    int previousRowValid = 0;
    int currentRowValid = 0;
    int nextRowValid = 0;

    int previousColumnValid = 0;
    int currentColumnValid = 0;
    int nextColumnValid = 0;

    int lowerLeftZValid = 0;
    int lowerRightZValid = 0;
    int upperLeftZValid = 0;
    int upperRightZValid = 0;

    int jm1HorizontalEdgeZValid = 0;
    int im1VerticalEdgeZValid = 0;
    int jHorizontalEdgeZValid = 0;
    int iVerticalEdgeZValid = 0;
    int jp1HorizontalEdgeZValid = 0;
    int ip1VerticalEdgeZValid = 0;

    int ij = 0;
    int ip1j = 0;
    int ijp1 = 0;
    int ip1jp1 = 0;
    int ijm1 = 0;
    int ip1jm1 = 0;

    int firstVertexIndex = 0;

    int bufferOffset = 0;

    getGraphicObjectProperty(id, __GO_DATA_MODEL_NUM_X__, jni_int, (void**) &piNumX);
    getGraphicObjectProperty(id, __GO_DATA_MODEL_NUM_Y__, jni_int, (void**) &piNumY);

    /* 0 indices if less than 2 points along either dimension */
    if (numX < 2 || numY < 2)
    {
        return 0;
    }

    getGraphicObjectProperty(id, __GO_DATA_MODEL_X__, jni_double_vector, (void**) &x);
    getGraphicObjectProperty(id, __GO_DATA_MODEL_Y__, jni_double_vector, (void**) &y);
    getGraphicObjectProperty(id, __GO_DATA_MODEL_Z__, jni_double_vector, (void**) &z);

    /* First row */
    previousRowValid = 0;
    currentRowValid = DecompositionUtils::isValid(y[0]);

    if (logMask & 0x2)
    {
        currentRowValid &= DecompositionUtils::isLogValid(y[0]);
    }

    /* Set to 0 as it is not relevant for the first row iteration */
    jm1HorizontalEdgeZValid = 0;

    /* x-axis and y-axis indices (respectively) */
    for (int j = 0; j < numY-1; j++)
    {
        nextRowValid = DecompositionUtils::isValid(y[j+1]);

        if (logMask & 0x2)
        {
            nextRowValid &= DecompositionUtils::isLogValid(y[j+1]);
        }

        if (!currentRowValid)
        {
            previousRowValid = currentRowValid;
            currentRowValid = nextRowValid;
            continue;
        }

        previousColumnValid = 0;
        currentColumnValid = DecompositionUtils::isValid(x[0]);

        if (logMask & 0x1)
        {
            currentColumnValid &= DecompositionUtils::isLogValid(x[0]);
        }

        ij = getPointIndex(numX, numY, 0, j);
        ijp1 = getPointIndex(numX, numY, 0, j+1);

        lowerLeftZValid = DecompositionUtils::isValid(z[ij]);
        upperLeftZValid = DecompositionUtils::isValid(z[ijp1]);

        if (logMask & 0x4)
        {
            lowerLeftZValid &= DecompositionUtils::isLogValid(z[ij]);
            upperLeftZValid &= DecompositionUtils::isLogValid(z[ijp1]);
        }

        iVerticalEdgeZValid = lowerLeftZValid && upperLeftZValid;

        /* Set to 0 as not relevant for the first column iteration */
        im1VerticalEdgeZValid = 0;

        for (int i = 0; i < numX-1; i++)
        {
#if !PER_VERTEX_VALUES
            firstVertexIndex = getFirstVertexIndex(numX, numY, i, j);
#endif

            ip1j = getPointIndex(numX, numY, i+1, j);
            ip1jp1 = getPointIndex(numX, numY, i+1, j+1);

            nextColumnValid = DecompositionUtils::isValid(x[i+1]);

            if (logMask & 0x1)
            {
                nextColumnValid &= DecompositionUtils::isLogValid(x[i+1]);
            }

            lowerRightZValid = DecompositionUtils::isValid(z[ip1j]);
            upperRightZValid = DecompositionUtils::isValid(z[ip1jp1]);

            if (logMask & 0x4)
            {
                lowerRightZValid &= DecompositionUtils::isLogValid(z[ip1j]);
                upperRightZValid &= DecompositionUtils::isLogValid(z[ip1jp1]);
            }

            if (j > 0)
            {
                ijm1 = getPointIndex(numX, numY, i, j-1);
                ip1jm1 = getPointIndex(numX, numY, i+1, j-1);

                jm1HorizontalEdgeZValid = DecompositionUtils::isValid(z[ijm1]) && DecompositionUtils::isValid(z[ip1jm1]);

                if (logMask & 0x4)
                {
                    jm1HorizontalEdgeZValid &= (DecompositionUtils::isLogValid(z[ijm1]) && DecompositionUtils::isLogValid(z[ip1jm1]));
                }
            }

            jHorizontalEdgeZValid = lowerLeftZValid && lowerRightZValid;

            jp1HorizontalEdgeZValid = upperLeftZValid && upperRightZValid;
            ip1VerticalEdgeZValid = lowerRightZValid && upperRightZValid;

            /*
             * Two segments: between points (i,j) and (i+1,j)
             * and points (i,j) and (i,j+1) .
             */
            if ((currentColumnValid && nextColumnValid) && jHorizontalEdgeZValid && ((previousRowValid && jm1HorizontalEdgeZValid) || (nextRowValid && jp1HorizontalEdgeZValid)))
            {
#if PER_VERTEX_VALUES
                buffer[bufferOffset] = ij;
                buffer[bufferOffset+1] = ip1j;
#else
                buffer[bufferOffset] = firstVertexIndex;
                buffer[bufferOffset+1] = firstVertexIndex +1;
#endif

                bufferOffset += 2;
            }

            if (currentColumnValid && nextRowValid && iVerticalEdgeZValid && ((previousColumnValid && im1VerticalEdgeZValid) || (nextColumnValid && ip1VerticalEdgeZValid)))
            {
#if PER_VERTEX_VALUES
                buffer[bufferOffset] = ij;
                buffer[bufferOffset+1] = ijp1;
#else
                buffer[bufferOffset] = firstVertexIndex;
                buffer[bufferOffset+1] = firstVertexIndex +2;
#endif

                bufferOffset += 2;
            }

            previousColumnValid = currentColumnValid;
            currentColumnValid = nextColumnValid;
            lowerLeftZValid = lowerRightZValid;
            upperLeftZValid = upperRightZValid;

            im1VerticalEdgeZValid = iVerticalEdgeZValid;
            iVerticalEdgeZValid = ip1VerticalEdgeZValid;

            ij = ip1j;
            ijp1 = ip1jp1;
        }

        /* Rightmost vertical line */
        if (currentColumnValid && nextRowValid && iVerticalEdgeZValid && (previousColumnValid && im1VerticalEdgeZValid))
        {
#if PER_VERTEX_VALUES
            buffer[bufferOffset] = ij;
            buffer[bufferOffset+1] = ijp1;
#else
            firstVertexIndex = getFirstVertexIndex(numX, numY, numX-2, j);

            buffer[bufferOffset] = firstVertexIndex +1;
            buffer[bufferOffset+1] = firstVertexIndex +3;
#endif

            bufferOffset += 2;
        }

        previousRowValid = currentRowValid;
        currentRowValid = nextRowValid;
    }

    /* Topmost horizontal lines */
    currentRowValid = DecompositionUtils::isValid(y[numY-1]);

    if (logMask & 0x2)
    {
        currentRowValid &= DecompositionUtils::isLogValid(y[numY-1]);
    }

    currentColumnValid = DecompositionUtils::isValid(x[0]);

    if (logMask & 0x1)
    {
        currentColumnValid = DecompositionUtils::isValid(x[0]);
    }

    ij = getPointIndex(numX, numY, 0, numY-1);

    lowerLeftZValid = DecompositionUtils::isValid(z[ij]);

    if (logMask & 0x4)
    {
        lowerLeftZValid &= DecompositionUtils::isValid(z[ij]);
    }

    ij = getPointIndex(numX, numY, 0, numY-1);

    for (int i = 0; i < numX-1; i++)
    {
        nextColumnValid = DecompositionUtils::isValid(x[i+1]);

        if (logMask & 0x1)
        {
            nextColumnValid &= DecompositionUtils::isLogValid(x[i+1]);
        }

        ip1j = getPointIndex(numX, numY, i+1, numY-1);

        lowerRightZValid = DecompositionUtils::isValid(z[ip1j]);

        if (logMask & 0x4)
        {
            lowerRightZValid &= DecompositionUtils::isLogValid(z[ip1j]);
        }

        ijm1 = getPointIndex(numX, numY, i, numY-2);
        ip1jm1 = getPointIndex(numX, numY, i+1, numY-2);

        jm1HorizontalEdgeZValid = DecompositionUtils::isValid(z[ijm1]) && DecompositionUtils::isValid(z[ip1jm1]);

        if (logMask & 0x4)
        {
            jm1HorizontalEdgeZValid &= (DecompositionUtils::isLogValid(z[ijm1]) && DecompositionUtils::isLogValid(z[ip1jm1]));
        }

        jHorizontalEdgeZValid = lowerLeftZValid && lowerRightZValid;

        if (currentRowValid && (currentColumnValid && nextColumnValid) && jHorizontalEdgeZValid && (previousRowValid && jm1HorizontalEdgeZValid))
        {
#if PER_VERTEX_VALUES
            buffer[bufferOffset] = ij;
            buffer[bufferOffset+1] = ip1j;
#else
            firstVertexIndex = getFirstVertexIndex(numX, numY, i, numY-2);

            buffer[bufferOffset] = firstVertexIndex +2;
            buffer[bufferOffset+1] = firstVertexIndex +3;
#endif

            bufferOffset += 2;
        }

        currentColumnValid = nextColumnValid;
        lowerLeftZValid = lowerRightZValid;

        ij = ip1j;
    }

    return bufferOffset;
}

