/* ----------------------------------------
 * Origin  : GFI INFORMATIQUE 
 * Project : CDPP Propagation Tool 
 * ----------------------------------------
 * History 
 * Version : 01/05/2015 : SCA : V2.1
 * ----------------------------------------
 */
package fr.tlse.irap.pt.client.controller.tools.fitParameter;

import java.util.ArrayList;
import java.util.HashMap;

import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;

/**
 * "ContourPlot" is the most important class. It is a
 * user-interface component which parses the data, draws
 * the contour plot, and returns a string of results.
 * 
 * @author 
 * @version
 */
public class ContourPlot extends Canvas {

    // Below, data members which store the grid steps,
    // the z values, the interpolation flag, the dimensions
    // of the contour plot and the increments in the grid:
    int xSteps, ySteps;
    double z[][];
    boolean logInterpolation = false;
    Dimension d;
    double deltaX, deltaY;

    // Below, data members, most of which are adapted from
    // Fortran variables in Snyder's code:
    int ncv = ContourPlotParams.N_CONTOURS;
    int l1[] = new int[4];
    int l2[] = new int[4];
    int ij[] = new int[2];
    int i1[] = new int[2];
    int i2[] = new int[2];
    int i3[] = new int[6];
    int ibkey, icur, jcur, ii, jj, elle, ix, iedge, iflag, ni, ks;
    int cntrIndex, prevIndex;
    int idir, nxidir, k;
    double z1, z2, cval, zMax, zMin;
    double intersect[] = new double[4];
    double xy[] = new double[2];
    double prevXY[] = new double[2];
    float cv[] = new float[ncv];
    boolean jump;

    // The polygons to fill
    HashMap<Integer, ArrayList<ArrayList<Double>>> polygons = new HashMap<Integer, ArrayList<ArrayList<Double>>>();

    // The errors calculated
    double betaError;
    double speedError;

    // -------------------------------------------------------
    // A constructor method.
    // -------------------------------------------------------
    public ContourPlot(int x, int y) {
        super(x, y);

        d = new Dimension(x, y);
        xSteps = x;
        ySteps = y;
        // setForeground(Color.BLACK);
        // setBackground(Color.WHITE);

        // Init the polygons map
        for (int i = 0; i < ncv; i++) {
            ArrayList<Double> xList = new ArrayList<Double>();
            ArrayList<Double> yList = new ArrayList<Double>();
            ArrayList<ArrayList<Double>> xyList = new ArrayList<ArrayList<Double>>();
            xyList.add(xList);
            xyList.add(yList);
            polygons.put(i, xyList);
        }
    }

    // -------------------------------------------------------
    int sign(int a, int b) {
        a = Math.abs(a);
        if (b < 0) {
            return -a;
        } else {
            return a;
        }
    }

    // -------------------------------------------------------
    // "InvalidData" sets the first two components of the
    // contour value array to equal values, thus preventing
    // subsequent drawing of the contour plot.
    // -------------------------------------------------------
    void invalidData() {
        cv[0] = (float) 0.0;
        cv[1] = (float) 0.0;
    }

    // -------------------------------------------------------
    // "GetExtremes" scans the data in "z" in order
    // to assign values to "zMin" and "zMax".
    // -------------------------------------------------------
    void getExtremes() throws ParseMatrixException {
        int i, j;
        double here;

        zMin = z[0][0];
        zMax = zMin;
        for (i = 0; i < xSteps; i++) {
            for (j = 0; j < ySteps; j++) {
                here = z[i][j];
                if (zMin > here) {
                    zMin = here;
                }
                if (zMax < here) {
                    zMax = here;
                }
            }
        }
        if (zMin == zMax) {
            invalidData();
            throw new ParseMatrixException(ContourPlotParams.errParse + ContourPlotParams.EOL + ContourPlotParams.errEqual);
        }
        return;
    }

    /**
     * "AssignContourValues" interpolates between "zMin" and "zMax", either
     * logarithmically or linearly, in order to assign contour values to the
     * array "cv".
     * 
     * @throws ParseMatrixException
     */
    void assignContourValues() throws ParseMatrixException {
        int i;
        double delta;

        if ((logInterpolation) && (zMin <= 0.0)) {
            invalidData();
            throw new ParseMatrixException(ContourPlotParams.errLog);
        }
        if (logInterpolation) {
            double temp = Math.log(zMin);

            delta = (Math.log(zMax) - temp) / ncv;
            for (i = 0; i < ncv; i++) {
                cv[i] = (float) Math.exp(temp + (i + 1) * delta);
            }
        } else {
            delta = (zMax - zMin) / ncv;
            for (i = 0; i < ncv; i++) {
                cv[i] = (float) (zMin + (i + 1) * delta);
            }
        }
    }

    // -------------------------------------------------------
    // "GetContourValuesString" returns a list of the
    // contour values for display in the results area.
    // -------------------------------------------------------
    public String getContourValuesString() {
        String s = new String();
        int i;

        for (i = 0; i < ncv; i++) {
            s = s + "[" + Integer.toString(i) + "] " + Float.toString(cv[i]) + ContourPlotParams.EOL;
        }
        return s;
    }

    // -------------------------------------------------------
    // "SetMeasurements" determines the dimensions of
    // the contour plot and the increments in the grid.
    // -------------------------------------------------------
    void setMeasurements() {
        d.width = d.width - 2 * ContourPlotParams.PLOT_MARGIN;
        d.height = d.height - 2 * ContourPlotParams.PLOT_MARGIN;
        deltaX = d.width / (xSteps - 1.0);
        deltaY = d.height / (ySteps - 1.0);
    }

    void drawBackground(GraphicsContext g) {
        // Draw the background of the scale
        g.fillPolygon(new double[] { ContourPlotParams.PLOT_MARGIN + d.width + 20, ContourPlotParams.PLOT_MARGIN + d.width + 40, ContourPlotParams.PLOT_MARGIN + d.width + 40,
                ContourPlotParams.PLOT_MARGIN + d.width + 20 }, new double[] { ContourPlotParams.PLOT_MARGIN + 0, ContourPlotParams.PLOT_MARGIN + 0,
                ContourPlotParams.PLOT_MARGIN + d.height, ContourPlotParams.PLOT_MARGIN + d.height }, 4);
        // Draw the scale
        g.setStroke(Color.BLACK);
        // g.setFont(Font.font("System", FontWeight.THIN, 11));
        g.setFont(Font.font("serif", FontWeight.THIN, 11));

        for (int i = 0; i <= 10; i += 1) {
            g.strokeText("" + i, ContourPlotParams.PLOT_MARGIN + d.width + 45, ContourPlotParams.PLOT_MARGIN + d.height - d.height * i / 10 + 5);
        }
        for (int i = 1; i < 252; i += 5) {
            g.setFill(Color.hsb(251 - i, 1.0, 1.0));
            g.fillRect(ContourPlotParams.PLOT_MARGIN + d.width + 20, ContourPlotParams.PLOT_MARGIN + d.height - 5 - (d.height - 5) * i / 251, 20, 5);
        }
    }

    /**
     * "DrawGrid" draws the rectangular grid of gray lines on top of which the
     * contours will later be drawn.
     * 
     * @param g
     */
    void drawGrid(GraphicsContext g) {
        // Write x-axis values
        g.setStroke(Color.BLACK);
        g.setFont(Font.font("System", FontWeight.LIGHT, 11));
        for (int i = 0; i < 7; i++) {
            g.strokeText("" + (i * 100 + 200), ContourPlotParams.PLOT_MARGIN - 10 + (i * d.width) / 6, ContourPlotParams.PLOT_MARGIN + d.height + 15);
        }
        g.strokeText("Speed (km/s)", ContourPlotParams.PLOT_MARGIN / 2 + d.width / 2, ContourPlotParams.PLOT_MARGIN + d.height + 30);
        // Write y-axis values
        g.setStroke(Color.BLACK);
        for (int i = 0; i < 10; i++) {
            g.strokeText("" + (i * 20), ContourPlotParams.PLOT_MARGIN - 22, ContourPlotParams.PLOT_MARGIN + d.height + 3 - (i * d.height) / 9);
        }
        g.strokeText("Beta (°)", ContourPlotParams.PLOT_MARGIN - 55, ContourPlotParams.PLOT_MARGIN + d.height / 2 + 3);
    }

    /**
     * "SetColour" sets the colour of the graphics object, HSB model (hue from 0
     * to 250)
     * 
     * @param g
     */
    void setColour(GraphicsContext g) {
        Color c = Color.hsb(ContourPlotParams.PLOT_HUE - ((ncv - cntrIndex) * ContourPlotParams.PLOT_HUE / ncv), 1.0, 1.0);
        g.setStroke(c);
        g.setFill(c);
    }

    /**
     * Fill the extracted polygon with their level color
     * 
     * @param g
     */
    void fillPolygons(GraphicsContext g) {
        // Draw the background
        g.setFill(Color.hsb(ContourPlotParams.PLOT_HUE, 1.0, 1.0));
        g.fillPolygon(new double[] { ContourPlotParams.PLOT_MARGIN + 0, ContourPlotParams.PLOT_MARGIN + d.width, ContourPlotParams.PLOT_MARGIN + d.width,
                ContourPlotParams.PLOT_MARGIN + 0 }, new double[] { ContourPlotParams.PLOT_MARGIN + 0, ContourPlotParams.PLOT_MARGIN + 0, ContourPlotParams.PLOT_MARGIN + d.height,
                ContourPlotParams.PLOT_MARGIN + d.height }, 4);
        // System.out.println("Indexes: " + polygons.size());
        for (int i = ncv - 1; i >= 0; i--) {
            // System.out.println("" + i + ": " +
            // polygons.get(i).get(0).size());
            double[] xArray = buildDoubleArray(polygons.get(i).get(0));
            double[] yArray = buildDoubleArray(polygons.get(i).get(1));
            cntrIndex = i;
            setColour(g);
            g.fillPolygon(xArray, yArray, polygons.get(i).get(0).size());
        }
    }

    /**
     * Draw the ErrorPlot pixel by pixel according to calculated Z levels
     * 
     * @param g
     */
    void drawRawBitmap(GraphicsContext g) {
        for (int y = 0; y < z.length; y++) {
            for (int x = 0; x < z[y].length; x++) {
                double zValue = z[y][x];
                for (int i = 0; i < ncv; i++) {
                    if (i == 0) {
                        if (zValue >= 0 && zValue < cv[0]) {
                            cntrIndex = 0;
                            setColour(g);
                            g.strokeLine(ContourPlotParams.PLOT_MARGIN + x * d.width / z[y].length, ContourPlotParams.PLOT_MARGIN + y * d.height / z.length,
                                    ContourPlotParams.PLOT_MARGIN + x * d.width / z[y].length, ContourPlotParams.PLOT_MARGIN + (y + 0.6) * d.height / z.length);
                        }
                    } else if (zValue >= cv[i - 1] & zValue < cv[i]) {
                        cntrIndex = i;
                        setColour(g);
                        g.strokeLine(ContourPlotParams.PLOT_MARGIN + x * d.width / z[y].length, ContourPlotParams.PLOT_MARGIN + y * d.height / z.length,
                                ContourPlotParams.PLOT_MARGIN + x * d.width / z[y].length, ContourPlotParams.PLOT_MARGIN + (y + 0.6) * d.height / z.length);
                    }
                }
            }
        }
    }

    /**
     * Return an array of primitive type in stead of associated Object
     * 
     * @param doubles
     * @return
     */
    private double[] buildDoubleArray(ArrayList<Double> doubles) {
        double[] doobles = new double[doubles.size()];
        int i = 0;
        for (Double n : doubles) {
            doobles[i++] = n;
        }
        return doobles;
    }

    // -------------------------------------------------------
    // "DrawKernel" is the guts of drawing and is called
    // directly or indirectly by "ContourPlotKernel" in order
    // to draw a segment of a contour or to set the pen
    // position "prevXY". Its action depends on "iflag":
    //
    // iflag == 1 means Continue a contour
    // iflag == 2 means Start a contour at a boundary
    // iflag == 3 means Start a contour not at a boundary
    // iflag == 4 means Finish contour at a boundary
    // iflag == 5 means Finish closed contour not at boundary
    // iflag == 6 means Set pen position
    //
    // If the constant "SHOW_NUMBERS" is true then when
    // completing a contour ("iflag" == 4 or 5) the contour
    // index is drawn adjacent to where the contour ends.
    // -------------------------------------------------------
    void drawKernel(GraphicsContext g) {
        int prevU, prevV, u, v;

        if ((iflag == 1) || (iflag == 4) || (iflag == 5)) {
            if (cntrIndex != prevIndex) { // Must change colour
                setColour(g);
                prevIndex = cntrIndex;
            }
            prevU = (int) ((prevXY[0] - 1.0) * deltaX);
            prevV = (int) ((prevXY[1] - 1.0) * deltaY);
            u = (int) ((xy[0] - 1.0) * deltaX);
            v = (int) ((xy[1] - 1.0) * deltaY);

            // Interchange horizontal & vertical
            g.strokeLine(ContourPlotParams.PLOT_MARGIN + prevV, ContourPlotParams.PLOT_MARGIN + prevU, ContourPlotParams.PLOT_MARGIN + v, ContourPlotParams.PLOT_MARGIN + u);
            // In the same time, save lines by index for polygon generation
            polygons.get(cntrIndex).get(0).add((double) v);
            polygons.get(cntrIndex).get(1).add((double) u);

            if (ContourPlotParams.SHOW_NUMBERS) {
                if (iflag == 4 || iflag == 5) {
	                if (u == 0) {
	                    u = u - ContourPlotParams.WEE_BIT;
	                } else if (u == d.width) {
	                    u = u + ContourPlotParams.PLOT_MARGIN / 2;
	                } else if (v == 0) {
	                    v = v - ContourPlotParams.PLOT_MARGIN / 2;
	                } else if (v == d.height) {
	                    v = v + ContourPlotParams.WEE_BIT;
	                }
	                g.strokeText(Integer.toString(cntrIndex), ContourPlotParams.PLOT_MARGIN + v, ContourPlotParams.PLOT_MARGIN + u);
                }
            }
        }
        prevXY[0] = xy[0];
        prevXY[1] = xy[1];
    }

    // -------------------------------------------------------
    // "DetectBoundary"
    // -------------------------------------------------------
    void detectBoundary() {
        ix = 1;
        if (ij[1 - elle] != 1) {
            ii = ij[0] - i1[1 - elle];
            jj = ij[1] - i1[elle];
            if (z[ii - 1][jj - 1] <= ContourPlotParams.Z_MAX_MAX) {
                ii = ij[0] + i2[elle];
                jj = ij[1] + i2[1 - elle];
                if (z[ii - 1][jj - 1] < ContourPlotParams.Z_MAX_MAX) {
                    ix = 0;
                }
            }
            if (ij[1 - elle] >= l1[1 - elle]) {
                ix = ix + 2;
                return;
            }
        }
        ii = ij[0] + i1[1 - elle];
        jj = ij[1] + i1[elle];
        if (z[ii - 1][jj - 1] > ContourPlotParams.Z_MAX_MAX) {
            ix = ix + 2;
            return;
        }
        if (z[ij[0]][ij[1]] >= ContourPlotParams.Z_MAX_MAX) {
            ix = ix + 2;
        }
    }

    // -------------------------------------------------------
    // "Routine_label_020" corresponds to a block of code
    // starting at label 20 in Synder's subroutine "GCONTR".
    // -------------------------------------------------------
    boolean Routine_label_020() {
        l2[0] = ij[0];
        l2[1] = ij[1];
        l2[2] = -ij[0];
        l2[3] = -ij[1];
        idir = 0;
        nxidir = 1;
        k = 1;
        ij[0] = Math.abs(ij[0]);
        ij[1] = Math.abs(ij[1]);
        if (z[ij[0] - 1][ij[1] - 1] > ContourPlotParams.Z_MAX_MAX) {
            elle = idir % 2;
            ij[elle] = sign(ij[elle], l1[k - 1]);
            return true;
        }
        elle = 0;
        return false;
    }

    // -------------------------------------------------------
    // "Routine_label_050" corresponds to a block of code
    // starting at label 50 in Synder's subroutine "GCONTR".
    // -------------------------------------------------------
    boolean Routine_label_050() {
        while (true) {
            if (ij[elle] >= l1[elle]) {
                if (++elle <= 1) {
                    continue;
                }
                elle = idir % 2;
                ij[elle] = sign(ij[elle], l1[k - 1]);
                if (Routine_label_150()) {
                    return true;
                }
                continue;
            }
            ii = ij[0] + i1[elle];
            jj = ij[1] + i1[1 - elle];
            if (z[ii - 1][jj - 1] > ContourPlotParams.Z_MAX_MAX) {
                if (++elle <= 1) {
                    continue;
                }
                elle = idir % 2;
                ij[elle] = sign(ij[elle], l1[k - 1]);
                if (Routine_label_150()) {
                    return true;
                }
                continue;
            }
            break;
        }
        jump = false;
        return false;
    }

    // -------------------------------------------------------
    // "Routine_label_150" corresponds to a block of code
    // starting at label 150 in Synder's subroutine "GCONTR".
    // -------------------------------------------------------
    boolean Routine_label_150() {
        while (true) {
            // ------------------------------------------------
            // Lines from z[ij[0]-1][ij[1]-1]
            // to z[ij[0] ][ij[1]-1]
            // and z[ij[0]-1][ij[1]]
            // are not satisfactory. Continue the spiral.
            // ------------------------------------------------
            if (ij[elle] < l1[k - 1]) {
                ij[elle]++;
                if (ij[elle] > l2[k - 1]) {
                    l2[k - 1] = ij[elle];
                    idir = nxidir;
                    nxidir = idir + 1;
                    k = nxidir;
                    if (nxidir > 3) {
                        nxidir = 0;
                    }
                }
                ij[0] = Math.abs(ij[0]);
                ij[1] = Math.abs(ij[1]);
                if (z[ij[0] - 1][ij[1] - 1] > ContourPlotParams.Z_MAX_MAX) {
                    elle = idir % 2;
                    ij[elle] = sign(ij[elle], l1[k - 1]);
                    continue;
                }
                elle = 0;
                return false;
            }
            if (idir != nxidir) {
                nxidir++;
                ij[elle] = l1[k - 1];
                k = nxidir;
                elle = 1 - elle;
                ij[elle] = l2[k - 1];
                if (nxidir > 3) {
                    nxidir = 0;
                }
                continue;
            }

            if (ibkey != 0) {
                return true;
            }
            ibkey = 1;
            ij[0] = icur;
            ij[1] = jcur;
            if (Routine_label_020()) {
                continue;
            }
            return false;
        }
    }

    // -------------------------------------------------------
    // "Routine_label_200" corresponds to a block of code
    // starting at label 200 in Synder's subroutine "GCONTR".
    // It has return values 0, 1 or 2.
    // -------------------------------------------------------
    short Routine_label_200(GraphicsContext g, boolean workSpace[]) {
        while (true) {
            xy[elle] = 1.0 * ij[elle] + intersect[iedge - 1];
            xy[1 - elle] = 1.0 * ij[1 - elle];
            workSpace[2 * (xSteps * (ySteps * cntrIndex + ij[1] - 1) + ij[0] - 1) + elle] = true;
            drawKernel(g);
            if (iflag >= 4) {
                icur = ij[0];
                jcur = ij[1];
                return 1;
            }
            continueContour();
            if (!workSpace[2 * (xSteps * (ySteps * cntrIndex + ij[1] - 1) + ij[0] - 1) + elle]) {
                return 2;
            }
            iflag = 5; // 5. Finish a closed contour
            iedge = ks + 2;
            if (iedge > 4) {
                iedge = iedge - 4;
            }
            intersect[iedge - 1] = intersect[ks - 1];
        }
    }

    // -------------------------------------------------------
    // "CrossedByContour" is true if the current segment in
    // the grid is crossed by one of the contour values and
    // has not already been processed for that value.
    // -------------------------------------------------------
    boolean crossedByContour(boolean workSpace[]) {
        ii = ij[0] + i1[elle];
        jj = ij[1] + i1[1 - elle];
        z1 = z[ij[0] - 1][ij[1] - 1];
        z2 = z[ii - 1][jj - 1];
        for (cntrIndex = 0; cntrIndex < ncv; cntrIndex++) {
            int i = 2 * (xSteps * (ySteps * cntrIndex + ij[1] - 1) + ij[0] - 1) + elle;

            if (!workSpace[i]) {
                float x = cv[cntrIndex];
                if ((x > Math.min(z1, z2)) && (x <= Math.max(z1, z2))) {
                    workSpace[i] = true;
                    return true;
                }
            }
        }
        return false;
    }

    // -------------------------------------------------------
    // "ContinueContour" continues tracing a contour. Edges
    // are numbered clockwise, the bottom edge being # 1.
    // -------------------------------------------------------
    void continueContour() {
        short local_k;

        ni = 1;
        if (iedge >= 3) {
            ij[0] = ij[0] - i3[iedge - 1];
            ij[1] = ij[1] - i3[iedge + 1];
        }
        for (local_k = 1; local_k < 5; local_k++)
            if (local_k != iedge) {
                ii = ij[0] + i3[local_k - 1];
                jj = ij[1] + i3[local_k];
                z1 = z[ii - 1][jj - 1];
                ii = ij[0] + i3[local_k];
                jj = ij[1] + i3[local_k + 1];
                z2 = z[ii - 1][jj - 1];
                if ((cval > Math.min(z1, z2) && (cval <= Math.max(z1, z2)))) {
                    if ((local_k == 1) || (local_k == 4)) {
                        double zz = z2;
                        z2 = z1;
                        z1 = zz;
                    }
                    intersect[local_k - 1] = (cval - z1) / (z2 - z1);
                    ni++;
                    ks = local_k;
                }
            }
        if (ni != 2) {
            // -------------------------------------------------
            // The contour crosses all 4 edges of cell being
            // examined. Choose lines top-to-left & bottom-to-
            // right if interpolation point on top edge is
            // less than interpolation point on bottom edge.
            // Otherwise, choose the other pair. This method
            // produces the same results if axes are reversed.
            // The contour may close at any edge, but must not
            // cross itself inside any cell.
            // -------------------------------------------------
            ks = 5 - iedge;
            if (intersect[2] >= intersect[0]) {
                ks = 3 - iedge;
                if (ks <= 0) {
                    ks = ks + 4;
                }
            }
        }
        // ----------------------------------------------------
        // Determine whether the contour will close or run
        // into a boundary at edge ks of the current cell.
        // ----------------------------------------------------
        elle = ks - 1;
        iflag = 1; // 1. Continue a contour
        jump = true;
        if (ks >= 3) {
            ij[0] = ij[0] + i3[ks - 1];
            ij[1] = ij[1] + i3[ks + 1];
            elle = ks - 3;
        }
    }

    // -------------------------------------------------------
    // "ContourPlotKernel" is the guts of this class and
    // corresponds to Synder's subroutine "GCONTR".
    // -------------------------------------------------------
    void contourPlotKernel(GraphicsContext g, boolean workSpace[]) {
        short val_label_200;

        l1[0] = xSteps;
        l1[1] = ySteps;
        l1[2] = -1;
        l1[3] = -1;
        i1[0] = 1;
        i1[1] = 0;
        i2[0] = 1;
        i2[1] = -1;
        i3[0] = 1;
        i3[1] = 0;
        i3[2] = 0;
        i3[3] = 1;
        i3[4] = 1;
        i3[5] = 0;
        prevXY[0] = 0.0;
        prevXY[1] = 0.0;
        xy[0] = 1.0;
        xy[1] = 1.0;
        cntrIndex = 0;
        prevIndex = -1;
        iflag = 6;
        drawKernel(g);
        icur = Math.max(1, Math.min((int) Math.floor(xy[0]), xSteps));
        jcur = Math.max(1, Math.min((int) Math.floor(xy[1]), ySteps));
        ibkey = 0;
        ij[0] = icur;
        ij[1] = jcur;
        if (Routine_label_020() && Routine_label_150()) {
            return;
        }
        if (Routine_label_050()) {
            return;
        }
        while (true) {
            detectBoundary();
            if (jump) {
                if (ix != 0) {
                    iflag = 4; // Finish contour at boundary
                }
                iedge = ks + 2;
                if (iedge > 4) {
                    iedge = iedge - 4;
                }
                intersect[iedge - 1] = intersect[ks - 1];
                val_label_200 = Routine_label_200(g, workSpace);
                if (val_label_200 == 1) {
                    if (Routine_label_020() && Routine_label_150()) {
                        return;
                    }
                    if (Routine_label_050()) {
                        return;
                    }
                    continue;
                }
                if (val_label_200 == 2) {
                    continue;
                }
                return;
            }
            if ((ix != 3) && (ix + ibkey != 0) && crossedByContour(workSpace)) {
                // An acceptable line segment has been found.
                // Follow contour until it hits a
                // boundary or closes.
                iedge = elle + 1;
                cval = cv[cntrIndex];
                if (ix != 1) {
                    iedge = iedge + 2;
                }
                iflag = 2 + ibkey;
                intersect[iedge - 1] = (cval - z1) / (z2 - z1);
                val_label_200 = Routine_label_200(g, workSpace);
                if (val_label_200 == 1) {
                    if (Routine_label_020() && Routine_label_150()) {
                        return;
                    }
                    if (Routine_label_050()) {
                        return;
                    }
                    continue;
                }
                if (val_label_200 == 2) {
                    continue;
                }
                return;
            }
            if (++elle > 1) {
                elle = idir % 2;
                ij[elle] = sign(ij[elle], l1[k - 1]);
                if (Routine_label_150()) {
                    return;
                }
            }
            if (Routine_label_050()) {
                return;
            }
        }
    }

    /**
     * "paint" overrides the superclass' "paint()" method. This method draws the
     * grid and then the contours, provided that the first two contour values
     * are not equal (which would indicate invalid data). The "workSpace" is
     * used to remember which segments in the grid have been crossed by which
     * contours.
     */
    public void paint() {
        GraphicsContext g = this.getGraphicsContext2D();

        g.clearRect(0, 0, d.height + 2 * ContourPlotParams.PLOT_MARGIN, d.width + 2 * ContourPlotParams.PLOT_MARGIN);

        makeMatrixRectangular();
        try {
            getExtremes();
        } catch (ParseMatrixException e) {
            e.printStackTrace();
        }

        if (zMax > ContourPlotParams.Z_MAX_MAX) {
            zMax = ContourPlotParams.Z_MAX_MAX;
        }
        if (zMin < ContourPlotParams.Z_MIN_MIN) {
            zMin = ContourPlotParams.Z_MIN_MIN;
        }

        try {
            assignContourValues();
        } catch (ParseMatrixException e) {
            e.printStackTrace();
        }

        int workLength = 2 * xSteps * ySteps * ncv;
        boolean workSpace[]; // Allocate below if data valid

        setMeasurements();
        if (cv[0] != cv[1]) { // Valid data
            workSpace = new boolean[workLength];
            contourPlotKernel(g, workSpace);
        }
        drawRawBitmap(g);
        calculateErrors();
    }

    /**
     * "MakeMatrixRectangular" appends zero(s) to the end of any row of "z"
     * which is shorter than the longest row.
     */
    public void makeMatrixRectangular() {
        int i, y, leng;

        xSteps = z.length;
        ySteps = ContourPlotParams.MIN_Y_STEPS;
        for (i = 0; i < xSteps; i++) {
            y = z[i].length;
            if (ySteps < y) {
                ySteps = y;
            }
        }

        for (i = 0; i < xSteps; i++) {
            leng = z[i].length;
            if (leng < ySteps) {
                double temp[] = new double[ySteps];

                System.arraycopy(z[i], 0, temp, 0, leng);
                while (leng < ySteps) {
                    temp[leng++] = 0;
                }
                z[i] = temp;
            }
        }
    }

    /**
     * 
     * @param b
     */
    public void setLogInterpolation(boolean b) {
        this.logInterpolation = b;
    }

    /**
     * 
     * @param z
     */
    public void setZ(double[][] z) {
        this.z = z;
    }

    private void calculateErrors() {
        if (speedError == 0 && betaError == 0) {
            double[] xList = buildDoubleArray(polygons.get(0).get(0));
            double xMin = ContourPlotParams.SPEED_MAX - ContourPlotParams.SPEED_MIN;
            double xMax = ContourPlotParams.SPEED_MIN - ContourPlotParams.SPEED_MIN;
            double[] yList = buildDoubleArray(polygons.get(0).get(1));
            double yMin = ContourPlotParams.ELONGATION_MAX;
            double yMax = ContourPlotParams.ELONGATION_MIN;
            for (int i = 0; i < xList.length; i++) {
                if (xList[i] > xMax) {
                    xMax = xList[i];
                }
                if (xList[i] < xMin) {
                    xMin = xList[i];
                }
                if (yList[i] > yMax) {
                    yMax = yList[i];
                }
                if (yList[i] < yMin) {
                    yMin = yList[i];
                }
            }
            betaError = xMax - xMin;
            speedError = yMax - yMin;
        }
    }

    public double getSpeedError() {
        calculateErrors();
        return speedError;
    }

    public double getBetaError() {
        calculateErrors();
        return betaError;
    }
}