/**
 * JavaScript Color Picker
 *
 * @author    Honza Odvarko, http://odvarko.cz
 * @copyright Honza Odvarko
 * @license   http://www.gnu.org/copyleft/gpl.html  GNU General Public License
 * @version   1.0.5
 * @link      http://jscolor.com
 */


jscolor_register() // register jscolor_init() at page load


function jscolor_register() {
    if(typeof window.onload == 'function') {
        var f = window.onload
        window.onload = function() {
            if(f)/* IE7 */ f()
            jscolor_init()
        }
    } else {
        window.onload = jscolor_init
    }
}


function jscolor_init() {

    // bind <input class="..."> elements
    var bindClass = 'color'

    // set field's background according selected color?
    var reflectOnBackground = true

    // prepend field's color code with #
    var leadingHash = false

    // allow an empty value in the field instead of setting it to #000000
    var allowEmpty = false

    var borderWidth = 1
    var padding = 10
    var HVSize = [ 180, 101 ]
    var HVCrossSize = [ 15, 15 ]
    var SSize = [ 22, 101 ]
    var SArrowSize = [ 7, 11 ]
    var SSampleSize = 4
    var ClientSliderSize = 18

    var instanceId = 0
    var instance
    var elements = {}

    var dir = function() {
        var base = location.href

        var e = document.getElementsByTagName('base')
        for(var i=0; i<e.length; i++) {
            if(e[i].href) base = e[i].href
        }

        var e = document.getElementsByTagName('script')
        for(var i=0; i<e.length; i++) {
            if(e[i].src) {
                var src = new URI(e[i].src)
                if(/\/jscolor\.js$/.test(src.path)) {
                    var srcAbs = src.toAbsolute(base).toString()
                    delete srcAbs.query
                    delete srcAbs.fragment
                    return srcAbs.replace(/[^\/]+$/, '') // remove filename from path
                }
            }
        }
        return false
    }()


    function createDialog() {

        // dialog
        elements.dialog = document.createElement('div')
        setStyle(elements.dialog, {
            'zIndex' : '100',
            'clear' : 'both',
            'position' : 'absolute',
            'width' : HVSize[0]+SSize[0]+3*padding+'px',
            'height' : HVSize[1]+2*padding+'px',
            'border' : borderWidth+'px solid ThreeDHighlight',
            'borderRightColor' : 'ThreeDShadow', 'borderBottomColor' : 'ThreeDShadow',
            'background' : "url('"+dir+"hv.png') "+padding+"px "+padding+"px no-repeat ThreeDFace"
        })
        elements.dialog.onmousedown = function() {
            instance.preserve = true
        }
        elements.dialog.onmousemove = function(e) {
            if(instance.holdHV) setHV(e)
            if(instance.holdS) setS(e)
        }
        elements.dialog.onmouseup = elements.dialog.onmouseout = function() {
            if(instance.holdHV || instance.holdS) {
                instance.holdHV = instance.holdS = false
                if(typeof instance.input.onchange == 'function') instance.input.onchange()
            }
            instance.input.focus()
        }

        // hue/value spectrum
        elements.hv = document.createElement('div')
        setStyle(elements.hv, {
            'position' : 'absolute',
            'left' : '0',
            'top' : '0',
            'width' : HVSize[0]+2*padding+'px',
            'height' : HVSize[1]+2*padding+'px',
            'background' : "url('"+dir+"cross.gif') no-repeat",
            'cursor' : 'crosshair'
        })
        var setHV = function(e) {
            var p = getMousePos(e)
            var relX = p[0]<instance.posHV[0] ? 0 : (p[0]-instance.posHV[0]>HVSize[0]-1 ? HVSize[0]-1 : p[0]-instance.posHV[0])
            var relY = p[1]<instance.posHV[1] ? 0 : (p[1]-instance.posHV[1]>HVSize[1]-1 ? HVSize[1]-1 : p[1]-instance.posHV[1])
            instance.color.setHSV(6/HVSize[0]*relX, null, 1-1/(HVSize[1]-1)*relY)
            updateDialogPointers()
            updateDialogSaturation()
            updateInput(instance.input, instance.color, null)
        }
        elements.hv.onmousedown = function(e) { instance.holdHV = true; setHV(e) }
        elements.dialog.appendChild(elements.hv)

        // saturation gradient
        elements.grad = document.createElement('div')
        setStyle(elements.grad, {
            'position' : 'absolute',
            'left' : HVSize[0]+SArrowSize[0]+2*padding+'px',
            'top' : padding+'px',
            'width' : SSize[0]-SArrowSize[0]+'px',
            'fontSize' : '1px',
            'lineHeight' : '1px'
        })
        // saturation gradient's samples
        for(var i=0; i+SSampleSize<=SSize[1]; i+=SSampleSize) {
            var g = document.createElement('div')
            g.style.height = SSampleSize+'px'
            elements.grad.appendChild(g)
        }
        elements.dialog.appendChild(elements.grad)

        // saturation slider
        elements.s = document.createElement('div')
        setStyle(elements.s, {
            'position' : 'absolute',
            'left' : HVSize[0]+2*padding+'px',
            'top' : '0',
            'width' : SSize[0]+padding+'px',
            'height' : SSize[1]+2*padding+'px',
            'background' : "url('"+dir+"s.gif') no-repeat"
        })
        // IE 5 fix
        try {
            elements.s.style.cursor = 'pointer'
        } catch(eOldIE) {
            elements.s.style.cursor = 'hand'
        }
        var setS = function(e) {
            var p = getMousePos(e)
            var relY = p[1]<instance.posS[1] ? 0 : (p[1]-instance.posS[1]>SSize[1]-1 ? SSize[1]-1 : p[1]-instance.posS[1])
            instance.color.setHSV(null, 1-1/(SSize[1]-1)*relY, null)
            updateDialogPointers()
            updateInput(instance.input, instance.color, null)
        }
        elements.s.onmousedown = function(e) { instance.holdS = true; setS(e) }
        elements.dialog.appendChild(elements.s)
    }


    function showDialog(input) {
        var is = [ input.offsetWidth, input.offsetHeight ]
        var ip = getElementPos(input)
        var sp = getScrollPos()
        var ws = getWindowSize()
        var ds = [
            HVSize[0]+SSize[0]+3*padding+2*borderWidth,
            HVSize[1]+2*padding+2*borderWidth
        ]
        var dp = [
            -sp[0]+ip[0]+ds[0] > ws[0]-ClientSliderSize ? (-sp[0]+ip[0]+is[0]/2 > ws[0]/2 ? ip[0]+is[0]-ds[0] : ip[0]) : ip[0],
            -sp[1]+ip[1]+is[1]+ds[1] > ws[1]-ClientSliderSize ? (-sp[1]+ip[1]+is[1]/2 > ws[1]/2 ? ip[1]-ds[1] : ip[1]+is[1]) : ip[1]+is[1]
        ]

        instanceId++
        instance = {
            input : input,
            color : new color(input.value),
            preserve : false,
            holdHV : false,
            holdS : false,
            posHV : [ dp[0]+borderWidth+padding, dp[1]+borderWidth+padding ],
            posS : [ dp[0]+borderWidth+HVSize[0]+2*padding, dp[1]+borderWidth+padding ]
        }

        updateDialogPointers()
        updateDialogSaturation()

        elements.dialog.style.left = dp[0]+'px'
        elements.dialog.style.top = dp[1]+'px'
        document.getElementsByTagName('body')[0].appendChild(elements.dialog)
    }


    function hideDialog() {
        var b = document.getElementsByTagName('body')[0]
        b.removeChild(elements.dialog)

        instance = null
    }


    function updateDialogPointers() {
        // update hue/value cross
        var x = Math.round(instance.color.hue/6*HVSize[0])
        var y = Math.round((1-instance.color.value)*(HVSize[1]-1))
        elements.hv.style.backgroundPosition =
            (padding-Math.floor(HVCrossSize[0]/2)+x)+'px '+
            (padding-Math.floor(HVCrossSize[1]/2)+y)+'px'

        // update saturation arrow
        var y = Math.round((1-instance.color.saturation)*SSize[1])
        elements.s.style.backgroundPosition = '0 '+(padding-Math.floor(SArrowSize[1]/2)+y)+'px'
    }


    function updateDialogSaturation() {
        // update saturation gradient
        var r, g, b, s, c = [ instance.color.value, 0, 0 ]
        var i = Math.floor(instance.color.hue)
        var f = i%2 ? instance.color.hue-i : 1-(instance.color.hue-i)
        switch(i) {
            case 6:
            case 0: r=0;g=1;b=2; break
            case 1: r=1;g=0;b=2; break
            case 2: r=2;g=0;b=1; break
            case 3: r=2;g=1;b=0; break
            case 4: r=1;g=2;b=0; break
            case 5: r=0;g=2;b=1; break
        }
        var gr = elements.grad.childNodes
        for(var i=0; i<gr.length; i++) {
            s = 1 - 1/(gr.length-1)*i
            c[1] = c[0] * (1 - s*f)
            c[2] = c[0] * (1 - s)
            gr[i].style.backgroundColor = 'rgb('+(c[r]*100)+'%,'+(c[g]*100)+'%,'+(c[b]*100)+'%)'
        }
    }


    function bindInputs() {
        var onfocus = function() {
            if(instance && instance.preserve) {
                instance.preserve = false
            } else {
                showDialog(this)
            }
        }
        var onblur = function() {
            if(instance && instance.preserve) return

            var This = this
            var Id = instanceId
            setTimeout(function() {
                if(instance && instance.preserve) return

                if(instance && instanceId == Id) hideDialog() // if dialog hasn't been already shown by another instance
                updateInput(This, new color(This.value), This.value)
            }, 0)
        }
        var setcolor = function(str) {
            var c = new color(str)
            updateInput(this, c, str)
            if(instance && instance.input == this) {
                instance.color = c
                updateDialogPointers()
                updateDialogSaturation()
            }
        }

        var e = document.getElementsByTagName('input')
        var matchClass = new RegExp('\\s'+bindClass+'\\s')

        for(var i=0; i<e.length; i++) {
            if(e[i].type == 'text' && matchClass.test(' '+e[i].className+' ')) {

                e[i].originalStyle = {
                        'color' : e[i].style.color,
                        'backgroundColor' : e[i].style.backgroundColor
                }
                e[i].setAttribute('autocomplete', 'off')
                e[i].onfocus = onfocus
                e[i].onblur = onblur
                e[i].setcolor = setcolor

                updateInput(e[i], new color(e[i].value), e[i].value)
            }
        }
    }


    function updateInput(e, color, realValue) {
        if(allowEmpty && realValue != null && !/^\s*#?[0-9A-F]{6}\s*$/i.test(realValue)) {
            e.value = ''
            if(reflectOnBackground) {
                e.style.backgroundColor = e.originalStyle.backgroundColor
                e.style.color = e.originalStyle.color
            }
        } else {
            e.value = (leadingHash?'#':'')+color
            if(reflectOnBackground) {
                e.style.backgroundColor = '#'+color
                e.style.color =
                    0.212671 * color.red +
                    0.715160 * color.green +
                    0.072169 * color.blue
                    < 0.5 ? '#FFF' : '#000'
            }
        }
    }


    function setStyle(e, properties) {
        for(var p in properties) eval('e.style.'+p+' = properties[p]')
    }


    function getElementPos(e) {
        var x=0, y=0
        if(e.offsetParent) {
            do {
                x += e.offsetLeft
                y += e.offsetTop
            } while(e = e.offsetParent)
        }
        return [ x, y ]
    }


    function getMousePos(e) {
        if(!e) var e = window.event
        var x=0, y=0
        if(typeof e.pageX == 'number') {
            x = e.pageX
            y = e.pageY
        } else if(typeof e.clientX == 'number') {
            x = e.clientX+document.documentElement.scrollLeft+document.body.scrollLeft
            y = e.clientY+document.documentElement.scrollTop+document.body.scrollTop
        }
        return [ x, y ]
    }


    function getScrollPos() {
        var x=0, y=0
        if(typeof window.pageYOffset == 'number') {
            x = window.pageXOffset
            y = window.pageYOffset
        } else if(document.body && (document.body.scrollLeft || document.body.scrollTop)) {
            x = document.body.scrollLeft
            y = document.body.scrollTop
        } else if(document.documentElement && (document.documentElement.scrollLeft || document.documentElement.scrollTop)) {
            x = document.documentElement.scrollLeft
            y = document.documentElement.scrollTop
        }
        return [ x, y ]
    }


    function getWindowSize() {
        var w=0, h=0
        if(typeof window.innerWidth == 'number') {
            w = window.innerWidth
            h = window.innerHeight
        } else if(document.documentElement && (document.documentElement.clientWidth || document.documentElement.clientHeight)) {
            w = document.documentElement.clientWidth
            h = document.documentElement.clientHeight
        } else if(document.body && (document.body.clientWidth || document.body.clientHeight)) {
            w = document.body.clientWidth
            h = document.body.clientHeight
        }
        return [ w, h ]
    }


    function color(hex) {

        this.hue        = 0 // 0-6
        this.saturation = 0 // 0-1
        this.value      = 0 // 0-1

        this.red   = 0 // 0-1
        this.green = 0 // 0-1
        this.blue  = 0 // 0-1

        this.setRGB = function(r, g, b) { // null = don't change
            var hsv = RGB_HSV(
                r==null ? this.red : (this.red=r),
                g==null ? this.green : (this.green=g),
                b==null ? this.blue : (this.blue=b)
            )
            if(hsv[0] != null) {
                this.hue = hsv[0]
            }
            this.saturation = hsv[1]
            this.value = hsv[2]
        }

        this.setHSV = function(h, s, v) { // null = don't change
            var rgb = HSV_RGB(
                h==null ? this.hue : (this.hue=h),
                s==null ? this.saturation : (this.saturation=s),
                v==null ? this.value : (this.value=v)
            )
            this.red   = rgb[0]
            this.green = rgb[1]
            this.blue  = rgb[2]
        }

        function RGB_HSV(r, g, b) {
            var n = Math.min(Math.min(r,g),b)
            var v = Math.max(Math.max(r,g),b)
            var m = v - n
            if(m == 0) return [ null, 0, v ]
            var h = r==n ? 3+(b-g)/m : (g==n ? 5+(r-b)/m : 1+(g-r)/m)
            return [ h==6?0:h, m/v, v ]
        }

        function HSV_RGB(h, s, v) {
            if(h == null) return [ v, v, v ]
            var i = Math.floor(h)
            var f = i%2 ? h-i : 1-(h-i)
            var m = v * (1 - s)
            var n = v * (1 - s*f)
            switch(i) {
                case 6:
                case 0: return [ v, n, m ]
                case 1: return [ n, v, m ]
                case 2: return [ m, v, n ]
                case 3: return [ m, n, v ]
                case 4: return [ n, m, v ]
                case 5: return [ v, m, n ]
            }
        }

        this.setString = function(hex) {
            var m = hex.match(/^\s*#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})\s*$/i)
            if(m) {
                this.setRGB(
                    parseInt(m[1],16)/255,
                    parseInt(m[2],16)/255,
                    parseInt(m[3],16)/255
                )
            } else {
                this.setRGB(0,0,0)
                return false
            }
        }

        this.toString = function() {
            var r = Math.round(this.red * 255).toString(16)
            var g = Math.round(this.green * 255).toString(16)
            var b = Math.round(this.blue * 255).toString(16)
            return (
                (r.length==1 ? '0'+r : r)+
                (g.length==1 ? '0'+g : g)+
                (b.length==1 ? '0'+b : b)
            ).toUpperCase()
        }

        if(hex) {
            this.setString(hex)
        }

    }


    function URI(uri) { // See RFC3986

        this.scheme    = null
        this.authority = null
        this.path      = ''
        this.query     = null
        this.fragment  = null

        this.parse = function(uri) {
            var m = uri.match(/^(([A-Za-z][0-9A-Za-z+.-]*)(:))?((\/\/)([^\/?#]*))?([^?#]*)((\?)([^#]*))?((#)(.*))?/)
            this.scheme    = m[3] ? m[2] : null
            this.authority = m[5] ? m[6] : null
            this.path      = m[7]
            this.query     = m[9] ? m[10] : null
            this.fragment  = m[12] ? m[13] : null
            return this
        }

        this.toString = function() {
            var result = ''
            if(this.scheme    != null) result = result +      this.scheme + ':'
            if(this.authority != null) result = result +'//'+ this.authority
            if(this.path      != null) result = result +      this.path
            if(this.query     != null) result = result + '?'+ this.query
            if(this.fragment  != null) result = result + '#'+ this.fragment
            return result
        }

        this.toAbsolute = function(base) {
            var base = new URI(base)
            var r = this
            var t = new URI

            if(base.scheme == null) return false

            if(r.scheme != null && r.scheme.toLowerCase() == base.scheme.toLowerCase()) {
                r.scheme = null
            }

            if(r.scheme != null) {
                t.scheme    = r.scheme
                t.authority = r.authority
                t.path      = removeDotSegments(r.path)
                t.query     = r.query
            } else {
                if(r.authority != null) {
                    t.authority = r.authority
                    t.path      = removeDotSegments(r.path)
                    t.query     = r.query
                } else {
                    if(r.path == '') {
                        t.path = base.path
                        if(r.query != null) {
                            t.query = r.query
                        } else {
                            t.query = base.query
                        }
                    } else {
                        if(r.path.substr(0,1) == '/') {
                            t.path = removeDotSegments(r.path)
                        } else {
                            if(base.authority != null && base.path == '') {
                                t.path = '/'+r.path
                            } else {
                                t.path = base.path.replace(/[^\/]+$/,'')+r.path
                            }
                            t.path = removeDotSegments(t.path)
                        }
                        t.query = r.query
                    }
                    t.authority = base.authority
                }
                t.scheme = base.scheme
            }
            t.fragment = r.fragment

            return t
        }

        function removeDotSegments(path) {
            var out = ''
            while(path) {
                if(path.substr(0,3)=='../' || path.substr(0,2)=='./') {
                    path = path.replace(/^\.+/,'').substr(1)
                } else if(path.substr(0,3)=='/./' || path=='/.') {
                    path = '/'+path.substr(3)
                } else if(path.substr(0,4)=='/../' || path=='/..') {
                    path = '/'+path.substr(4)
                    out = out.replace(/\/?[^\/]*$/, '')
                } else if(path=='.' || path=='..') {
                    path = ''
                } else {
                    var rm = path.match(/^\/?[^\/]*/)[0]
                    path = path.substr(rm.length)
                    out = out + rm
                }
            }
            return out
        }

        if(uri) {
            this.parse(uri)
        }

    }

    // init
    createDialog()
    bindInputs()

}


