/**
 * Google Charts API
 *
 * @copyright Anton Shevchuk
 */
var ChartsAPI = function(){
    var api = 'http://chart.apis.google.com/chart?';
    var opt = {};
    var dataLength = 0; // requried for generate ID
    var axisLength = 0; // requried for generate ID
    var hash = '';
    var options = {
		type:'p3',
        size:'320x240',
        
        encode:'s',
        max:100,
        
        data :new Array(), 
        label:new Array(),
        color:new Array(),
        axis :new Array(),
        
        // Title
        title:'',
        title_color:'000000',
        title_size:12,
        
        // Grid
        grid:false,
        grid_data:[10,10,10,0,0,0],
        
        // Background
        // chf=<fill type>,s,<color>|<fill type>,s,<color>
        // chf=<fill type>,lg,<angle>,<color 1>,<offset 1>,<color n>,<offset n>
        // chf=<fill type>,ls,<angle>,<color 1>,<width 1>,<color n>,<width n>
        bg:false,
        bg_type:"s", // s - solid;lg - linear gradient;ls - linear stripes
        bg_data:["ffffff"],
        
        // Background (chart)
        chbg:false,
        chbg_type:"s",
        chbg_data:["ffffff"],
        
        // Fill 
        fillarea:false,
        
        // Bar settings
        bar:false,
        bar_auto:true,
        bar_width:20,
        bar_space_bar:1,
        bar_space_group:1
    };
    
    var params = {
        type           : "cht",
        size           : "chs",
        data           : "chd",        
        legend         : "chdl", // chdl=<first data set label>|<n data set label>
        label          : "chl",  // chl=<first data set label>|<n data set label>

        scaling        : "chds",
        
        color          : "chco", // chco=<color1>,...<colorn>
        
        axis_type      : "chxt", // chxt=<axis 1>,...<axis n>
        axis_label     : "chxl", // chxl=<axis index>:|<label 1>|<label n>|...
        axis_position  : "chxp", // chxp=<axis index>,<label 1 position>,<label n position> ...
        axis_range     : "chxr", // chxr=<axis index>,<start of range>,<end of range>|...
        axis_style     : "chxs", // chxs=<axis index>,<color>,<font size>,<alignment>|...
        
        background     : "chf",
        fillarea       : "chm",  // chm=b,<color>,<start line index>,<end line index>,<any value>|...
        margin         : "chma", // chma=<left margin>,<right margin>,<top margin>,<bottom margin>|<legend width>,<legend height>
        
        title		   : "chtt",
        title_style	   : "chts",
        
        bar_width      : "chbh", // chbh=<bar width in pixels>,<optional space between bars in a group>,<optional space between groups>        
        line_style	   : "chls", // chls=<data set 1 line thickness>,<length of line segment>,<length of blank segment>|...
        grid		   : "chg",  // chg=<x axis step size>,<y axis step size>,<length of line segment>,<length of blank segment>,<x offset>,<y offset>
        agent		   : "agent",
        max            : "max"
    };
    
    var palette = [ "5131C9","FFCC00","DA1B1B",
                    "FF9900","FF6600","CCFFFF",
                    "CCFF00","CCCCCC","FF99CC",
                    "999900","999999","66FF00",
                    "66CC00","669900","660099",
                    "33CC00","333399","000000"  ];
                    
    var hexMap      = '0123456789ABCDEFabcdef';
    var simpleMap   = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    var extendedMap = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.';
    
    var simpleEncode = function(valueArray,maxValue) {
        var chartData = [];
        for (var i = 0; i < valueArray.length; i++) {
            var currentValue = valueArray[i];
            if (!isNaN(currentValue) && currentValue >= 0) {
                chartData.push(simpleMap.charAt(Math.round((simpleMap.length-1) * currentValue / maxValue)));
            } else {
                chartData.push('_');
            }
        }
        return chartData.join('');
    };
    
    var simpleDecode = function(valueString,maxValue) {
        var valueArray = [];
        for (var i = 0; i < valueString.length; i++) {
            valueArray.push(Math.round(simpleMap.indexOf(valueString.charAt(i)) * maxValue / (simpleMap.length-1)));
        }
        return valueArray;
    };
    
    var extendedEncode = function(valueArray,maxValue) {
        var chartData = [];
        for (var i = 0; i < valueArray.length; i++) {
            var currentValue = valueArray[i];
            if (!isNaN(currentValue) && currentValue >= 0) {
                var quotient  = Math.floor(currentValue / extendedMap.length);
                var remainder = currentValue - extendedMap.length * quotient;
                
                chartData.push(extendedMap.charAt(quotient) + extendedMap.charAt(remainder));
            }
            else {
                chartData.push('__');
            }
        }
        return chartData.join('');
    }

    var extendedDecode = function(valueString,maxValue) {
        var valueArray = [];
        for (var i = 0; i < valueString.length; i++) {
            var first  = extendedMap.indexOf(valueString.chartAt(i));
            var second = extendedMap.indexOf(valueString.chartAt(++i)); // second increment of i
            
            valueArray.push(first * extendedMap.length + second);
        }
        return valueArray;
    };
    
    var urlDecode = function (encoded)
    {
       // Replace + with ' '
       // Replace %xx with equivalent character
       // Put [ERROR] in output if %xx is invalid.
       var plaintext = "";
       var i = 0;
       while (i < encoded.length) {
           var ch = encoded.charAt(i);
    	   if (ch == "+") {
    	       plaintext += " ";
    		   i++;
    	   } else if (ch == "%") {
    			if (i < (encoded.length-2) 
    					&& hexMap.indexOf(encoded.charAt(i+1)) != -1 
    					&& hexMap.indexOf(encoded.charAt(i+2)) != -1 ) {
    				plaintext += unescape( encoded.substr(i,3) );
    				i += 3;
    			} else {
    				alert( 'Bad escape combination near ...' + encoded.substr(i) );
    				plaintext += "%[ERROR]";
    				i++;
    			}
    		} else {
    		   plaintext += ch;
    		   i++;
    		}
    	} // while
       return plaintext;
    };

    var isset = function(variable){
        return( typeof( variable ) != 'undefined' );
    };
          
    /**
     * Set propertie value
     */
    var set = function(type, data) {
        options[type] = data;
    };
    
    /**
     * Get property
     */
    var get = function(type) {
        return options[type];
    };
    
    var validate = function(type, data) {
        switch (type) {
            case 'data':
                if (data == '') return false;
                break;
        }
        return true;
    };
    /**
     * remove empty array elements
     * @param Array Arr
     */
    var clean = function(Arr){
        var newArr = new Array();
        for (var i = 0; i < Arr.length; i++) {
            if (typeof( Arr[i] ) != 'undefined' && Arr[i] != '') {         
                newArr.push(Arr[i]);
            }
        }
        return newArr;
    };
    /**
     * Add data to container
     *
     * @param string data
     */
    var addData = function(data, label, color) {
        var id = dataLength++;
        
        if (!$.isArray(data))
            data  = _parseData(data);
            
        if (parseInt(options.max) < parseInt(data.max())) {
            options.max = data.max();
        }
        
        if (data) {
            // label, data, color
            options.data[id]  = data;
            options.label[id] = label;
            options.color[id] = (color == 'auto')?palette[id]:color;
            return id;
        } else {            
            return null;
        }
    };
    
    /**
     * Edit data
     */
    var getData = function(id) {
        return [options.data[id].join(','), options.label[id], options.color[id]];
    };
    
    /**
     * Delete data from container
     */
    var delData = function(id) {
        delete options.data[id];        
        delete options.label[id];        
        delete options.color[id];
    }; 
    
    /**
     * Get data from data field
     */
    var _parseData = function(data) {
        // need more rules for check data
        data = $.trim(data);
        data = data.replace(/[; \n]/gi,',');
        data = data.replace(/(,,)/gi,',');
        data = data.replace(/[^0-9\-,\.]/gi,'');
        
        if (data.substr(data.length-1,1) == ',') {
            data = data.substr(0,data.length-1);
        }
        
        if (data.length==0) {
            return null;            
        }
        return data.split(',');
    };
    
    /**
     * Add axis to container
     */
    var addAxis = function(type, label, opt, range, style) {    
       var id = axisLength++;
       
       label = label.replace(/[,]/gi,'|');
       label = label.split('|');

       options.axis[id] = {
          type  :type,
          label :label,
          opt   :opt,
          range :range,
          style :style
       };
       return id;
    };
    
    /**
     * Clear axis
     */
    var getAxis = function(id) {
        return options.axis[id];
    };
    
    /**
     * Delete data from container
     */
    var delAxis = function(id) {
        delete options.axis[id];
    };
        
    /**
     * Set Image Background
     */
    var setBg = function(type, data) {
        set('bg', true);
        set('bg_type', type);
        set('bg_data', data);
    };
    
    /**
     * Set Chart Background
     */
    var setChbg = function(type, data) {
        set('chbg', true);
        set('chbg_type', type);
        set('chbg_data', data);
    };
    
    /**
     * set bar options
     */
    var setBarWidth = function(width, space_bar, space_group) {
        
        if (width == 'a') {
            set('bar', true);
            set('bar_auto', true);
            return;
        }
        
        set('bar', true);
        set('bar_auto', false);
        set('bar_width', width);
        set('bar_space_bar', space_bar);
        set('bar_space_group', space_group);
    };
    
    /**
     * Parse URL
     */
    var parse = function(url, simple) {
        
        var urlObj  = {};
        
        if (!simple)
            url = url.substr(35);

        url = url.split("&");
        for(var i=0; i < url.length; i++) {
            var t = url[i].split('=');
            var k = _paramKey(t[0]);
            if (k)
                urlObj[k] = t[1];
        }
        
        // size
        // .. should be all ok
        // title
        if (isset(urlObj.title) && isset(urlObj.title_style)) {
            urlObj.title = urlDecode(urlObj.title);
            var t = urlObj.title_style.split(',');
            urlObj.title_color = t[0];
            urlObj.title_size  = t[1];
            
            delete urlObj.title_style;
        }
        // data
        urlObj.data = urlObj.data.split(':');
        var encodeType =  urlObj.data[0];

        if (!isset(urlObj.max)) {
            urlObj.max = 100;
        } else {
            urlObj.max = parseInt(urlObj.max);
        }


	 if (encodeType == 't') {
            urlObj.data = urlObj.data[1].split('|');
        } else {
            urlObj.data = urlObj.data[1].split(',');
        }

        for (var i = 0; i < urlObj.data.length; i++) {
            switch (encodeType) {
                case 't':
                    urlObj.data[i] = urlObj.data[i].split(',');
                    break;
                case 's':
                    urlObj.data[i] = simpleDecode(urlObj.data[i], urlObj.max);
                    break;
                case 'e':
                    urlObj.data[i] = extendedDecode(urlObj.data[i], urlObj.max);
                    break;
            }
            urlObj.encode  = encodeType;
            dataLength++;
        }

        // label
        if (isset(urlObj.label)) {
            urlObj.label = urlDecode(urlObj.label);
            urlObj.label = urlObj.label.split('|');
        }
        if (isset(urlObj.legend)) {
            urlObj.legend = urlDecode(urlObj.legend);
            urlObj.label = urlObj.legend.split('|');
            delete urlObj.legend;
        }
        
        // color
        if (isset(urlObj.color)) {
            urlObj.color = urlObj.color.split(',');
        } else {
            urlObj.color = [];
        }
        
        for (var i = 0; i < dataLength; i++) {
            if (!isset(urlObj.color[i])) {
                urlObj.color[i] = palette[i];
            }
        }
        
        // axes
        if (isset(urlObj.axis_type)) {
            var axis_type = urlObj.axis_type.split(',');
            
            var axis_label = [];
            var axis_position = [];
            var axis_range = [];
            var axis_style = [];
            
            /*  axis_label     : "chxl", // chxl=<axis index>:|<label 1>|<label n>|...
                axis_position  : "chxp", // chxp=<axis index>,<label 1 position>,<label n position>|...
                axis_range     : "chxr", // chxr=<axis index>,<start of range>,<end of range>|...
                axis_style     : "chxs", // chxs=<axis index>,<color>,<font size>,<alignment>|...  */
            
            if (isset(urlObj.axis_label)) {
                axis_label = urlObj.axis_label.split('|');                
                var t = [];
                var n = null;
                $.each(axis_label, function(key,val){
                    if (val.match(/\d+:/g)) {
                        n = val.substr(0, val.length-1);
                        t[n] = [];
                    } else {
                        t[n].push(val);
                    }
                });
                axis_label = t;
            }
            
            if (isset(urlObj.axis_position)) {
                axis_position = urlObj.axis_position.split('|');
                var t = [];
                $.each(axis_position, function(key,val){
                    t[val.split(',')[0]] = val.split(',').slice(1);
                });
                axis_position = t;
            }
            
            if (isset(urlObj.axis_range)) {
                axis_range = urlObj.axis_range.split('|');
                var t = [];
                $.each(axis_range, function(key,val){
                    t[val.split(',')[0]] = val.split(',').slice(1);
                });
                axis_range = t;                
            }
            
            if (isset(urlObj.axis_style)) {
                axis_style = urlObj.axis_style.split('|');
                var t = [];
                $.each(axis_style, function(key,val){
                    t[val.split(',')[0]] = val.split(',').slice(1);
                });
                axis_style = t;         
            }
            
            urlObj.axis = [];
            $.each(axis_type, function(i, val) {
                
                var label = (isset(axis_label[i])?axis_label[i]:[]);
                var range = (isset(axis_range[i])?axis_range[i]:[]);
                var style = (isset(axis_style[i])?axis_style[i]:[]);
                var opt   = 'd';
                
                switch (true) {
                    case isset(axis_label[i]):
                        opt = 'm';
                        break;
                    case isset(axis_range[i]):
                        opt = 's';
                        break;
                }
                urlObj.axis[i] = {
                  type  :val,
                  label :label,
                  opt   :opt,
                  range :range,
                  style :style
                };
                
                axisLength++;
            });
            
            // remove temporary data
            delete urlObj.axis_type;
            delete urlObj.axis_label;
            delete urlObj.axis_range;
            delete urlObj.axis_style;
        }
        
        // background
        if (isset(urlObj.background)) {
            urlObj.background = urlObj.background.split('|');
            
            $.each(urlObj.background, function(i,val) {
                var t = val.split(',');
                if (t[0] == 'bg') {
                    urlObj.bg = true;
                    urlObj.bg_type = t[1];
                    urlObj.bg_data = t.slice(2);
                }
                
                if (t[0] == 'c') {
                    urlObj.chbg = true;
                    urlObj.chbg_type = t[1];
                    urlObj.chbg_data = t.slice(2);
                }
            });
            
            delete urlObj.background;
        }
        
        // bar options
        if (isset(urlObj.bar_width)) {
            if (urlObj.bar_width == 'a') {
                urlObj.bar      = true;
                urlObj.bar_auto = true;
            } else {
                var t = urlObj.bar_width.split(',');                
                urlObj.bar      = true;
                urlObj.bar_auto = false;
                urlObj.bar_width = t[0];
                urlObj.bar_space_bar = t[1];
                urlObj.bar_space_group = t[2];
            }
            
        }
        
        // fillarea
        if (isset(urlObj.fillarea)) {
            urlObj.fillarea = true;
        }
        
        // grid
        if (isset(urlObj.grid)) {
            urlObj.grid_data = urlObj.grid.split(',');
            urlObj.grid = true;
        }
        options = $.extend({}, options, urlObj);
        
//        console.log('api.parse');
//        console.log(urlObj);
//        console.log('data: '+dataLength);
    };
    
    var _paramKey = function(value) {
        var key = null;
        $.each(params, function(k,v){
           if (v == value) key = k;
        });
        return key;       
    };
    /**
     * Build URL
     */
    var build = function(params, preview) {
        
        if (params)
            opt = $.extend({}, options, params);
        else
            opt = $.extend({}, options);
            
        var url = '';
        url += param("type",  opt.type);
        url += param("size",  opt.size);
        url += param("data",  _data());
        
        url += _label();
        url += _color();
        url += _axis();
        url += _bg();
        url += _bar();
        url += _fill();
        url += _grid();
        
        if (opt.title != '') {
            url += param("title", opt.title);
            url += param("title_style", opt.title_color + ',' + opt.title_size);
        }
        url += param("max",   opt.max);
        url += param("agent", "hohli.com", true);
        
        if (!preview) {
            this.hash = document.location.hash = url;
        }
//        console.log('api.build: '+url);
//        console.log(opt);
        return api+url;
    };
    
    var param = function(index, data, last){
        if (!data) return '';
        
        var l = last ? "" : "&"; 
        return params[index] + "=" + data + l;
    };
    
    /**
     * Get Maximum
     */
    var _max = function() {
        var maxArr = [];
        for (var i = 0; i < opt.data.length; i++) {
            maxArr.push(opt.data[i].max());
        }
        
        var maxValue = maxArr.max();
        
        return (opt.max >= maxValue)?opt.max:maxValue;
    }
    
    /**
     * Generate URL data
     */
    var _data = function() {
        
        opt.data = clean(opt.data);
        
//        if (opt.max >= 1000) {
//            opt.encode = 'e';
//        }
        
        switch (opt.type) {
            case 'p':
            case 'p3':
            case 'ps':
                var chart = [];
                if (opt.data.length == 1 && $.isArray(opt.data[0])) {
                    chart  = opt.data[0];
                    if (opt.encode == 't')   
                        return 't:' + chart.join('|');
                    else if (opt.encode == 's')            
                        return 's:' + simpleEncode(chart, opt.max);
                    else if  (opt.encode == 'e')
                        return 'e:' + extendedEncode(chart, opt.max);
                }

                if (opt.encode == 't') {
                    for (var i = 0; i < opt.data.length; i++) {
                        chart.push(opt.data[i].join(','));
                    }
                    return 't:' + chart.join('|');
                } else if (opt.encode == 's') {
                    for (var i = 0; i < opt.data.length; i++) {
                        chart.push(simpleEncode(opt.data[i], opt.max));
                    }
                    return 's:' + chart.join(',');
                } else {                    
                    for (var i = 0; i < opt.data.length; i++) {
                        chart.push(extendedEncode(opt.data[i], opt.max));
                    }
                    return 'e:' + chart.join(',');
                }

                break;
            case 'lxy':
                var chart = [];
                if (opt.encode == 't') {
                    for (var i = 0; i < opt.data.length; i++) {
                        var x = [];
                        var y = [];
                        for (var j = 0; j < opt.data[i].length; j++) {
                            x.push(opt.data[i][j]);
                            y.push(opt.data[i][++j]); // second increment of j
                        }
                        chart.push(x.join(','));
                        chart.push(y.join(','));
                    }
                    return 't:' + chart.join('|');
                } else if (opt.encode == 's') {
                    for (var i = 0; i < opt.data.length; i++) {
                        var x = [];
                        var y = [];
                        for (var j = 0; j < opt.data[i].length; j++) {
                            x.push(opt.data[i][j]);
                            y.push(opt.data[i][++j]); // second increment of j
                        }
                        chart.push(simpleEncode(x, opt.max));
                        chart.push(simpleEncode(y, opt.max));
                    }
                    return 's:' + chart.join(',');
                } else if (opt.encode == 'e') {                    
                    for (var i = 0; i < opt.data.length; i++) {
                        var x = [];
                        var y = [];
                        for (var j = 0; j < opt.data[i].length; j++) {
                            x.push(opt.data[i][j]);
                            y.push(opt.data[i][++j]); // second increment of j
                        }
                        chart.push(extendedEncode(x, opt.max));
                        chart.push(extendedEncode(y, opt.max));
                    }
                    return 'e:' + chart.join(',');
                }
                break;
            default:
                var chart = [];
                if (opt.encode == 't') {
                    for (var i = 0; i < opt.data.length; i++) {
                        chart.push(opt.data[i].join(','));
                    }
                    return 't:' + chart.join('|');
                } else if (opt.encode == 's') {
                    for (var i = 0; i < opt.data.length; i++) {
                        chart.push(simpleEncode(opt.data[i], opt.max));
                    }
                    return 's:' + chart.join(',');
                } else {                    
                    for (var i = 0; i < opt.data.length; i++) {
                        chart.push(extendedEncode(opt.data[i], opt.max));
                    }
                    return 'e:' + chart.join(',');
                }
                break;
        }
    };
    
    var _label = function() {
        if (opt.label.length == 0) return '';
        
        opt.label = clean(opt.label);
        
        switch (opt.type) {
            case 'p':
            case 'p3':
            case 'ps':
                return param("label", opt.label.join('|'));
            default:
                return param("legend", opt.label.join('|'));
                break;
        }
        
    };
    
    var _color = function() {
        if (opt.color.length == 0) return '';
        
        opt.color = clean(opt.color);
        
        return param("color", opt.color.join(','));
    };
    
    var _axis = function() {
        
        opt.axis = clean(opt.axis);
        
        var axis_type = [];
        var axis_label = [];
        var axis_position = [];
        var axis_range = [];
        var axis_style = [];
        
        var url = '';
        
        for (var i = 0; i < opt.axis.length; i++) {
            var axis = opt.axis[i];
            axis_type.push(axis.type);
            axis_style.push(i+','+axis.style.join(','))
            
            switch (axis.opt) {
                case 'd':                
                    break;
                case 'a':
                    var range = [0,opt.max];
                    axis_range.push(i+','+range.join(','));
                    break;
                case 's':
                    axis_range.push(i+','+axis.range.join(','));
                    break;
                case 'm':
                    axis_label.push(i+':');
                    axis_label.push(axis.label.join('|'));
                    break;
            }         
        }
        url += param('axis_type',  axis_type.join(','));
        url += param('axis_label', axis_label.join('|'));
        url += param('axis_style', axis_style.join('|'));
        url += param('axis_range', axis_range.join('|'));
        
        return url;
    };
    
    var _bg = function() {
        var bg = [];
        
        if (opt.bg) {
            bg.push('bg,'+opt.bg_type+','+opt.bg_data.join(','));
        }
        
        if (opt.chbg) {
            bg.push('c,'+opt.chbg_type+','+opt.chbg_data.join(','));
        }
        
        return param('background', bg.join('|'));
    };
    
    var _bar = function() {
        if ((   get('type') == 'bhs'
             || get('type') == 'bvs'
             || get('type') == 'bhg'
             || get('type') == 'bvg') && get('bar')) {
            
           if (get('bar_auto')) return param('bar_width','a');
           
           return param('bar_width',get('bar_width')+','+get('bar_space_bar')+','+get('bar_space_group'));
           
       } else {
           return '';
       }
    };
    var _fill = function() {
        if (opt.fillarea) {
            var url = [];
	        $.each(opt.color, function(index,value){
	        	url.push('b,' + value + ',' + index + ',' +(index + 1) + ',' + '0');
	        });
	        return param('fillarea', url.join('|'));
        }
        return '';
        
    };
    var _grid = function() {
        if (opt.grid) {
            return param('grid', opt.grid_data.join(','));
        } else {
            return '';
        }
    };
    
    // public section
    this.build   = build;
    this.parse   = parse;
    this.set     = set;
    this.get     = get;
    this.validate = validate;
    // FIXME
    this.options = options;
    
    this.addData = addData;
    this.getData = getData;
    this.delData = delData;
    
    this.addAxis = addAxis;
    this.getAxis = getAxis;
    this.delAxis = delAxis;
    
    this.setBg = setBg;
    this.setChbg = setChbg;
    
    this.setBarWidth = setBarWidth;

}

/**
 * Array Prototype changes
 */

if(!Array.indexOf)
Array.prototype.indexOf = function(obj){
    for(var i=0; i<this.length; i++){
        if(this[i]==obj){
            return i;
        }
    }
    return -1;
}

if(!Array.max)
Array.prototype.max = function() {
    var max = this[0];
    var len = this.length;
    for (var i = 1; i < len; i++) if (this[i] > max) max = this[i];
    return max;
}

if(!Array.min)
Array.prototype.min = function() {
    var min = this[0];
    var len = this.length;
    for (var i = 1; i < len; i++) if (this[i] < min) min = this[i];
    return min;
}

if(!Array.clean)
Array.prototype.clean = function() {
    for (var i = 0; i < this.length; i++) {
        if (typeof( this[i] ) == 'undefined') {         
            this.splice(i, 1);
            i--;
        }
    }
    return this;
};