/*
 RainbowVis-JS
 Released under MIT License
 */

// https://github.com/anomal/RainbowVis-JS/blob/master/rainbowvis.js

function Rainbow()
{
    var gradients = null;
    var minNum = 0;
    var maxNum = 100;
    var colours = ['ff0000', 'ffff00', '00ff00', '0000ff'];
    setColours(colours);

    function setColours (spectrum)
    {
        if (spectrum.length < 2) {
            throw new Error('Rainbow must have two or more colours.');
        } else {
            var increment = (maxNum - minNum)/(spectrum.length - 1);
            var firstGradient = new ColourGradient();
            firstGradient.setGradient(spectrum[0], spectrum[1]);
            firstGradient.setNumberRange(minNum, minNum + increment);
            gradients = [ firstGradient ];

            for (var i = 1; i < spectrum.length - 1; i++) {
                var colourGradient = new ColourGradient();
                colourGradient.setGradient(spectrum[i], spectrum[i + 1]);
                colourGradient.setNumberRange(minNum + increment * i, minNum + increment * (i + 1));
                gradients[i] = colourGradient;
            }

            colours = spectrum;
            return this;
        }
    }

    this.setColors = this.setColours;

    this.setSpectrum = function ()
    {
        setColours(arguments);
        return this;
    }

    this.setSpectrumByArray = function (array)
    {
        setColours(array);
        return this;
    }

    this.colourAt = function (number)
    {
        if (isNaN(number)) {
            throw new TypeError(number + ' is not a number');
        } else if (gradients.length === 1) {
            return gradients[0].colourAt(number);
        } else {
            var segment = (maxNum - minNum)/(gradients.length);
            var index = Math.min(Math.floor((Math.max(number, minNum) - minNum)/segment), gradients.length - 1);
            return gradients[index].colourAt(number);
        }
    }

    this.colorAt = this.colourAt;

    this.setNumberRange = function (minNumber, maxNumber)
    {
        if (maxNumber > minNumber) {
            minNum = minNumber;
            maxNum = maxNumber;
            setColours(colours);
        } else {
            throw new RangeError('maxNumber (' + maxNumber + ') is not greater than minNumber (' + minNumber + ')');
        }
        return this;
    }
}

function ColourGradient()
{
    var startColour = 'ff0000';
    var endColour = '0000ff';
    var minNum = 0;
    var maxNum = 100;

    this.setGradient = function (colourStart, colourEnd)
    {
        startColour = getHexColour(colourStart);
        endColour = getHexColour(colourEnd);
    }

    this.setNumberRange = function (minNumber, maxNumber)
    {
        if (maxNumber > minNumber) {
            minNum = minNumber;
            maxNum = maxNumber;
        } else {
            throw new RangeError('maxNumber (' + maxNumber + ') is not greater than minNumber (' + minNumber + ')');
        }
    }

    this.colourAt = function (number)
    {
        return calcHex(number, startColour.substring(0,2), endColour.substring(0,2))
            + calcHex(number, startColour.substring(2,4), endColour.substring(2,4))
            + calcHex(number, startColour.substring(4,6), endColour.substring(4,6));
    }

    function calcHex(number, channelStart_Base16, channelEnd_Base16)
    {
        var num = number;
        if (num < minNum) {
            num = minNum;
        }
        if (num > maxNum) {
            num = maxNum;
        }
        var numRange = maxNum - minNum;
        var cStart_Base10 = parseInt(channelStart_Base16, 16);
        var cEnd_Base10 = parseInt(channelEnd_Base16, 16);
        var cPerUnit = (cEnd_Base10 - cStart_Base10)/numRange;
        var c_Base10 = Math.round(cPerUnit * (num - minNum) + cStart_Base10);
        return formatHex(c_Base10.toString(16));
    }

    formatHex = function (hex)
    {
        if (hex.length === 1) {
            return '0' + hex;
        } else {
            return hex;
        }
    }

    function isHexColour(string)
    {
        var regex = /^#?[0-9a-fA-F]{6}$/i;
        return regex.test(string);
    }

    function getHexColour(string)
    {
        if (isHexColour(string)) {
            return string.substring(string.length - 6, string.length);
        } else {
            var colourNames =
                [
                    ['red', 'ff0000'],
                    ['lime', '00ff00'],
                    ['blue', '0000ff'],
                    ['yellow', 'ffff00'],
                    ['orange', 'ff8000'],
                    ['aqua', '00ffff'],
                    ['fuchsia', 'ff00ff'],
                    ['white', 'ffffff'],
                    ['black', '000000'],
                    ['gray', '808080'],
                    ['grey', '808080'],
                    ['silver', 'c0c0c0'],
                    ['maroon', '800000'],
                    ['olive', '808000'],
                    ['green', '008000'],
                    ['teal', '008080'],
                    ['navy', '000080'],
                    ['purple', '800080']
                ];
            for (var i = 0; i < colourNames.length; i++) {
                if (string.toLowerCase() === colourNames[i][0]) {
                    return colourNames[i][1];
                }
            }
            throw new Error(string + ' is not a valid colour.');
        }
    }
}