/*
    Protractor - A Screen Protractor
    Copyright © 2008-2018 Harry Whitfield

    This program is free software; you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by the
    Free Software Foundation; either version 2 of the License, or (at your
    option) any later version.

    This program is distributed in the hope that it will be useful, but
    WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    Protractor - version 1.4.4
    6 December 2018
    Copyright © 2008-2018 Harry Whitfield
    mailto:g6auc@arrl.net
*/

/*jslint bitwise, for */

/*property
    PI, appendChild, arc, atan2, beginPath, bgAlphaPref, bgSizePref,
    centerXPref, centerYPref, clip, color, colorize, compassAlphaPref,
    compassColorPref, compassDeltaPref, compassStylePref, contextMenuItems,
    data, defaultValue, description, event, fill, fillStyle, getFullContext,
    hOffset, hRegistrationPoint, height, isNaN, lineTo, lineWidth,
    markerAnglePref, markerColorPref, markerDirectionPref, markerShowPref,
    modePref, moveTo, name, onMouseDown, onMouseDrag, onMouseEnter, onMouseExit,
    onMouseUp, onMultiClick, opacity, option, platform, removeChild, restore,
    rotate, rotation, rotationPref, round, save, scale, scaleFont, scalePref,
    scaleValuesShowPref, setStyle, show, size, src, stroke, strokeStyle, title,
    translate, type, vOffset, vRegistrationPoint, value, visible, width, x, y,
    zOrder, zeroDegreeShowPref
*/

"use strict";

var compass;
var drag;
var mainFrame;
var mainWindow;
var mark;
var text;
var zero;
var ztxt;
var legend;

var window = mainWindow;
var frame = mainFrame;
var hOffset = 0;
var vOffset = 0;
var size = 590;
var width = size;
var height = size;
var zOrder = 5;

var margins = 78; // was 72                         // allowance for two margins
var scales = 1.0; // maximum radius of scales
var radius = ((width - margins) >> 1) / scales;

var dpi = 288;
var lw0 = (72 / dpi) / radius;
var lw1 = lw0;
var lw2 = 2 * lw0;
var lw3 = 3 * lw0;
var lw5 = 5 * lw0;

var protractor = null; // Canvas
var ctx = null; // Canvas 2d Context

var bgAlpha = 0.1; // Background opacity
var modePref = 0; // Position of zero degree mark

var compassAlpha = 0.1; // Compass opacity
var compassDelta = 0; // Position of north relative to zero degree mark

var downX;
var downY;
var downRotation;
var downAngle;

var markerAngle = 0;

var scalePref = preferences.scalePref.value;
var scale = Number(scalePref) / 100; // 0.6 (0.1) 1.0

var radial = 0.0;
var radial0 = 0.0;

include("Resources/ES5.js");
include("Resources/setStyle.js");
include("Resources/getFullContext.js");

var reSize = function (scale) {
    var halfSize = size >> 1;
    var delta = size >> 4;

    mainWindow.width = size * scale;
    mainWindow.height = size * scale;
    mainFrame.width = size * scale;
    mainFrame.height = size * scale;

    mainFrame.hRegistrationPoint = halfSize * scale;
    mainFrame.vRegistrationPoint = halfSize * scale;
    mainFrame.hOffset = halfSize * scale;
    mainFrame.vOffset = halfSize * scale;

    legend.width = (size + 4) * scale;              // for Macintosh
    legend.height = (size + 6) * scale;
    legend.hRegistrationPoint = halfSize * scale;
    legend.vRegistrationPoint = halfSize * scale;
    legend.hOffset = halfSize * scale;
    legend.vOffset = halfSize * scale;

    zero.width = 16 * scale;
    zero.height = 16 * scale;
    zero.hRegistrationPoint = 16 * scale;
    zero.vRegistrationPoint = 0.925 * halfSize * scale;
    zero.hOffset = halfSize * scale;
    zero.vOffset = halfSize * scale;

    ztxt.size = Math.round(4 + 10 * scale);
    ztxt.width = 36 * scale;
    ztxt.hRegistrationPoint = 36 * scale;
    ztxt.vRegistrationPoint = 0.94576 * halfSize * scale;
    ztxt.hOffset = halfSize * scale;
    ztxt.vOffset = halfSize * scale;

    drag.width = 16 * scale;
    drag.height = 16 * scale;
    drag.hRegistrationPoint = 0;
    drag.vRegistrationPoint = 0.925 * halfSize * scale;
    drag.hOffset = halfSize * scale;
    drag.vOffset = halfSize * scale;

    mark.width = 8 * scale;
    mark.height = 0.8678 * halfSize * scale;
    mark.hRegistrationPoint = 4 * scale;
    mark.vRegistrationPoint = 0.8678 * halfSize * scale;
    mark.hOffset = halfSize * scale;
    mark.vOffset = halfSize * scale;

    text.size = Math.round(4 + 10 * scale);
    text.width = 36 * scale;
    text.hRegistrationPoint = 0;
    text.vRegistrationPoint = 0.94576 * halfSize * scale;
    text.hOffset = halfSize * scale;
    text.vOffset = halfSize * scale;

    compass.width = 1.22 * halfSize * scale;
    compass.height = 1.22 * halfSize * scale;
    compass.hRegistrationPoint = 0.61 * halfSize * scale;
    compass.vRegistrationPoint = 0.61 * halfSize * scale;
    compass.hOffset = halfSize * scale;
    compass.vOffset = halfSize * scale;

    rtxt.size = Math.round(4 + 10 * scale);
    rtxt.width = 300 * scale;
    rtxt.hOffset = halfSize * scale;
    rtxt.vOffset = (halfSize + delta) * scale;

    width = size * scale;
    height = size * scale;

    margins = 78 * scale;
    radius = ((width - margins) >> 1) / scales;

    lw0 = (72 / dpi) / radius;
    lw1 = lw0;
    lw2 = 2 * lw0;
    lw3 = 3 * lw0;
    lw5 = 5 * lw0;
};

//////////////////////////////////// Start of the Canvas functions /////////////////////////////////

function newCanvas(hOffset, vOffset, width, height, zOrder) {
    var o = new Canvas();
    o.hOffset = hOffset;
    o.vOffset = vOffset;
    o.width = width;
    o.height = height;
    o.zOrder = zOrder;
    return o;
}

function drawFilledCircle(radius) {
    bgAlpha = preferences.bgAlphaPref.value / 100;

    ctx.beginPath();
    ctx.arc(0, 0, radius, 0, 2 * Math.PI, false);
    ctx.fillStyle = "rgba( 255, 255, 255, " + bgAlpha + ")"; // was "#FFFFFF";  // white
    ctx.fill();
}

function drawUnitCircle() {
    ctx.beginPath();
    ctx.lineWidth = lw3;
    ctx.arc(0, 0, 1.0, 0, 2 * Math.PI, false);
    ctx.stroke();
}

function drawCenterCircle() {
    ctx.beginPath();
    ctx.lineWidth = lw3;
    ctx.arc(0, 0, 0.015, 0, 2 * Math.PI, false); // centre circle
    ctx.strokeStyle = "#000000"; // black
    ctx.stroke();

    ctx.beginPath();
    ctx.arc(0, 0, 0.0075, 0, 2 * Math.PI, false); // centre point
    ctx.fillStyle = "#000000"; // black
    ctx.fill();
}

function drawRadial(ang, x0) { // draw radial
    ctx.save();
    ctx.rotate(-ang);
    ctx.beginPath();
    ctx.lineWidth = lw3;
    ctx.moveTo(x0, 0);
    ctx.lineTo(1, 0);
    ctx.stroke();
    ctx.restore();
}

function drawGradMark(ang, len) { // draw graduation mark
    ctx.save();
    ctx.rotate(-ang);
    ctx.beginPath();
    ctx.lineWidth = lw3;
    ctx.moveTo(1.0 - len, 0);
    ctx.lineTo(1, 0);
    ctx.stroke();
    ctx.restore();
}

function drawGradMarks() { // draw graduation marks
    var i;
    var len;

    for (i = 0; i < 360; i += 1) {
        if ((i % 10) === 0) {
            len = 0.1;
        } else if ((i % 5) === 0) {
            len = 0.075;
        } else {
            len = 0.05;
        }
        drawGradMark(i * Math.PI / 180, len);
    }
}

function writeScaleValue(d, ang, v) {
    ctx.save();
    ctx.rotate(ang);
    ctx.beginPath();
    ctx.show(0, d, v);
    ctx.restore();
}

function writeScale(alpha) { // write scale values
    var i;
    var j;
    var delta;

    for (i = 0; i < 360; i += 10) {
        if (i >= 100) {
            delta = 3;
        } else if (i >= 10) {
            delta = 2;
        } else {
            delta = 1;
        }
        writeScaleValue(-0.9, (i + alpha - delta) * Math.PI / 180, i);

        j = (360 - i) % 360;
        if (j >= 100) {
            delta = 3;
        } else if (j >= 10) {
            delta = 2;
        } else {
            delta = 1;
        }
        writeScaleValue(-0.8, (i + alpha - delta) * Math.PI / 180, j);
    }
}

function removeChart() {
    if (protractor) {
        frame.removeChild(protractor);
        protractor = null;
    }
}

function openChart() {
    removeChart();
    protractor = newCanvas(hOffset, vOffset, width, height, zOrder);
    frame.appendChild(protractor);

    ctx = protractor.getFullContext("2d");

    ctx.save();
    ctx.translate(width >> 1, height >> 1); // move origin to middle of page
    ctx.scale(radius, radius); // scale unit circle to fit page

    drawFilledCircle(Number(preferences.bgSizePref.value)); // the background

    drawUnitCircle(); // make it the clipping region
    ctx.save();

    ctx.clip();
    drawRadial(0, radial); // E
    drawRadial(0.5 * Math.PI, radial0); // N
    drawRadial(Math.PI, radial); // W
    drawRadial(1.5 * Math.PI, radial); // S

    drawCenterCircle();

    drawGradMarks();
    ctx.restore();
    ctx.setStyle("font-family:'Lucida Grande','Lucida Sans Unicode','Arial'; font-size:" + Math.round(4 + 10 * scale) + "px; font-weight:normal; font-style:normal; opacity:1.0");
    ctx.scaleFont(radius, radius);
    if ((preferences.scaleValuesShowPref.value === "1") && (system.platform === "windows")) {
        writeScale(modePref);
    }
    ctx.scaleFont(1 / radius, 1 / radius);
//ctx.restore();
}

function closeChart() {
    ctx.restore();
}

//////////////////////////////////// End  of  the Canvas functions /////////////////////////////////

//////////////////////////////////// Start of the Interface functions //////////////////////////////

function simpleForm(name, heading, title, type, option, defaultValue, description, confirmButtonLabel) {
    var result = null;
    var formfields;
    var formResults;

    if (confirmButtonLabel === undefined) {
        confirmButtonLabel = "OK";
    }

    formfields = [];

    formfields[0] = new FormField();
    formfields[0].name = name;
    formfields[0].title = title;
    formfields[0].type = type;
    formfields[0].option = option;
    formfields[0].defaultValue = defaultValue;
    formfields[0].description = description;

//form(fieldArray, [dialogTitle], [confirmButtonLabel], [cancelButtonLabel])

    formResults = form(formfields, heading, confirmButtonLabel);

    if (formResults !== null) {
        result = formResults[0];
    }

    delete formfields[0];
    formfields[0] = null;
    return result;
}

function normalize180(angle) {
    angle = angle % 360;
    if (angle < -180) {
        angle += 360;
    } else if (angle >= 180) {
        angle -= 360;
    }
    return angle;
}

function normalize360(angle) {
    angle = angle % 360;
    if (angle < 0) {
        angle += 360;
    }
    return angle;
}

function checkInteger(s) {
    var value = parseInt(s, 10);
    if (Number.isNaN(value)) {
        value = 0;
    }
    return value;
}

function setRelativeRotation() {
    var rotation;
    var result = simpleForm("rAngle", "Set Relative Rotation", "Relative Rotation:", "text", [], String(preferences.rotationPref.value), "Enter the angle of relative clockwise rotation of the protractor (-180..179 degrees).", "Set");

    if (result !== null) {
        rotation = normalize180(checkInteger(result));
        ztxt.data = String(rotation);
        preferences.rotationPref.value = String(rotation);
        frame.rotation = rotation;
    }
}

function showAngleMarker() {
    drag.visible = true;
    mark.visible = true;
    text.visible = true;
    preferences.markerShowPref.value = "1";
    window.contextMenuItems[3].title = "Hide Angle Marker";
}

function hideAngleMarker() {
    drag.visible = false;
    mark.visible = false;
    text.visible = false;
    preferences.markerShowPref.value = "0";
    window.contextMenuItems[3].title = "Show Angle Marker";
}

function toggleAngleMarker() {
    if (drag.visible) {
        hideAngleMarker();
    } else {
        showAngleMarker();
    }
}

function setAngleMarkerDirection() {
    var rotation = preferences.markerAnglePref.value;
    var radians;

    if (preferences.markerDirectionPref.value === "1") {
        preferences.markerDirectionPref.value = "0";
        text.data = String(rotation);
        window.contextMenuItems[2].title = "Increase Angle Marker Anticlockwise";
    } else {
        preferences.markerDirectionPref.value = "1";
        text.data = String((360 - rotation) % 360);
        window.contextMenuItems[2].title = "Increase Angle Marker Clockwise";
    }
    radians = Math.PI * text.data / 180;
   	rtxt.data = text.data + " degrees           " + radians.toFixed(4) + " radians";
}

function setAngleMarker() {
    var rotation;
    var result = simpleForm("mAngle", "Set Marker Angle", "Marker Angle:", "text", [], String(markerAngle), "Enter the angle of the angle marker (0..359 degrees).", "Set");
	var radians;

    if (result !== null) {
        rotation = normalize360(checkInteger(result));
        if (preferences.markerDirectionPref.value === "1") {
            text.data = String((360 - rotation) % 360);
        } else {
            text.data = String(rotation);
        }
        radians = Math.PI * text.data / 180;
   		rtxt.data = text.data + " degrees           " + radians.toFixed(4) + " radians";
        preferences.markerAnglePref.value = String(rotation);
        markerAngle = rotation;
        text.rotation = normalize360(markerAngle + modePref);
        mark.rotation = normalize360(markerAngle + modePref);
        drag.rotation = normalize360(markerAngle + modePref);
        showAngleMarker();
    }
}

function setCompassDeviation() {
    var rotation;
    var result = simpleForm("cAngle", "Set Compass Deviation", "Compass Deviation:", "text", [], String(preferences.compassDeltaPref.value), "Enter the deviation of the compass relative to the zero degree mark of the protractor (-180..179 degrees).", "Set");

    if (result !== null) {
        rotation = normalize180(checkInteger(result));
        compassDelta = rotation;
        compass.rotation = compassDelta;
        preferences.compassDeltaPref.value = String(rotation);
    }
}

function updatePrefs() {
    var rotation;
    var radians;

    switch (preferences.modePref.value) {
    case "At 3 o'clock":
        modePref = 90;
        break;
    case "At 6 o'clock":
        modePref = 180;
        break;
    case "At 9 o'clock":
        modePref = 270;
        break;
    case "At 12 o'clock":
        modePref = 0;
        break;
    }

    rotation = normalize180(checkInteger(preferences.rotationPref.value));
    ztxt.data = String(rotation);
    preferences.rotationPref.value = String(rotation);
    frame.rotation = rotation;
    ztxt.rotation = modePref;
    zero.rotation = modePref;
    zero.visible = (preferences.zeroDegreeShowPref.value === "1");
    ztxt.visible = (preferences.zeroDegreeShowPref.value === "1");

    rotation = normalize360(checkInteger(preferences.markerAnglePref.value));
    if (preferences.markerDirectionPref.value === "1") {
        text.data = String((360 - rotation) % 360);
    } else {
        text.data = String(rotation);
    }
    radians = Math.PI * text.data / 180;
   	rtxt.data = text.data + " degrees           " + radians.toFixed(4) + " radians";
    preferences.markerAnglePref.value = String(rotation);
    markerAngle = rotation;
    text.rotation = normalize360(markerAngle + modePref);
    mark.rotation = normalize360(markerAngle + modePref);
    drag.rotation = normalize360(markerAngle + modePref);
    drag.colorize = preferences.markerColorPref.value;
    mark.colorize = preferences.markerColorPref.value;
    text.color = preferences.markerColorPref.value;

    compass.src = "Resources/" + preferences.compassStylePref.value;
    compassAlpha = Number(preferences.compassAlphaPref.value);
    radial = (
        compassAlpha === 0
        ? 0.0
        : 0.703125
    );
    radial0 = radial;
    compass.opacity = 2.55 * compassAlpha;
    compass.colorize = preferences.compassColorPref.value;
    compassDelta = normalize180(checkInteger(preferences.compassDeltaPref.value));
    compass.rotation = compassDelta;


    if ((preferences.scaleValuesShowPref.value === "1") && (system.platform === "macintosh")) {
        legend.opacity = 255;   // 2.55 * Number(preferences.bgAlphaPref.value);
    } else {
        legend.opacity = 10;
    }

    if (preferences.markerShowPref.value === "0") {
        hideAngleMarker();
    } else {
        showAngleMarker();
        radial0 = 0.0;
    }

    if (preferences.markerDirectionPref.value === "0") {
        window.contextMenuItems[2].title = "Increase Angle Marker Anticlockwise";
    } else {
        window.contextMenuItems[2].title = "Increase Angle Marker Clockwise";
    }
}

var initialize = function () {
    reSize(scale);
    mainWindow.hOffset = Number(preferences.centerXPref.value) - 0.5 * mainWindow.width;
    mainWindow.vOffset = Number(preferences.centerYPref.value) - 0.5 * mainWindow.height;
    updatePrefs();
    openChart();
    zero.visible = (preferences.zeroDegreeShowPref.value === "1");
    ztxt.visible = (preferences.zeroDegreeShowPref.value === "1");
    compass.visible = true;
//closeChart();
//frame.rotation = 0;
//frame.saveImageToFile(system.userDesktopFolder + "/" + widget.name + ".png", "png");
};

zero.onMultiClick = function () {
    setRelativeRotation();
};
zero.onMouseEnter = function () {
    zero.opacity = 255;
};
zero.onMouseExit = function () {
    zero.opacity = 128;
};
zero.onMouseDown = function () {
    downX = system.event.x;
    downY = system.event.y;
    downRotation = frame.rotation;
    downAngle = (Math.atan2((window.height / 2) - system.event.vOffset, (window.width / 2) - system.event.hOffset) / Math.PI) * 180;
};
zero.onMouseDrag = function () {
    zero.opacity = 255;
    var angle = (Math.atan2((window.height / 2) - system.event.vOffset, (window.width / 2) - system.event.hOffset) / Math.PI) * 180;
    frame.rotation = normalize180(Math.round(downRotation + angle - downAngle));
    ztxt.data = String(frame.rotation);
    preferences.rotationPref.value = String(frame.rotation);
};
zero.onMouseUp = function () {
    zero.opacity = 128;
};

drag.onMultiClick = function () {
    setAngleMarker();
};
drag.onMouseEnter = function () {
    drag.opacity = 255;
};
drag.onMouseExit = function () {
    drag.opacity = 128;
};
drag.onMouseDown = function () {
    downX = system.event.x;
    downY = system.event.y;
    downRotation = drag.rotation;
    downAngle = (Math.atan2((window.height / 2) - system.event.vOffset, (window.width / 2) - system.event.hOffset) / Math.PI) * 180;
};
drag.onMouseDrag = function () {
    var rotation;
    var radians;

    drag.opacity = 255;
    var angle = (Math.atan2((window.height / 2) - system.event.vOffset, (window.width / 2) - system.event.hOffset) / Math.PI) * 180;
    markerAngle = normalize360(Math.round(downRotation + angle - downAngle));
    text.rotation = markerAngle;
    mark.rotation = markerAngle;
    drag.rotation = markerAngle;
    preferences.markerAnglePref.value = String(normalize360(markerAngle - modePref));
    rotation = preferences.markerAnglePref.value;
    if (preferences.markerDirectionPref.value === "1") {
        text.data = String((360 - rotation) % 360);
    } else {
        text.data = String(rotation);
    }
    radians = Math.PI * text.data / 180;
   	rtxt.data = text.data + " degrees           " + radians.toFixed(4) + " radians";
};

drag.onMouseUp = function () {
    drag.opacity = 128;
};

compass.onMultiClick = function () {
    setCompassDeviation();
    compass.opacity = 2.55 * compassAlpha;
};
compass.onMouseEnter = function () {
    compass.opacity = (compass.opacity + 85) % 256;
};
compass.onMouseExit = function () {
    compass.opacity = 2.55 * compassAlpha;
};

legend.onMultiClick = function () {
	var n = Number(preferences.compassStylePref.value[7]);

	n = (n + 1) % 3;
	preferences.compassStylePref.value = "compass" + n + ".png"
	compass.src = "Resources/" + preferences.compassStylePref.value;
};

//////////////////////////////////// End/ of the Interface functions ///////////////////////////////
