/**
* Copyright 2012-2018, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var constants = require('./constants');
var search = require('../../lib/search').findBin;
var computeControlPoints = require('./compute_control_points');
var createSplineEvaluator = require('./create_spline_evaluator');
var createIDerivativeEvaluator = require('./create_i_derivative_evaluator');
var createJDerivativeEvaluator = require('./create_j_derivative_evaluator');

/*
 * Create conversion functions to go from one basis to another. In particular the letter
 * abbreviations are:
 *
 *   i: i/j coordinates along the grid. Integer values correspond to data points
 *   a: real-valued coordinates along the a/b axes
 *   c: cartesian x-y coordinates
 *   p: screen-space pixel coordinates
 */
module.exports = function setConvert(trace) {
    var a = trace._a;
    var b = trace._b;
    var na = a.length;
    var nb = b.length;
    var aax = trace.aaxis;
    var bax = trace.baxis;

    // Grab the limits once rather than recomputing the bounds for every point
    // independently:
    var amin = a[0];
    var amax = a[na - 1];
    var bmin = b[0];
    var bmax = b[nb - 1];
    var arange = a[a.length - 1] - a[0];
    var brange = b[b.length - 1] - b[0];

    // Compute the tolerance so that points are visible slightly outside the
    // defined carpet axis:
    var atol = arange * constants.RELATIVE_CULL_TOLERANCE;
    var btol = brange * constants.RELATIVE_CULL_TOLERANCE;

    // Expand the limits to include the relative tolerance:
    amin -= atol;
    amax += atol;
    bmin -= btol;
    bmax += btol;

    trace.isVisible = function(a, b) {
        return a > amin && a < amax && b > bmin && b < bmax;
    };

    trace.isOccluded = function(a, b) {
        return a < amin || a > amax || b < bmin || b > bmax;
    };

    trace.setScale = function() {
        var x = trace._x;
        var y = trace._y;

        // This is potentially a very expensive step! It does the bulk of the work of constructing
        // an expanded basis of control points. Note in particular that it overwrites the existing
        // basis without creating a new array since that would potentially thrash the garbage
        // collector.
        var result = computeControlPoints(trace._xctrl, trace._yctrl, x, y, aax.smoothing, bax.smoothing);
        trace._xctrl = result[0];
        trace._yctrl = result[1];

        // This step is the second step in the process, but it's somewhat simpler. It just unrolls
        // some logic since it would be unnecessarily expensive to compute both interpolations
        // nearly identically but separately and to include a bunch of linear vs. bicubic logic in
        // every single call.
        trace.evalxy = createSplineEvaluator([trace._xctrl, trace._yctrl], na, nb, aax.smoothing, bax.smoothing);

        trace.dxydi = createIDerivativeEvaluator([trace._xctrl, trace._yctrl], aax.smoothing, bax.smoothing);
        trace.dxydj = createJDerivativeEvaluator([trace._xctrl, trace._yctrl], aax.smoothing, bax.smoothing);
    };

    /*
     * Convert from i/j data grid coordinates to a/b values. Note in particular that this
     * is *linear* interpolation, even if the data is interpolated bicubically.
     */
    trace.i2a = function(i) {
        var i0 = Math.max(0, Math.floor(i[0]), na - 2);
        var ti = i[0] - i0;
        return (1 - ti) * a[i0] + ti * a[i0 + 1];
    };

    trace.j2b = function(j) {
        var j0 = Math.max(0, Math.floor(j[1]), na - 2);
        var tj = j[1] - j0;
        return (1 - tj) * b[j0] + tj * b[j0 + 1];
    };

    trace.ij2ab = function(ij) {
        return [trace.i2a(ij[0]), trace.j2b(ij[1])];
    };

    /*
     * Convert from a/b coordinates to i/j grid-numbered coordinates. This requires searching
     * through the a/b data arrays and assumes they are monotonic, which is presumed to have
     * been enforced already.
     */
    trace.a2i = function(aval) {
        var i0 = Math.max(0, Math.min(search(aval, a), na - 2));
        var a0 = a[i0];
        var a1 = a[i0 + 1];
        return Math.max(0, Math.min(na - 1, i0 + (aval - a0) / (a1 - a0)));
    };

    trace.b2j = function(bval) {
        var j0 = Math.max(0, Math.min(search(bval, b), nb - 2));
        var b0 = b[j0];
        var b1 = b[j0 + 1];
        return Math.max(0, Math.min(nb - 1, j0 + (bval - b0) / (b1 - b0)));
    };

    trace.ab2ij = function(ab) {
        return [trace.a2i(ab[0]), trace.b2j(ab[1])];
    };

    /*
     * Convert from i/j coordinates to x/y caretesian coordinates. This means either bilinear
     * or bicubic spline evaluation, but the hard part is already done at this point.
     */
    trace.i2c = function(i, j) {
        return trace.evalxy([], i, j);
    };

    trace.ab2xy = function(aval, bval, extrapolate) {
        if(!extrapolate && (aval < a[0] || aval > a[na - 1] | bval < b[0] || bval > b[nb - 1])) {
            return [false, false];
        }
        var i = trace.a2i(aval);
        var j = trace.b2j(bval);

        var pt = trace.evalxy([], i, j);

        if(extrapolate) {
            // This section uses the boundary derivatives to extrapolate linearly outside
            // the defined range. Consider a scatter line with one point inside the carpet
            // axis and one point outside. If we don't extrapolate, we can't draw the line
            // at all.
            var iex = 0;
            var jex = 0;
            var der = [];

            var i0, ti, j0, tj;
            if(aval < a[0]) {
                i0 = 0;
                ti = 0;
                iex = (aval - a[0]) / (a[1] - a[0]);
            } else if(aval > a[na - 1]) {
                i0 = na - 2;
                ti = 1;
                iex = (aval - a[na - 1]) / (a[na - 1] - a[na - 2]);
            } else {
                i0 = Math.max(0, Math.min(na - 2, Math.floor(i)));
                ti = i - i0;
            }

            if(bval < b[0]) {
                j0 = 0;
                tj = 0;
                jex = (bval - b[0]) / (b[1] - b[0]);
            } else if(bval > b[nb - 1]) {
                j0 = nb - 2;
                tj = 1;
                jex = (bval - b[nb - 1]) / (b[nb - 1] - b[nb - 2]);
            } else {
                j0 = Math.max(0, Math.min(nb - 2, Math.floor(j)));
                tj = j - j0;
            }

            if(iex) {
                trace.dxydi(der, i0, j0, ti, tj);
                pt[0] += der[0] * iex;
                pt[1] += der[1] * iex;
            }

            if(jex) {
                trace.dxydj(der, i0, j0, ti, tj);
                pt[0] += der[0] * jex;
                pt[1] += der[1] * jex;
            }
        }

        return pt;
    };


    trace.c2p = function(xy, xa, ya) {
        return [xa.c2p(xy[0]), ya.c2p(xy[1])];
    };

    trace.p2x = function(p, xa, ya) {
        return [xa.p2c(p[0]), ya.p2c(p[1])];
    };

    trace.dadi = function(i /* , u*/) {
        // Right now only a piecewise linear a or b basis is permitted since smoother interpolation
        // would cause monotonicity problems. As a retult, u is entirely disregarded in this
        // computation, though we'll specify it as a parameter for the sake of completeness and
        // future-proofing. It would be possible to use monotonic cubic interpolation, for example.
        //
        // See: https://en.wikipedia.org/wiki/Monotone_cubic_interpolation

        // u = u || 0;

        var i0 = Math.max(0, Math.min(a.length - 2, i));

        // The step (demoninator) is implicitly 1 since that's the grid spacing.
        return a[i0 + 1] - a[i0];
    };

    trace.dbdj = function(j /* , v*/) {
        // See above caveats for dadi which also apply here
        var j0 = Math.max(0, Math.min(b.length - 2, j));

        // The step (demoninator) is implicitly 1 since that's the grid spacing.
        return b[j0 + 1] - b[j0];
    };

    // Takes: grid cell coordinate (i, j) and fractional grid cell coordinates (u, v)
    // Returns: (dx/da, dy/db)
    //
    // NB: separate grid cell + fractional grid cell coordinate format is due to the discontinuous
    // derivative, as described better in create_i_derivative_evaluator.js
    trace.dxyda = function(i0, j0, u, v) {
        var dxydi = trace.dxydi(null, i0, j0, u, v);
        var dadi = trace.dadi(i0, u);

        return [dxydi[0] / dadi, dxydi[1] / dadi];
    };

    trace.dxydb = function(i0, j0, u, v) {
        var dxydj = trace.dxydj(null, i0, j0, u, v);
        var dbdj = trace.dbdj(j0, v);

        return [dxydj[0] / dbdj, dxydj[1] / dbdj];
    };

    // Sometimes we don't care about precision and all we really want is decent rough
    // directions (as is the case with labels). In that case, we can do a very rough finite
    // difference and spare having to worry about precise grid coordinates:
    trace.dxyda_rough = function(a, b, reldiff) {
        var h = arange * (reldiff || 0.1);
        var plus = trace.ab2xy(a + h, b, true);
        var minus = trace.ab2xy(a - h, b, true);

        return [
            (plus[0] - minus[0]) * 0.5 / h,
            (plus[1] - minus[1]) * 0.5 / h
        ];
    };

    trace.dxydb_rough = function(a, b, reldiff) {
        var h = brange * (reldiff || 0.1);
        var plus = trace.ab2xy(a, b + h, true);
        var minus = trace.ab2xy(a, b - h, true);

        return [
            (plus[0] - minus[0]) * 0.5 / h,
            (plus[1] - minus[1]) * 0.5 / h
        ];
    };

    trace.dpdx = function(xa) {
        return xa._m;
    };

    trace.dpdy = function(ya) {
        return ya._m;
    };
};
