/**
 * 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.7
 * @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 = true;

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

  // spectrum's width and height
  //var HVSize = [ 180, 101 ]; // normal
  //var HVSize = [ 120, 69 ]; // small
  var HVSize = [ 102, 61 ]; // tiny

  var padding = 3;
  var borderWidth = 0;
  var HVCrossSize = [ 15, 15 ];
  var SSize = 22;
  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+3*padding+'px',
      'height' : HVSize[1]+2*padding+'px',
      'border' : borderWidth+'px solid ThreeDHighlight',
      'borderRightColor' : 'ThreeDShadow', 'borderBottomColor' : 'ThreeDShadow',
      'background' : "url('"+dir+"hv"+HVSize[0]+'x'+HVSize[1]+".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-SArrowSize[0]+'px',
      'fontSize' : '1px',
      'lineHeight' : '1px'
    })
    // saturation gradient's samples
    for(var i=0; i+SSampleSize<=HVSize[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+padding+'px',
      'height' : HVSize[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]>HVSize[1]-1 ? HVSize[1]-1 : p[1]-instance.posS[1]);
      instance.color.setHSV(null, 1-1/(HVSize[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+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)*HVSize[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]{3}([0-9A-F]{3})?)\s*$/i.test(realValue)) {
      e.value = ''
      if(reflectOnBackground) {
        e.style.backgroundColor = e.originalStyle.backgroundColor
        //e.style.border='1px solid #ccc'
        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'
                                e.style.border='1px solid #999'
      }
    }
  }

  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]{3}([0-9A-F]{3})?)\s*$/i)
      if(m) {
        if(m[1].length==6) { // 6x hex
          this.setRGB(
            parseInt(m[1].substr(0,2),16)/255,
            parseInt(m[1].substr(2,2),16)/255,
            parseInt(m[1].substr(4,2),16)/255
          )
        } else { // 3x hex
          this.setRGB(
            parseInt(m[1].charAt(0)+m[1].charAt(0),16)/255,
            parseInt(m[1].charAt(1)+m[1].charAt(1),16)/255,
            parseInt(m[1].charAt(2)+m[1].charAt(2),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();

}

