'use strict';

/* global variables */
var STRETCH_X = 3;
var STRETCH_Y = 0.5;
var X_MIN = -10;
var X_MAX = +10;
var Y_MIN = -10;
var Y_MAX = +10;
var X_OFFSET = -128;
var Y_OFFSET = 256;
var INITIAL_RADIUS = 20;
var POINT_RADIUS = 5;

/*******************************************************************/
/* Graphics                                                        */
/*******************************************************************/
/** 
 * Calculates coordinates from worldspace to screenspace
 * @param {Number} x the coordinate you want to transform
 * @param {bool} isX true iff x is a x-coordinate, otherwise false
 * @return {Number} transformed coordinate
 */
function c(x, isX) {
    if (isX) {
        return STRETCH_X * (x - X_OFFSET);
    }

    return STRETCH_Y * (-x + Y_OFFSET);
}

/** 
 * Calculates coordinates from screenspace to worldspace
 * @param {Number} x the coordinate you want to transform
 * @param {bool} isX true iff x is a x-coordinate, otherwise false
 * @return {Number} transformed coordinate
 */
function r(x, isX) {
    if (isX) {
        return (x / STRETCH_X) + X_OFFSET;
    }

    return (-x / STRETCH_Y) + Y_OFFSET;
}

function setCursorByID(id,cursorStyle) {
    var elem;
    if (document.getElementById &&
    (elem=document.getElementById(id)) ) {
        if (elem.style) elem.style.cursor=cursorStyle;
    }
}

function drawEllipse(centerX, centerY, width, height) {
    context.beginPath() ;

    var x             = centerX;
    var y             = centerY;
    var rx            = width;
    var ry            = height;
    var rotation      = 0; // The rotation of the ellipse (in radians)
    var start         = 0; // The start angle (in radians)
    var end           = 2 * Math.PI; // The end angle (in radians)
    var anticlockwise = false;

    context.ellipse(x, y, rx, ry, rotation, start, end, anticlockwise);
    context.fillStyle = "rgba(255, 0, 0, 0.5)";
    context.fill();
}

function getColor(i, transparency) {
    //var t = (i+1)*(360/k);
    //var color = 'hsla('+t+', 100%, 50%, '+transparency+')';
    var x = i / 256;
    if (x > 1) {x = 1.0;}
    x = parseInt(x*255);
    var color = 'rgba('+x+','+x+','+x+','+transparency+')';
    return color;
}

function drawFunction(canvas) {
    var add = parseInt(document.getElementById("density").value);

    context.beginPath();
    context.fillStyle = 'red';

    context.moveTo(0, c(getValue(r(0))), false);
    for (var xS=0; xS < canvas.width; xS+=add) {
        var x = r(xS);
        var y = getValue(x);
        context.lineTo(c(x, true), c(y, false));
    }

    context.closePath();
    context.stroke();
}

/*******************************************************************/
/* Math                                                            */
/*******************************************************************/
function euklideanDist(p1, p2) {
    return Math.sqrt(
                  Math.pow(p1["x"]-p2["x"], 2) 
                + Math.pow(p1["y"]-p2["y"], 2));
}

/** 
 * Calculates the value of a cubic function at x
 * @param {Number} x
 * @return {Number} f(x)
 */
function getValue(x) {
    var a = parseFloat(document.getElementById("a").value);
    var b = parseFloat(document.getElementById("b").value);
    var c1 = parseFloat(document.getElementById("c").value);
    return a*x*x+b*x+c1;
}

/** 
 * Calculates the drivate f'(x)
 * @param {Number} x
 * @return {Number} f'(x)
 */
function getDValue(x) {
    var a = parseFloat(document.getElementById("a").value);
    var b = parseFloat(document.getElementById("b").value);
    return 2*a*x+b;
}

/** 
 * Calculates the drivate f''(x)
 * @param {Number} x
 * @return {Number} f''(x)
 */
function getDDValue(x) {
    var a = parseFloat(document.getElementById("a").value);
    return 2*a;
}

/** 
 * Calculates (f(x)^2)' = ((a*x*x+b*x+c)^2)' = 2 (b + 2 a x) (c + x (b + a x))
 * @param {Number} x
 * @return {Number} (f(x)^2)'
 */
function gedSquaredValueD(x) {
    var a = parseFloat(document.getElementById("a").value);
    var b = parseFloat(document.getElementById("b").value);
    var c1 = parseFloat(document.getElementById("c").value);
    return 2*(2*a*x+b)*(x*(a*x+b)+c1);
}

/** 
 * Calculates (f(x)^2)'' = ((a*x*x+b*x+c)^2)'' = 2 (b^2 + 6 a b x + 2 a (c + 3 a x^2))
 * @param {Number} x
 * @return {Number} (f(x)^2)''
 */
function gedSquaredValueDD(x) {
    var a = parseFloat(document.getElementById("a").value);
    var b = parseFloat(document.getElementById("b").value);
    var c1 = parseFloat(document.getElementById("c").value);

    return 2*(b*b+6*a*b*x+2*a*(c1+3*a*x*x));
}

function findMin(p) {
    var a = parseFloat(document.getElementById("a").value);
    var b = parseFloat(document.getElementById("b").value);
    var c1 = parseFloat(document.getElementById("c").value);

    /* old iterative solution that had problems near the center*/
    var lastx = -10000;
    var x = p.x;
    var i = 0;
    while (Math.abs(lastx-x) > 1 && i < 100) {
        // first derivate of the square of the distance function
        var fx = -2.0*p.x+2.0*x-2.0*p.y*getDValue(x) +2*getValue(x)*getDValue(x);
        var fxd =         2.0  -2.0*p.y*getDDValue(x)+2*(getDValue(x)*getDValue(x) + getValue(x)*getDDValue(x));

        if (fxd == 0) {
            console.log("wow!");
            return x;
        }
        // x_{n+1} = x_n - f(x_n)/f'(x_n) wenn x gesucht, sodass f(x) = 0 gilt
        lastx = x;
        x -= fx / fxd;

        i++;
    }

    // New direct solution
    /*var xs = -b / (2*a);
    var w = p.y + b*b/(4*a)-c1;
    var z = p.x + b / (2*a);
    var alpha = (1-2*a*w)/(2*a*a);
    var beta = -z/(2*a*a);
    var t = Math.pow(Math.pow(3*(4*Math.pow(alpha,3) +27* Math.pow(beta,2)),1/2) - 9*beta, 1/3);
    var currentMinX = t / (Math.pow(18, 1/3)) - Math.pow(2/3*alpha, 1/3) / t;
    */
    
    return x;
}

function getDist(p, minX) {
    var minY = getValue(minX);
    return euklideanDist({"x":minX, "y":minY}, {"x":p.x, "y":p.y});
}

/*******************************************************************/
/* Start / Update                                                  */
/*******************************************************************/
function drawBoard(canvas, mouseCoords, radius) {
    var context = canvas.getContext('2d');
    context.canvas.width  = window.innerWidth - 50;
    context.canvas.height = window.innerHeight - 120;
    context.clearRect(0, 0, canvas.width, canvas.height);

    drawFunction(canvas);
    if (document.getElementById("pDistance").checked) {
        var add = parseInt(document.getElementById("density").value)+10;
        for (var x=0; x < canvas.width; x+=add) {
            for (var y=0; y < canvas.height; y+=add) {
                var dist = getDist({"x":r(x,true), "y":r(y,false)}, findMin({"x":r(x,true), "y":r(y,false)}));
                context.fillStyle = getColor(dist,0.5);
                context.fillRect(x, y, add/2, add/2);
            }
        }
    }
}

function updateBoard(){
    var canvas = document.getElementById("myCanvas");
    STRETCH_X = parseFloat(document.getElementById("STRETCH_X").value);
    STRETCH_Y = parseFloat(document.getElementById("STRETCH_Y").value);
    X_OFFSET = parseFloat(document.getElementById("X_OFFSET").value);
    Y_OFFSET = parseFloat(document.getElementById("Y_OFFSET").value);
    drawBoard(canvas, {"x":0,"y":0}, INITIAL_RADIUS);
}

var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
drawBoard(canvas, {"x":0,"y":0}, INITIAL_RADIUS);
setCursorByID("myCanvas", "crosshair");

/** get the current position of the mouse */
function getMouseCoords(canvas, evt) {
    var rect = canvas.getBoundingClientRect();
    return {
        "x": evt.clientX - rect.left,
        "y": evt.clientY - rect.top
    };
}

/** event listeners */
canvas.addEventListener('mousemove',
    function (evt) {
        var mouseCoords = getMouseCoords(canvas, evt);
        drawBoard(canvas, mouseCoords, 10);
        // draw coordinates next to mouse
        context.fillStyle = "blue";
        context.font = "bold 16px Arial";
        var x = r(mouseCoords.x, true).toFixed(3);
        var y = r(mouseCoords.y, false).toFixed(3);
        context.fillText("(" + x + ", " + y + ")", mouseCoords.x + 5, mouseCoords.y - 5);
        var minX = findMin({"x": r(mouseCoords.x,true), "y": r(mouseCoords.y,false)});
        var minY = getValue(minX);
        context.beginPath();
        context.moveTo(c(minX,true), c(minY, false));
        context.lineTo(mouseCoords.x, mouseCoords.y, false);
        context.stroke();
        var minRadius = euklideanDist({"x": r(mouseCoords.x,true), "y": r(mouseCoords.y,false)}, {"x":minX,"y":minY});
        
        /* Draw circle */
        drawEllipse(mouseCoords.x, mouseCoords.y, minRadius*STRETCH_X, minRadius*STRETCH_Y);
    }, false);
