/*
 * IMC 의 장비 가동상태바 를 도시하기 위한 플러그인
 * by MHD
 */
$.widget( "imc.barchart", {
	// default options
	options: {
		margin : 20,
		statusColor: {
			auto : "#219244"
			, idling : "#eb9934"
			, setup : "#595858"
			, change : "#00a0e9"
			, alarm : "#ca1134"
			, off : "#9e9e9f"
			, defaultColor : "#FFFFFF"
		},
		oee : {
			second : "second"
			, startDateTime : "start_date"
			, status : "status"
		},
		tickColor : "#AAAAAA",
		tickWidth : 1,
		dutyTime : "00:00",
		hourTxt : '시간' ,
		minTxt : '분' ,
		toolTipPosition : 'up', // tooltip의 위치 up, down
		isShowTooltip : true ,
		isLimitMoveTooltip : false,
		
		// callbacks
		onChangedDate: null ,  //function(e, addDays){}  param  +1 다음날, -1 전날 날짜 변환 이벤트 발생 function(e, addDays) 
								// 선언되지 않으면 날짜 변경이 되지 않음 .. 즉 당일꺼만 볼 경우... 날짜이동이 없는 경우
		onClickBarChart : null	,//	 functiono(e, timeTxt) 클릭시 콜백 timeTxt 값을 리턴
		onChangedViewType : null ,//function(e, t){}  t = 'day', 'hour', 'min'
		onChangedTooltip : null, // functiono(e, left, timeTxt, isShow) left 위치좌표, top 은 필요없음, isShow 로 마우스 over 상태 확인, txt 로 시간 체크
		onDrawBar : null		// function(e, oeeResult) oeeResult 안에는 각 상태별 시간과 count 값이 json 구조로 저장되어 있음
	},
      
	_oeeType : {
					day:{
						rate:24*60*60, 
						txt: function(){
							this.options.hourTxt;
							return 24 + this.options.hourTxt;
						},								
						getPointTimeTxt:function(lastPoint, width){ // 속도 때문에 width 를 매번 계산하지 않는다 차이가 좀남
							var rate =this._cunrrentType.rate/60/width;
							return this.DateUtil.DateType.Min.execute(this._convertPx(lastPoint*rate + this.oeeTimeTxt[0].data("timepixel")));
						}
					} , 
					hour:{
						rate:60*60, 
						txt: function(){
							this.options.hourTxt;
							return 1 + this.options.hourTxt;
						},	
						getPointTimeTxt:function(lastPoint, width){
							return this._oeeType.day.getPointTimeTxt.call(this, lastPoint, width);
						}
					} , 
					min:{
						rate:12*60, 
						txt: function(){
							this.options.hourTxt;
							return 12 + this.options.minTxt;
						},						
						getPointTimeTxt:function(lastPoint, width){
							var rate =this._cunrrentType.rate/width;
							var x = lastPoint*rate + this.oeeTimeTxt[0].data("timepixel")*60;
							return this.DateUtil.DateType.Second.execute(x >= 60*60*24 ? x%(60*60*24) : x, true);
						}
					} 
				},
	_cunrrentType : null,
	_lastData : [],
	
	// the constructor
	_create: function() {
		this._setCurrentType("day");
		this.oeeBar = $("<div>",{"class":"imc-oee-bar"});
		this.oeeBar.css({height : this.element.height()-30, width:this.element.width()-this.options.margin*2, "marginLeft":this.options.margin});
		this.oeeTimeTick = $('<svg  xmlns="http://www.w3.org/2000/svg" height=50 width='+this.element.width()+'>');
		this.tooltip = $("<div>",{"class":"imc-oee-tooltip " + this.options.toolTipPosition}).append(this._tooltipHtml())
		
		this.oeeTimeTxt = [];
		this.element
		  .addClass( "imc-oee" )
		  .disableSelection().append(this.oeeBar).append(this.oeeTimeTick).append(this.tooltip);
		
		this._drawTimeTick();
		this.tooltipManager.initialize.call(this);
	},
	
	_refresh: function() {
    	this._drawbar(this._lastData, this.oeeTimeTxt[0].data("timepixel"));
		this.tooltip.empty().append(this._tooltipHtml());
		this.tooltipManager.initialize.call(this);
	},
 
	_destroy: function() {
		this.oeeBar.remove();
		this.oeeTimeTick.remove();
		this.tooltip.remove();
		this.element
		  .removeClass( "imc-oee-bar" )
		  .enableSelection()
		  .css( "background-color", "transparent" ).empty();
	},
	
	_setOptions: function() {
		this._superApply( arguments );
		this._refresh();
	},
	
	_setOption: function( key, value ) {
		if("dutyTime" == key && !/[0-9]{2}.[0-9]{2}/.test(value)){
			return;
		} 
		
		if(/oee|statusColor/.test(key)){
			value = $.extend({}, this.options[key], value);           
		}
			
		this._super( key, value );
	},
	
	_drawTimeTick : function(){
		var w = this.element.width();
		var h = this.element.height();
		var style = "stroke:"+this.options.tickColor+";stroke-width:"+this.options.tickWidth
		this.oeeTimeTick.append(this._makeSVG('line', {
			x1 : this.options.margin ,
			x2 : w-this.options.margin,
			y1 : 0 ,
			y2 : 0 ,
			style : style
		}));
		for(var i = 0 ; i <= 24 ; i++){
			var x = this.options.margin + (w-this.options.margin*2)/24*i;
			this.oeeTimeTick.append(this._makeSVG('line', {
				x1 : x ,
				x2 : x ,
				y1 : 0 ,
				y2 : 10 ,
				style : style
			}));
			if( i % 6 == 0 ) {
				this._drawTimeTxt(x, i / 6, this._createTime(x));
			}
		}
		this._updateTimeTxt(0, this.oeeTimeTxt[0].data("orgpixel"));
	},
	
	_drawTimeTxt : function(x, index, $time){
		var times = this.options.dutyTime.split(":");
		var hour = parseInt(times[0]) + index * 6;
		hour = hour > 24 ? hour % 24 : hour;
		$time.data("orgpixel", parseInt(times[0])* 60 + parseInt(times[1]));
		$time.data("timepixel", parseInt(times[0])* 60 + parseInt(times[1]));
		this.element.append($time);
	},
	
	_createTime : function(x){
		this.oeeTimeTxt[this.oeeTimeTxt.length] = $("<time>",{"class":"imc-oee-timetxt",text:"00:00",
			style :["left:", (x-18), "px;top:", ( this.element.height()-10), "px;"].join("")});
		return this.oeeTimeTxt[this.oeeTimeTxt.length-1];
	},
	
	_updateTimeTxt : function(x, startTime){
		var rate = this._cunrrentType.rate/60, DateUtil = this.DateUtil, _convertPx = this._convertPx;
		startTime += startTime < this._getSecond(this.options.dutyTime)/60 ? 1440 : 0;
		x = parseInt(x/rate) * rate + (startTime ? startTime : 0), rate /= 4;
		$.each(this.oeeTimeTxt, function( index, $this ) {
			var px = x + index * rate;
			$this.text(DateUtil.getString(DateUtil.DateType.Min, _convertPx(px), false));
			$this.data("timepixel", px)
		});
	},
	
	_findStartPoint : function(html, data, startMin, w, status, oeeResult){
		var startSec = startMin*60, seconds = 0, dutySec = this._getSecond(this.options.dutyTime), pixel = 0;
		startSec += (startSec < dutySec ?  60*60*24 : 0)
		for (var i = 0, len = data.length; i < len; i++) {
			seconds = this._getSecond(data[i][this.options.oee.startDateTime])
			seconds = startSec > seconds && dutySec > seconds ? (seconds += 60*60*24) : seconds;
			if(seconds > startSec){
				pixel = (seconds- startSec)/this._cunrrentType.rate * w;
				if(i > 0){
					status = data[i-1][status];
					this._addOeeResult(oeeResult[status], seconds- startSec);
				}else{
					status = "defaultColor";
				}
				return this._checkOverFirstPixel(html, status, pixel, w, i)
			}else if(i == len -1 && seconds < startSec && startSec < seconds + parseInt(data[i][this.options.oee.second])){
				pixel = (seconds + parseInt(data[i][this.options.oee.second])-startSec)/this._cunrrentType.rate * w;
				return this._checkOverFirstPixel(html, data[i][status], pixel, w, -1)
			}
		}
		return {index : -1,	pixel : pixel};
	},
	
	_checkOverFirstPixel : function(html, status, pixel, w, i){
		if(pixel > w){ // pixel 이 넓이 보다 큰 경우
			i = -1, pixel = w;
		}
		this._addbaritem(html, status, pixel, w);
		return {index : i,	pixel : pixel}
	},
	
	_drawbar : function(data, startMin) {
		var html = [], w = this.oeeBar.width(), oeeResult = this._getOeeTemplete(), totalPixel = 0, pixel, leakPixel=0,
		status = this.options.oee.status, second = this.options.oee.second, startDateTime = this.options.oee.startDateTime;
		for (var i = 0, len = data.length; i < len; i++) {
			if(i == 0 && (this.oeeTimeTxt[0].data("orgpixel") != startMin || this.oeeTimeTxt[0].data("orgpixel") != this._getSecond(data[i][startDateTime])/60)){
				var first = this._findStartPoint(html, data, startMin, w, status, oeeResult);
				totalPixel =+ first.pixel;
				i = first.index;
				if(first.index < 0) break;
			}
			pixel = (data[i][second]/this._cunrrentType.rate) * w;
			
			if(totalPixel + pixel > w){
				pixel = w - totalPixel;
				this._addOeeResult(oeeResult[data[i][status]], parseInt(pixel*this._cunrrentType.rate/w));
				this._addbaritem(html, data[i][status], pixel, w); 
				break;
			}
			leakPixel += pixel;
			pixel = leakPixel - leakPixel%1;
			leakPixel = leakPixel - pixel;
			totalPixel += pixel;
			if(len - 1 == i && leakPixel > 0){ // 잃어버린 1px 을 찾아서
				pixel += 1;
			}
			this._addOeeResult(oeeResult[data[i][status]], data[i][second]);
			this._addbaritem(html, data[i][status], pixel, w); 
		}
		this._bindEvent(this.oeeBar.empty().append(html.join('')).children(":not(.defaultColor)"));
		if(this._cunrrentType == this._oeeType.day){ // day 는 굳이 다시 그릴필요 없음
			this._trigger("onDrawBar", null , [oeeResult]);
		}
	},
	
	_bindEvent: function(bar){
		bar.on({mousemove: $.proxy(this.tooltipManager.calculatePoint, this), 
			mouseleave: $.proxy(this.tooltipManager.hideToolTip, this), 
			click : $.proxy(this.tooltipManager.clickPoint, this),
			dblclick : $.proxy(this.tooltipManager.nextProgress, this)
			}); // 이벤트는 우선 그냥 다 tooltip 에서 처리하자
	},
	
	_addbaritem : function(htmlarray, status, pixel, width) {
		if(pixel > 0){
			htmlarray.push('<span class="', status,  '" style="width:', pixel, 'px;background-color:',this.options.statusColor[status] ,'"></span>');
		}
	},
	
	_makeSVG :function(tag, attrs) {
        var el= document.createElementNS('http://www.w3.org/2000/svg', tag);
        for (var k in attrs)
            el.setAttribute(k, attrs[k]);
        return el;
    },
	
	_tooltipHtml : function(){
		return ['<time>00:00</time>','<button type="button" data-value="day" class="on"><span>',this._oeeType.day.txt.call(this),'</span></button>'
            ,'<button type="button" data-value="hour"><span>',this._oeeType.hour.txt.call(this),'</span></button>'
            ,'<button type="button" data-value="min"><span>',this._oeeType.min.txt.call(this),'</span></button>', '<div class="arrow">'].join("");
	},
	
	_addOeeResult : function(oeeResult, sec){
		if(sec > 0){
			oeeResult.sec += parseInt(sec);
			oeeResult.cnt ++;
		}
	},
	
	_getOeeTemplete : function(){
		var result = {}
		for(var key in this.options.statusColor){
			result[key] = { sec : 0 , cnt : 0 };
		}
		return result;
	},
	
	_setCurrentType : function(t){
		if(this._oeeType[t]){
			this._cunrrentType = this._oeeType[t];
		}
	},
	
	_pad : function(value, length) {
		value = String(value);
		length = parseInt(length,10) || 2;
		while (value.length < length)  { value = '0' + value; }
		return value;
	},
	
	// 24시간 기준 pixel 변환
	_convertPx : function(val){
		val = (val >= 1440) ? val%1440 : val;
		val = (val < 0) ? 1440 + val%1440 : val;
		return val;
	},
	
	DateUtil : (function(){
		var sep = ':';
		var DateType = {
			Min : { execute :
				function(min, useSec){
			        var hh = parseInt(min / 60);
					min %= 60;
				    return [pad(hh,2),sep ,pad(parseInt(min),2) ,(useSec ? (sep+"00") : "")].join("");
				}
			},
			Second : { execute :
				function(sec, useSec){
			        var hh = parseInt(sec / 3600);
					sec %= 3600;
				    var mm = parseInt(sec / 60), ss = parseInt(sec % 60);
				    return [pad(hh,2), pad(mm,2), useSec ? pad(ss,2) : ""].join(sep);
				}
			},
			Hour :{ execute :
				function(hour, useSec){
				    return [pad(parseInt(hour),2), "00", useSec ? "00" : ""].join(sep);
				}
			},
		}
		,getString = function(fn, val, useSec){
			return fn.execute.call(this, val, useSec=== undefined ? true : useSec);
		}
		,pad = function(value, length) {
			value = String(value);
			length = parseInt(length,10) || 2;
			while (value.length < length)  { value = '0' + value; }
			return value;
		}
		
		return{
			DateType : DateType
			,getString : getString
		}
	})(),
	
	
    _getdate : function(datestring) {
		var extracted = datestring.match(/([0-9]{4}).([0-9]{2}).([0-9]{2}) ([0-9]{2}).([0-9]{2}).([0-9]{2})/);
		return extracted && extracted.length > 6 ? new Date(extracted[1], parseInt(extracted[2])-1, extracted[3], extracted[4], extracted[5], extracted[6]) : new Date();
	},
	
	_getSecond : function(datestring) { //XX:XX , XX:XX:XX 둘다 처리 굳이 정규식 필요없음, 횟수가 많을 경우 정규식이 더느림
		var extracted = datestring.split(/[-: ]/);
		var len = extracted.length;
		return extracted && extracted.length > 1 ? (extracted.length % 3 == 0 ? 
				parseInt(extracted[len-3])*60*60 + parseInt(extracted[len-2])*60 + parseInt(extracted[len-1])
				: parseInt(extracted[len-2])*60*60 + parseInt(extracted[len-1])*60 ) : 0;
	},
	
	_getTxt : function(lastPoint, width){
		return this._cunrrentType.getPointTimeTxt.call(this, lastPoint, width);
	},
	
	_update : function(startMin){
		this._updateTimeTxt(0, startMin);
		this._drawbar(this._lastData, startMin);
	},
	
	// type : "min", "hour", "day"
	setData : function(data, t, startDateTime){
    	this._lastData = data;
		this.setStartDateTime(t, startDateTime);
    },
    setStartDateTime : function(t, startDateTime){
    	var startMin = t == "day" ? this.oeeTimeTxt[0].data("orgpixel") :
    		(startDateTime !== undefined ? this._getSecond(startDateTime)/60 : this.oeeTimeTxt[0].data("timepixel"));
		this._setCurrentType(t);
    	this._updateTimeTxt(0, startMin);
    	this._drawbar(this._lastData, startMin);
    },
    clear : function(){
    	this.setData([], "day", this.options.dutyTime);
    },
    next : function(){
    	if( this._getSecond(this.options.dutyTime)/60 +1440 <= this.oeeTimeTxt[0].data("timepixel") + this._cunrrentType.rate/60){
    		if($.isFunction( this.options.onChangedDate)){
    			this._trigger("onChangedDate", null , [+1]);
        		this._updateTimeTxt(0, this.oeeTimeTxt[0].data("orgpixel"));
        		console.log("trigger onChangedDate  +1 day")
    		}else if(this._cunrrentType != this._oeeType.day){ // day 는 굳이 다시 그릴필요 없음
    			this._update(this.oeeTimeTxt[0].data("orgpixel"));
    		}
    	}else{
    		this._update(this.oeeTimeTxt[0].data("timepixel") + this._cunrrentType.rate/60);
    	}
        	
    },
    prev : function(){
    	if(this._getSecond(this.options.dutyTime)/60 > this.oeeTimeTxt[0].data("timepixel") - this._cunrrentType.rate/60){
    		if($.isFunction( this.options.onChangedDate)){
	    		this._trigger("onChangedDate", null , [-1]);
	    		this._updateTimeTxt(0, this.oeeTimeTxt[0].data("timepixel") - this._cunrrentType.rate/60);
	    		console.log("trigger onChangedDate  -1 day");
    		}else if(this._cunrrentType != this._oeeType.day){ // day 는 굳이 다시 그릴필요 없음
    			this._update(this.oeeTimeTxt[0].data("orgpixel") + 1440 - this._cunrrentType.rate/60);
    		}
    	}else{
    		this._update(this.oeeTimeTxt[0].data("timepixel") - this._cunrrentType.rate/60);
    	}
    },
    
    tooltipManager : (function(){
		var displayed = false , hideToolTiptimer = [], lastPoint, width, pLeft, tWidth, tHWidth, width, aLeft, 
			$parent, $tooltip, $time, $tabs, $arrow;
		// $.proxy 이부분에서는 빼고 내부변수로 바꾸자 굳이 proxy 사용할 이유가 없음(오히려 비효율적)
		var initialize = function(){
			$parent = this, $tooltip =this.tooltip, $time = $tooltip.find('time'), $tabs = $tooltip.find('button'),$arrow = $tooltip.find('.arrow');
			aLeft = $arrow.position().left, pLeft = $tooltip.parent().offset().left, tWidth = $tooltip.width(), tHWidth = tWidth/2, width =this.oeeBar.width();
			$tooltip.on({mouseenter: cancelHideToolTip, mouseleave: $.proxy(hideToolTip, this)});
			
			$tabs.click($.proxy(function(e){
				var $this = $(e.currentTarget), index = $this.index()-1, t = $this.data("value"),x = 0, rate = this._cunrrentType.rate;
				this._setCurrentType(t); 
				if(t == 'day'){
            		x = this.oeeTimeTxt[0].data("orgpixel");
            	}else if(t == 'hour' && this._cunrrentType.rate > rate){
					this._setCurrentType(t);
					x = this.oeeTimeTxt[0].data("timepixel");
					x = x - x % 60;
            	}else{
            		x = lastPoint * rate/60/width;
                	rate = this._cunrrentType.rate/60;
                	x = parseInt(x/rate) * rate +this.oeeTimeTxt[0].data("timepixel");
            	}
				this._trigger("onChangedViewType", null , [t]);
				$tabs.removeClass('on').eq(index).addClass('on');
				this._update(x);
			}, this));
		}
		, calculatePoint = function(e) {
			if(this.options.isLimitMoveTooltip){
				_calculateLPoint(e, this.options.margin + this.options.margin); //덧셈 속도때문에
			}else{
				$tooltip.css({left: e.clientX - pLeft - tHWidth});
			}
			lastPoint = e.clientX - pLeft- this.options.margin;
			$time[0].innerHTML = this._getTxt(lastPoint, width);
			if (this.options.isShowTooltip && !displayed) {
				this.tooltip.show();
				displayed = true;
			}
			cancelHideToolTip();
			this._trigger("onChangedTooltip", null , [lastPoint, $time[0].innerHTML, true]);
		}
		, _calculateLPoint = function(e, fMargin) { // 속도가 왜 Math 함수가 더느릴까...
			var x = e.clientX - pLeft - tHWidth;
			var limit = width- tWidth + fMargin;
			if(x > 0){
				if(x < limit){
					$tooltip.css({left:x});
					$arrow.css({left:"50%"});
				}else{
					$tooltip.css({left:limit});
					$arrow.css({left: x-limit+tHWidth});
				}
			}else{
				$tooltip.css({left:0});
				$arrow.css({left: x + tHWidth});
			}
		}
		, hideToolTip = function(event) {
			event.preventDefault()
			hideToolTiptimer[hideToolTiptimer.length] = setTimeout( $.proxy(hideToolTipAction, this), 100);
		}
		, cancelHideToolTip = function(event) {
			for(var i = 0, len = hideToolTiptimer.length; i < len ; i++){
				clearTimeout(hideToolTiptimer[i]);
			}
			hideToolTiptimer = [];
		}
		, hideToolTipAction = function() {
			$tooltip.hide();
			this._trigger("onChangedTooltip", null , [-1, '', false]);
			displayed = false;
		}
		, nextProgress = function(e){
           	lastPoint = e.clientX - pLeft- this.options.margin;
			var $next = $tabs.filter(".on").next("button");
			if($next.length > 0 ){
				$next.trigger("click");
			}else{
				$tabs.first().trigger("click");
			}
		}
		, clickPoint = function(){
			this._trigger("onClickBarChart", null , [this._getTxt(lastPoint, width)]);
		}
		, clear = function(){
			$tabs.removeClass("on").eq(0).addClass("on");
		};
		return {
			calculatePoint : calculatePoint
			, hideToolTip : hideToolTip
			, cancelHideToolTip : cancelHideToolTip
			, nextProgress : nextProgress
			, clear : clear
			, clickPoint : clickPoint
			, initialize : initialize
			}
	})()
});