/**
 * jquery.slider - Slider ui control in jQuery
 * 
 * Written by
 * Egor Khmelev (hmelyoff@gmail.com)
 *
 * Licensed under the MIT (MIT-LICENSE.txt).
 *
 * @author Egor Khmelev
 * @version 1.1.0-RELEASE ($Id$)
 * 
 * Dependencies
 * 
 * jQuery (http://jquery.com)
 * jquery.numberformatter (http://code.google.com/p/jquery-numberformatter/)
 * tmpl (http://ejohn.org/blog/javascript-micro-templating/)
 * jquery.dependClass
 * draggable
 * 
 **/

(function( $ ) {
  
  function isArray( value ){
    if( typeof value == "undefined" ) return false;
    
    if (value instanceof Array || (!(value instanceof Object) &&
         (Object.prototype.toString.call((value)) == '[object Array]') ||
         typeof value.length == 'number' &&
         typeof value.splice != 'undefined' &&
         typeof value.propertyIsEnumerable != 'undefined' &&
         !value.propertyIsEnumerable('splice')
        )) {
      return true;
    }
    
    return false;
  }

	$.slider = function( node, settings ){
	  var jNode = $(node);
	  if( !jNode.data( "jslider" ) )
	    jNode.data( "jslider", new jSlider( node, settings ) );
	  
	  return jNode.data( "jslider" );
	};
	
	$.fn.slider = function( action, opt_value ){
	  var returnValue, args = arguments;
	  
	  function isDef( val ){
	    return val !== undefined;
	  };

	  function isDefAndNotNull( val ){
      return val != null;
	  };
	  
		this.each(function(){
		  var self = $.slider( this, action );
		  
		  // do actions
		  if( typeof action == "string" ){
		    switch( action ){
		      case "value":
		        if( isDef( args[ 1 ] ) && isDef( args[ 2 ] ) ){
		          var pointers = self.getPointers();
		          if( isDefAndNotNull( pointers[0] ) && isDefAndNotNull( args[1] ) ){
		            pointers[0].set( args[ 1 ] );
		            pointers[0].setIndexOver();
		          }
		          
		          if( isDefAndNotNull( pointers[1] ) && isDefAndNotNull( args[2] ) ){
		            pointers[1].set( args[ 2 ] );
		            pointers[1].setIndexOver();
		          }
		        }
		        
		        else if( isDef( args[ 1 ] ) ){
		          var pointers = self.getPointers();
		          if( isDefAndNotNull( pointers[0] ) && isDefAndNotNull( args[1] ) ){
		            pointers[0].set( args[ 1 ] );
		            pointers[0].setIndexOver();
		          }
		        }
		        
		        else
  		        returnValue = self.getValue();

		        break;

		      case "prc":
		        if( isDef( args[ 1 ] ) && isDef( args[ 2 ] ) ){
		          var pointers = self.getPointers();
		          if( isDefAndNotNull( pointers[0] ) && isDefAndNotNull( args[1] ) ){
		            pointers[0]._set( args[ 1 ] );
		            pointers[0].setIndexOver();
		          }

		          if( isDefAndNotNull( pointers[1] ) && isDefAndNotNull( args[2] ) ){
		            pointers[1]._set( args[ 2 ] );
		            pointers[1].setIndexOver();
		          }
		        }

		        else if( isDef( args[ 1 ] ) ){
		          var pointers = self.getPointers();
		          if( isDefAndNotNull( pointers[0] ) && isDefAndNotNull( args[1] ) ){
		            pointers[0]._set( args[ 1 ] );
		            pointers[0].setIndexOver();
		          }
		        }

		        else
  		        returnValue = self.getPrcValue();

		        break;

  		    case "calculatedValue":
  		      var value = self.getValue().split(";");
  		      returnValue = "";
  		      for (var i=0; i < value.length; i++) {
  		        returnValue += (i > 0 ? ";" : "") + self.nice( value[i] );
  		      };
  		      
  		      break;
  		      
  		    case "skin":
		        self.setSkin( args[1] );

  		      break;
		    };
		  
		  }
		  
		  // return actual object
		  else if( !action && !opt_value ){
		    if( !isArray( returnValue ) )
		      returnValue = [];

		    returnValue.push( self );
		  }
		});
		
		// flatten array just with one slider
		if( isArray( returnValue ) && returnValue.length == 1 )
		  returnValue = returnValue[ 0 ];
		
		return returnValue || this;
	};
  
  var OPTIONS = {

    settings: {
      from: 1,
      to: 10,
      step: 1,
      smooth: true,
      limits: true,
      round: 0,
      format: { format: "#,##0.##" },
      value: "5;7",
      dimension: ""
    },
    
    className: "jslider",
    selector: ".jslider-",

    template: tmpl(
      '<span class="<%=className%>">' +
        '<table><tr><td>' +
          '<div class="<%=className%>-bg">' +
            '<i class="l"></i><i class="f"></i><i class="r"></i>' +
            '<i class="v"></i>' +
          '</div>' +

          '<div class="<%=className%>-pointer"></div>' +
          '<div class="<%=className%>-pointer <%=className%>-pointer-to"></div>' +
        
          '<div class="<%=className%>-label"><span><%=settings.from%></span></div>' +
          '<div class="<%=className%>-label <%=className%>-label-to"><span><%=settings.to%></span><%=settings.dimension%></div>' +

          '<div class="<%=className%>-value"><span></span><%=settings.dimension%></div>' +
          '<div class="<%=className%>-value <%=className%>-value-to"><span></span><%=settings.dimension%></div>' +
          
          '<div class="<%=className%>-scale"><%=scale%></div>'+

        '</td></tr></table>' +
      '</span>'
    )
    
  };

  function jSlider(){
  	return this.init.apply( this, arguments );
  };

  jSlider.prototype.init = function( node, settings ){
    this.settings = $.extend(true, {}, OPTIONS.settings, settings ? settings : {});
    
    // obj.sliderHandler = this;
    this.inputNode = $( node ).hide();
    						
		this.settings.interval = this.settings.to-this.settings.from;
		this.settings.value = this.inputNode.attr("value");
		
		if( this.settings.calculate && $.isFunction( this.settings.calculate ) )
		  this.nice = this.settings.calculate;

		if( this.settings.onstatechange && $.isFunction( this.settings.onstatechange ) )
		  this.onstatechange = this.settings.onstatechange;

    this.is = {
      init: false
    };
		this.o = {};

    this.create();
  };
  
  jSlider.prototype.onstatechange = function(){
    
  };
  
  jSlider.prototype.create = function(){
    var $this = this;
    
    this.domNode = $( OPTIONS.template({
      className: OPTIONS.className,
      settings: {
        from: this.nice( this.settings.from ),
        to: this.nice( this.settings.to ),
        dimension: this.settings.dimension
      },
      scale: this.generateScale()
    }) );
    
    this.inputNode.after( this.domNode );
    this.drawScale();
    
    // set skin class
    if( this.settings.skin && this.settings.skin.length > 0 )
      this.setSkin( this.settings.skin );

		this.sizes = {
		  domWidth: this.domNode.width(),
		  domOffset: this.domNode.offset()
		};

    // find some objects
    $.extend(this.o, {
      pointers: {},
      labels: {
        0: {
          o: this.domNode.find(OPTIONS.selector + "value").not(OPTIONS.selector + "value-to")
        },
        1: {
          o: this.domNode.find(OPTIONS.selector + "value").filter(OPTIONS.selector + "value-to")
        }
      },
      limits: {
        0: this.domNode.find(OPTIONS.selector + "label").not(OPTIONS.selector + "label-to"),
        1: this.domNode.find(OPTIONS.selector + "label").filter(OPTIONS.selector + "label-to")
      }
    });

    $.extend(this.o.labels[0], {
      value: this.o.labels[0].o.find("span")
    });

    $.extend(this.o.labels[1], {
      value: this.o.labels[1].o.find("span")
    });

    
    if( !$this.settings.value.split(";")[1] ){
      this.settings.single = true;
      this.domNode.addDependClass("single");
    }

    if( !$this.settings.limits )
      this.domNode.addDependClass("limitless");

    this.domNode.find(OPTIONS.selector + "pointer").each(function( i ){
      var value = $this.settings.value.split(";")[i];
      if( value ){
        $this.o.pointers[i] = new jSliderPointer( this, i, $this );

        var prev = $this.settings.value.split(";")[i-1];
        if( prev && new Number(value) < new Number(prev) ) value = prev;

        value = value < $this.settings.from ? $this.settings.from : value;
        value = value > $this.settings.to ? $this.settings.to : value;
      
        $this.o.pointers[i].set( value, true );
      }
    });
    
    this.o.value = this.domNode.find(".v");
    this.is.init = true;
    
    $.each(this.o.pointers, function(i){
      $this.redraw(this);
    });
    
    (function(self){
      $(window).resize(function(){
        self.onresize();
      });
    })(this);

  };
  
  jSlider.prototype.setSkin = function( skin ){
    if( this.skin_ )
      this.domNode.removeDependClass( this.skin_, "_" );

    this.domNode.addDependClass( this.skin_ = skin, "_" );
  };
  
  jSlider.prototype.setPointersIndex = function( i ){
    $.each(this.getPointers(), function(i){
      this.index( i );
    });
  };
  
  jSlider.prototype.getPointers = function(){
    return this.o.pointers;
  };
  
  jSlider.prototype.generateScale = function(){
    if( this.settings.scale && this.settings.scale.length > 0 ){
      var str = "";
      var s = this.settings.scale;
      var prc = Math.round((100/(s.length-1))*10)/10;
      for( var i=0; i < s.length; i++ ){
        str += '<span style="left: ' + i*prc + '%">' + ( s[i] != '|' ? '<ins>' + s[i] + '</ins>' : '' ) + '</span>';
      };
      return str;
    } else return "";

    return "";
  };
  
  jSlider.prototype.drawScale = function(){
    this.domNode.find(OPTIONS.selector + "scale span ins").each(function(){
      $(this).css({ marginLeft: -$(this).outerWidth()/2 });
    });
  };
  
  jSlider.prototype.onresize = function(){
    var self = this;
		this.sizes = {
		  domWidth: this.domNode.width(),
		  domOffset: this.domNode.offset()
		};

    $.each(this.o.pointers, function(i){
      self.redraw(this);
    });
  };
  
  jSlider.prototype.update = function(){
    this.onresize();
    this.drawScale();
  };
  
  jSlider.prototype.limits = function( x, pointer ){
	  // smooth
	  if( !this.settings.smooth ){
	    var step = this.settings.step*100 / ( this.settings.interval );
	    x = Math.round( x/step ) * step;
	  }
	  
	  var another = this.o.pointers[1-pointer.uid];
	  if( another && pointer.uid && x < another.value.prc ) x = another.value.prc;
	  if( another && !pointer.uid && x > another.value.prc ) x = another.value.prc;

    // base limit
	  if( x < 0 ) x = 0;
	  if( x > 100 ) x = 100;
	  
    return Math.round( x*10 ) / 10;
  };
  
  jSlider.prototype.redraw = function( pointer ){
    if( !this.is.init ) return false;
    
    this.setValue();
    
    // redraw range line
    if( this.o.pointers[0] && this.o.pointers[1] )
      this.o.value.css({ left: this.o.pointers[0].value.prc + "%", width: ( this.o.pointers[1].value.prc - this.o.pointers[0].value.prc ) + "%" });

    this.o.labels[pointer.uid].value.html(
      this.nice(
        pointer.value.origin
      )
    );
    
    // redraw position of labels
    this.redrawLabels( pointer );

  };
  
  jSlider.prototype.redrawLabels = function( pointer ){

    function setPosition( label, sizes, prc ){
  	  sizes.margin = -sizes.label/2;

      // left limit
      label_left = sizes.border + sizes.margin;
      if( label_left < 0 )
        sizes.margin -= label_left;

      // right limit
      if( sizes.border+sizes.label / 2 > self.sizes.domWidth ){
        sizes.margin = 0;
        sizes.right = true;
      } else
        sizes.right = false;
        
      label.o.css({ left: prc + "%", marginLeft: sizes.margin, right: "auto" });
      if( sizes.right ) label.o.css({ left: "auto", right: 0 });
      return sizes;
    }

    var self = this;
	  var label = this.o.labels[pointer.uid];
	  var prc = pointer.value.prc;

	  var sizes = {
	    label: label.o.outerWidth(),
	    right: false,
	    border: ( prc * this.sizes.domWidth ) / 100
	  };

    if( !this.settings.single ){
      // glue if near;
      var another = this.o.pointers[1-pointer.uid];
    	var another_label = this.o.labels[another.uid];

      switch( pointer.uid ){
        case 0:
          if( sizes.border+sizes.label / 2 > another_label.o.offset().left-this.sizes.domOffset.left ){
            another_label.o.css({ visibility: "hidden" });
        	  another_label.value.html( this.nice( another.value.origin ) );

          	label.o.css({ visibility: "visible" });

          	prc = ( another.value.prc - prc ) / 2 + prc;
          	if( another.value.prc != pointer.value.prc ){
          	  label.value.html( this.nice(pointer.value.origin) + "&nbsp;&ndash;&nbsp;" + this.nice(another.value.origin) );
            	sizes.label = label.o.outerWidth();
            	sizes.border = ( prc * this.sizes.domWidth ) / 100;
            }
          } else {
          	another_label.o.css({ visibility: "visible" });
          }
          break;

        case 1:
          if( sizes.border - sizes.label / 2 < another_label.o.offset().left - this.sizes.domOffset.left + another_label.o.outerWidth() ){
            another_label.o.css({ visibility: "hidden" });
        	  another_label.value.html( this.nice(another.value.origin) );

          	label.o.css({ visibility: "visible" });

          	prc = ( prc - another.value.prc ) / 2 + another.value.prc;
          	if( another.value.prc != pointer.value.prc ){
          	  label.value.html( this.nice(another.value.origin) + "&nbsp;&ndash;&nbsp;" + this.nice(pointer.value.origin) );
            	sizes.label = label.o.outerWidth();
            	sizes.border = ( prc * this.sizes.domWidth ) / 100;
            }
          } else {
            another_label.o.css({ visibility: "visible" });
          }
          break;
      }
    }

    sizes = setPosition( label, sizes, prc );
    
    /* draw second label */
    if( another_label ){
      var sizes = {
  	    label: another_label.o.outerWidth(),
  	    right: false,
  	    border: ( another.value.prc * this.sizes.domWidth ) / 100
  	  };
      sizes = setPosition( another_label, sizes, another.value.prc );
    }
	  
    this.redrawLimits();
  };
  
  jSlider.prototype.redrawLimits = function(){
	  if( this.settings.limits ){

      var limits = [ true, true ];

      for( key in this.o.pointers ){

        if( !this.settings.single || key == 0 ){
        
      	  var pointer = this.o.pointers[key];
          var label = this.o.labels[pointer.uid];
          var label_left = label.o.offset().left - this.sizes.domOffset.left;

      	  var limit = this.o.limits[0];
          if( label_left < limit.outerWidth() )
            limits[0] = false;

      	  var limit = this.o.limits[1];
      	  if( label_left + label.o.outerWidth() > this.sizes.domWidth - limit.outerWidth() )
      	    limits[1] = false;
      	}

      };

      for( var i=0; i < limits.length; i++ ){
        if( limits[i] )
          this.o.limits[i].fadeIn("fast");
        else
          this.o.limits[i].fadeOut("fast");
      };

	  }
  };
  
  jSlider.prototype.setValue = function(){
    var value = this.getValue();
    this.inputNode.attr( "value", value );
    this.onstatechange.call( this, value );
  };

  jSlider.prototype.getValue = function(){
    if(!this.is.init) return false;
    var $this = this;
    
    var value = "";
    $.each( this.o.pointers, function(i){
      if( this.value.prc != undefined && !isNaN(this.value.prc) ) value += (i > 0 ? ";" : "") + $this.prcToValue( this.value.prc );
    });
    return value;
  };

  jSlider.prototype.getPrcValue = function(){
    if(!this.is.init) return false;
    var $this = this;
    
    var value = "";
    $.each( this.o.pointers, function(i){
      if( this.value.prc != undefined && !isNaN(this.value.prc) ) value += (i > 0 ? ";" : "") + this.value.prc;
    });
    return value;
  };
  
  jSlider.prototype.prcToValue = function( prc ){

	  if( this.settings.heterogeneity && this.settings.heterogeneity.length > 0 ){
  	  var h = this.settings.heterogeneity;

  	  var _start = 0;
  	  var _from = this.settings.from;

  	  for( var i=0; i <= h.length; i++ ){
  	    if( h[i] ) var v = h[i].split("/");
  	    else       var v = [100, this.settings.to];
  	    
  	    v[0] = new Number(v[0]);
  	    v[1] = new Number(v[1]);
  	      
  	    if( prc >= _start && prc <= v[0] ) {
  	      var value = _from + ( (prc-_start) * (v[1]-_from) ) / (v[0]-_start);
  	    }

  	    _start = v[0];
  	    _from = v[1];
  	  };

	  } else {
      var value = this.settings.from + ( prc * this.settings.interval ) / 100;
	  }

    return this.round( value );
  };
  
	jSlider.prototype.valueToPrc = function( value, pointer ){  	  
	  if( this.settings.heterogeneity && this.settings.heterogeneity.length > 0 ){
  	  var h = this.settings.heterogeneity;

  	  var _start = 0;
  	  var _from = this.settings.from;

  	  for (var i=0; i <= h.length; i++) {
  	    if(h[i]) var v = h[i].split("/");
  	    else     var v = [100, this.settings.to];
  	    v[0] = new Number(v[0]); v[1] = new Number(v[1]);
  	      
  	    if(value >= _from && value <= v[1]){
  	      var prc = pointer.limits(_start + (value-_from)*(v[0]-_start)/(v[1]-_from));
  	    }

  	    _start = v[0]; _from = v[1];
  	  };

	  } else {
  	  var prc = pointer.limits((value-this.settings.from)*100/this.settings.interval);
	  }

	  return prc;
	};
  
	jSlider.prototype.round = function( value ){
    value = Math.round( value / this.settings.step ) * this.settings.step;
		if( this.settings.round ) value = Math.round( value * Math.pow(10, this.settings.round) ) / Math.pow(10, this.settings.round);
		else value = Math.round( value );
		return value;
	};
	
	jSlider.prototype.nice = function( value ){
		value = value.toString().replace(/,/gi, ".").replace(/ /gi, "");;

		if( $.formatNumber ){
		  return $.formatNumber( new Number(value), this.settings.format || {} ).replace( /-/gi, "&minus;" );
		}
		  
		else {
		  return new Number(value);
		}
	};

  
  function jSliderPointer(){
  	Draggable.apply( this, arguments );
  }
  jSliderPointer.prototype = new Draggable();
  
  jSliderPointer.prototype.oninit = function( ptr, id, _constructor ){
    this.uid = id;
    this.parent = _constructor;
    this.value = {};
    this.settings = this.parent.settings;
  };
  
  jSliderPointer.prototype.onmousedown = function(evt){
	  this._parent = {
	    offset: this.parent.domNode.offset(),
	    width: this.parent.domNode.width()
	  };
	  this.ptr.addDependClass("hover");
	  this.setIndexOver();
	};

	jSliderPointer.prototype.onmousemove = function( evt, x ){
	  var coords = this._getPageCoords( evt );
	  this._set( this.calc( coords.x ) );
	};
	
	jSliderPointer.prototype.onmouseup = function( evt ){
	  if( this.parent.settings.callback && $.isFunction(this.parent.settings.callback) )
	    this.parent.settings.callback.call( this.parent, this.parent.getValue() );
	    
	  this.ptr.removeDependClass("hover");
	};
	
	jSliderPointer.prototype.setIndexOver = function(){
	  this.parent.setPointersIndex( 1 );
	  this.index( 2 );
	};
	
	jSliderPointer.prototype.index = function( i ){
	  this.ptr.css({ zIndex: i });
	};
	
	jSliderPointer.prototype.limits = function( x ){
	  return this.parent.limits( x, this );
	};
	
	jSliderPointer.prototype.calc = function(coords){
	  var x = this.limits(((coords-this._parent.offset.left)*100)/this._parent.width);
	  return x;
	};

	jSliderPointer.prototype.set = function( value, opt_origin ){
	  this.value.origin = this.parent.round(value);
	  this._set( this.parent.valueToPrc( value, this ), opt_origin );
	};
	
	jSliderPointer.prototype._set = function( prc, opt_origin ){
	  if( !opt_origin )
	    this.value.origin = this.parent.prcToValue(prc);

	  this.value.prc = prc;
		this.ptr.css({ left: prc + "%" });
	  this.parent.redraw(this);
	};
  
})(jQuery);
