var classAirGraph = function(lib, settings)
{
    // Module references //

    var _lib = lib || null;
    var _modules = {};
    var _this = this;
    this.version = '1.0';
    this.toString = function(){ return _lib.toString() + ' - Graph'; };
    arguments.callee.toString = function(){ return _lib.toString() + ' - Graph - Constructor'; };

    // Private properties //

    /**
    * Module memory.
    * @type Object
    */
    var _memory =
    {
        categories : [],
        defaultSettings : {},
        drawCallback : function(){},
        highchart : null,
        legends : [],
        points : {},
        series : {},
        titles : {},
        visible : false,
        units : []
    };

    // Public properties //

    /**
    * DOM references used by module.
    * @type Object
    */
    this.dom =
    {
        container : null,
        graph : null,
        legends : null,
        titles : null
    };

    /**
    * Module settings.
    * @type Object
    */
    this.settings =
    {
        imageDirPath : '/images/shared/air',
        classNames :
        {
            clear : 'clear',
            container : 'afwGraph',
            first : 'first',
            graphContainer : 'highchartsGraph',
            legends : 'afwGraphLegends',
            title : 'title'
        },
        domIndexes :
        {
            container : 'afwGraph'
        },
        graph :
        {
            chart :
            {
                defaultSeriesType : 'line',
                renderTo : null
            },
            colors : ['#4572A7','#AA4643','#89A54E', '#80699B','#3D96AE','#DB843D','#92A8CD','#A47D7C','#B5CA92'],
            colorsLight : ['#DAE3ED','#EEDAD9','#E7EDDC', '#E6E1EB','#D8EAEF','#F8E6D8','#E9EEF5','#EDE5E5','#F0F4E9'],
            credits :
            {
                enabled : false,
                href : 'javascript:void(0);',
                style :
                {
                    cursor : 'default'
                },
                text : 'Alert IR powered by Highcharts.com'
            },
            legend :
            {
                enabled : false
            },
            navigation :
            {
                buttonOptions :
                {
                    enabled : false
                }
            },
            tooltip :
            {
                crosshairs : true
            },
            symbols : ['circle','diamond','square','triangle','triangle-down'],
            title :
            {
                text : ''
            },
            tooltip :
            {
                crosshairs : [{width : 1, color : 'grey'}, {width : 1, color : 'grey'}],
                formatter : function()
                {
                    return '<b>' + this.series.name + '</b><br>' + this.x + ':  <b>' + this.y + '</b>';
                }
            },
            xAxis :
            {
                endOnTick : false,
                labels :
                {
                    align : 'center',
                    rotation : 0
                },
                startOnTick : false,
                tickmarkPlacement : 'on',
                tickPosition : 'inside'
            },
            yAxis : []
        },
        xLabelsSpacing : 0
    };

    // Private functions //

    /**
    * Adds a legend.
    * @ignore
    */
    function _addLegend(options)
    {
        var legendIndex, legendUnitIndex, dom = {};
        legendUnitIndex = _this.getLegendUnitIndex(options.unit);
        if(legendUnitIndex === false)
        {
            dom =
            {
                block : document.createElement('DIV'),
                title : document.createElement('DIV'),
                list : document.createElement('DIV'),
                clear : document.createElement('DIV')
            };
            _lib.addClass(dom.block, options.type);
            dom.clear.className = _this.settings.classNames.clear;
            dom.title.className = _this.settings.classNames.title;
            dom.title.innerHTML = options.unit;
            dom.block.appendChild(dom.title);
            dom.block.appendChild(dom.list);
            legendUnitIndex = _memory.legends.length;
            _memory.legends.push(
            {
                dom : dom,
                legends : [],
                unit : options.unit
            });
            if(legendUnitIndex == 0)
            {
                _lib.addClass(dom.block, _this.settings.classNames.first);
            }
            _this.dom.legends.appendChild(dom.block);
        }
        legendIndex = _memory.legends[legendUnitIndex].legends.length;
        _memory.legends[legendUnitIndex].legends.push(
        {
            dom : document.createElement('SPAN'),
            id : options.id,
            index : legendIndex,
            unit : options.unit
        });
        _memory.legends[legendUnitIndex].legends[legendIndex].dom.innerHTML = '';
        if(options.symbol)
        {
            _lib.addClass(_memory.legends[legendUnitIndex].legends[legendIndex].dom, 'symbol');
            _memory.legends[legendUnitIndex].legends[legendIndex].dom.innerHTML += '<img src="' + _lib.rtrim(_this.settings.imageDirPath, '/') + '/graph/' + options.symbol + '-' + options.color.replace('#', '') + '.gif" alt="" />&nbsp;';
            _memory.legends[legendUnitIndex].legends[legendIndex].dom.style.color = options.color;
        }
        else
        {
            _memory.legends[legendUnitIndex].legends[legendIndex].dom.innerHTML += '<img src="' + _lib.rtrim(_this.settings.imageDirPath, '/') + '/graph/solid-' + options.color.replace('#', '') + '.gif" alt="" />&nbsp;';
        }
        _memory.legends[legendUnitIndex].legends[legendIndex].dom.innerHTML += '<span class="text">' + options.name.replace(' ', '&nbsp;') + '</span>';
        _memory.legends[legendUnitIndex].dom.list.appendChild(_memory.legends[legendUnitIndex].legends[legendIndex].dom);
        _memory.legends[legendUnitIndex].dom.list.appendChild(_memory.legends[legendUnitIndex].dom.clear);
    }

    /**
    * Initialize script.
    * @ignore
    */
    function _init(settings)
    {
        // Verify that library is loaded and linked
        if(_lib === null)
        {
            alert('Library missing for ' + _this.toString());
            return;
        }

        // Settings
        _this.settings = _lib.mergeObjects(_this.settings, settings);

        // Store a copy of settings collection
        _memory.defaultSettings = _lib.clone(_this.settings);
    }

    /**
    * Remove a legend.
    * @ignore
    */
    function _removeLegend(id, unit)
    {
        var count, legend, unitIndex;
        legend = _this.getLegend(id);
        if(legend)
        {
            unitIndex = _this.getLegendUnitIndex(unit);
            count = _lib.getParent(legend.dom);
            count = _lib.getDOM(null, 'span', null, null, count, true, false, true);
            if(count)
            {
                _lib.removeElement(legend.dom);
                _memory.legends[unitIndex].legends[legend.index].id = '';
                if(count.length <= 1)
                {
                    _lib.removeElement(_memory.legends[unitIndex].dom.block);
                    _memory.legends[unitIndex].unit = '';
                }
            }
        }
    }

    // Public functions //

    /**
    * Add a data serie to graph.
    *
    * @param {String} id        Graph identifier.
    *                           Required.
    * @param {String} unit      Graph unit.
    * @param {Array} data       See http://www.highcharts.com/ref/#series
    *                           Required.
    * @param {String} name      See http://www.highcharts.com/ref/#series
    *                           Required.
    * @param {String} type      See http://www.highcharts.com/ref/#series
    * @param {String} xAxis     See http://www.highcharts.com/ref/#series
    * @param {String} yAxis     See http://www.highcharts.com/ref/#series
    * @param {String} color     Graph marker color.
    * @param {String} symbol    Graph marker symbol.
    * @version 1.0 2010-09-21
    * @author Mathias Petersson
    * @note Any options from http://www.highcharts.com/ref/#plotOptions-series is applicable when passing as an options object.
    */
    this.addSerie = function(id, unit, data, name, type, xAxis, yAxis, color, symbol)
    {
        var options = _lib.isValidInputs();
        if(options)
        {
            if(_memory.highchart)
            {
                options.id = options.id.replace(/\W/g, '_');
                options.type = options.type || null;
                if(!_memory.series[options.id])
                {
                    if(options.symbol)
                    {
                        options.marker = options.marker || {};
                        options.marker.symbol = options.symbol;
                    }
                    _memory.series[options.id] = options;
                    _memory.highchart.addSeries(options, _memory.visible);
                    if(!_this.settings.graph.series)
                    {
                        _this.settings.graph.series = [];
                    }
                    _this.settings.graph.series.push(options);
                    if(!_this.settings.graph.legend.enabled)
                    {
                        switch(options.type)
                        {
                            default:
                                _addLegend(options);
                            break;
                            case 'pie':
                                var i, m, dataClassName;
                                for(i = 0, m = options.data.length; i < m; ++i)
                                {
                                    dataClassName = 'data ' + (i == 0 ? 'first' : (i + 1 == m ? 'last' : ''));
                                    options.data[i].name += '<div class="' + dataClassName + '">' + _lib.numberFormat(options.data[i].y) + ' ' + options.data[i].unit + ' (' + _lib.numberFormat(options.data[i].y / options.total * 100, 2) + '%)</div>';
                                    options.data[i].unit = options.data[i].title;
                                    _addLegend(options.data[i]);
                                }
                            break;
                            case 'area':

                            break;
                        }
                    }
                }
            }
        }
    };
    this.addSerie.rules =
    {
        required : ['id', 'data', 'name'],
        types :
        {
            data : ['object', 'array', 'number'],
            unit : ['string'],
            id : ['string'],
            name : ['string'],
            type : ['string'],
            xAxis : ['number'],
            yAxis : ['number']
        }
    };

    /**
    * Adds an Y-axis.
    * This redraws the entire graph object.
    *
    * @param {String} unit      Y-axis unit.
    *                           Required.
    * @param {Boolean} redraw   If to redraw graph.
    *                           Optional. Default: true
    * @version 1.0 2010-09-28
    * @author Mathias Petersson
    * @return Y-axis index number.
    * @type Number
    */
    this.addUnit = function(unit, redraw)
    {
        var options = _lib.isValidInputs();
        if(options)
        {
            options.redraw = (options.redraw !== undefined ? options.redraw : true);
            if(_memory.highchart)
            {
                var unitIndex;
                unitIndex = _this.getUnitIndex(options.unit);
                if(unitIndex !== false)
                {
                    return unitIndex;
                }
                unitIndex = _memory.units.length;
                _memory.units.push(
                {
                    chart :
                    {
                        labels :
                        {
                            formatter : function()
                            {
                                return _lib.numberFormat(_lib.o(
                                {
                                    decimals : 0,
                                    number : this.value
                                }));
                            }
                        },
                        opposite : (unitIndex ? true : false),
                        title :
                        {
                            text : options.unit
                        }
                    },
                    unit : options.unit
                });
                _this.settings.graph.yAxis.push(_memory.units[unitIndex].chart);
                if(options.redraw)
                {
                    _memory.highchart.destroy();
                    _memory.highchart = new Highcharts.Chart(_this.settings.graph);
                }
                return unitIndex;
            }
        }
    };
    this.addUnit.rules =
    {
        required : ['unit'],
        types :
        {
            redraw : ['boolean'],
            unit : ['string']
        }
    };

    /**
    * Clear graph from series and axes.
    *
    * @version 1.0 2010-09-21
    * @author Mathias Petersson
    */
    this.clear = function(name)
    {
        if(_memory.highchart)
        {
            _memory.highchart.destroy();
            _memory.highchart = null;
            _memory.series = {};
            _memory.units = [];
            _memory.categories = [];
        }
        if(_this.dom.legends)
        {
            _this.dom.legends.innerHTML = '';
        }
    };

    /**
    * Export chart as an image.
    *
    * @version 2011-03-16
    * @author Mathias Petersson
    */
    this.exportChart = function()
    {
        if(_this.exportEnabled())
        {
            var options, hsOptions, dim;
            dim = _lib.getDimensions(_this.dom.graph)
            options = {};
            hsOptions =
            {
                legend :
                {
                    enabled : true
                }
            };
            if(dim)
            {
                options.width = dim.width;
            }
            _memory.highchart.exportChart(options, hsOptions);
        }
    };

    /**
    * Checks if export client code is loaded.
    *
    * @version 2011-03-16
    * @author Mathias Petersson
    */
    this.exportEnabled = function()
    {
        return (_memory.highchart.exportChart ? true : false);
    };

    /**
    * Get legend DOM object.
    *
    * @param {String} id    Legend ID.
    *                       Required.
    * @version 1.0 2010-11-15
    * @author Mathias Petersson
    * @return Result.
    * @type Object
    */
    this.getLegend = function(id)
    {
        var options = _lib.isValidInputs();
        if(options)
        {
            var iUnit, mUnit, iLegend, mLegend;
            for(iUnit = 0, mUnit = _memory.legends.length; iUnit < mUnit; ++iUnit)
            {
                for(iLegend = 0, mLegend = _memory.legends[iUnit].legends.length; iLegend < mLegend; ++iLegend)
                {
                    if(_memory.legends[iUnit].legends[iLegend].id == options.id)
                    {
                        return _memory.legends[iUnit].legends[iLegend];
                    }
                }
            }
        }
        return false;
    };
    this.getLegend.rules =
    {
        required : ['id'],
        types :
        {
            id : ['string']
        }
    };

    /**
    * Get legend unit index.
    *
    * @param {String} unit  Unit.
    *                       Required.
    * @version 1.0 2010-11-15
    * @author Mathias Petersson
    * @return Result.
    * @type Object
    */
    this.getLegendUnitIndex = function(unit)
    {
        var options = _lib.isValidInputs();
        if(options)
        {
            var iUnit, mUnit;
            for(iUnit = 0, mUnit = _memory.legends.length; iUnit < mUnit; ++iUnit)
            {
                if(_memory.legends[iUnit].unit == options.unit)
                {
                    return iUnit;
                }
            }
        }
        return false;
    };
    this.getLegendUnitIndex.rules =
    {
        required : ['unit'],
        types :
        {
            unit : ['string']
        }
    };

    /**
    * Get Y-axis index key by it's unit
    *
    * @param {String} unit  Unit.
    *                       Required.
    * @version 1.0 2010-09-28
    * @author Mathias Petersson
    * @return Result.
    * @type Number
    */
    this.getUnitIndex = function(unit)
    {
        var options = _lib.isValidInputs();
        if(options)
        {
            var i, m;
            for(i = 0, m = _memory.units.length; i < m; ++i)
            {
                if(_memory.units[i].unit == options.unit)
                {
                    return i;
                    break;
                }
            }
        }
        return false;
    };
    this.getUnitIndex.rules =
    {
        required : ['unit'],
        types :
        {
            unit : ['string']
        }
    };

    /**
    * Hides the graph.
    *
    * @version 1.0 2010-09-24
    * @author Mathias Petersson
    */
    this.hide = function()
    {
        if(_this.dom.container)
        {
            _this.dom.container.style.display = 'none';
            _memory.visible = false;
        }
    };

    /**
    * Initiate chart object.
    *
    * @param {Object} dom   Module DOM elements.
    *                       Required.
    * @version 1.0 2010-09-21
    * @author Mathias Petersson
    */
    this.init = function(dom)
    {
        var options = _lib.isValidInputs();
        var errorMessage = 'Failed to initiate air_table.';
        if(options)
        {
            _this.dom.container = options.dom[_this.settings.domIndexes.container];
            if(_this.dom.container)
            {
                _lib.addClass(_this.dom.container, _this.settings.classNames.container);
                _this.initGraph('');
                return;
            }
            errorMessage += '<br />- missing some DOM elements';
        }
        _lib.error(errorMessage);
    };
    this.init.rules =
    {
        required : ['dom'],
        types :
        {
            dom : ['object']
        }
    };

    /**
    * Prepare the graph for a new set of series.
    *
    * @param {String} title Graph title.
    *                       Required.
    * @version 1.0 2010-11-10
    * @author Mathias Petersson
    */
    this.initGraph = function(title)
    {
        var options = _lib.isValidInputs();
        if(options)
        {
            var table;
            _this.clear();
            _this.settings = _lib.clone(_memory.defaultSettings);
            _this.settings.graph.title.text = options.title;
            if(_this.settings.graph.chart.renderTo === null)
            {
                _this.dom.container = (_this.dom.container ? _this.dom.container : document.createElement('DIV'));
                if(!_this.dom.graph)
                {
                    _this.dom.graph = document.createElement('DIV');
                    _this.dom.graph.className = _this.settings.classNames.graphContainer;
                    _this.dom.container.appendChild(_this.dom.graph);
                }
                _this.settings.graph.chart.renderTo = _this.dom.graph;
            }
            _memory.highchart = new Highcharts.Chart(_this.settings.graph);
            if(!_this.settings.graph.legend.enabled)
            {
                if(!_this.dom.legends)
                {
                    _this.dom.legends = document.createElement('DIV');
                    _this.dom.legends.className = _this.settings.classNames.legends;
                    _this.dom.container.appendChild(_this.dom.legends);
                }
                _this.dom.legends.innerHTML = '';
                _memory.legends = [];
            }
        }
    };
    this.initGraph.rules =
    {
        required : ['title'],
        types :
        {
            title : ['string']
        }
    };

    /**
    * Checks if requested index name is graphed.
    *
    * @param {String} name  Index name.
    *                       Required.
    * @version 1.0 2010-09-28
    * @author Mathias Petersson
    * @return Result.
    * @type Boolean
    */
    this.isGraphed = function(name)
    {
        var options = _lib.isValidInputs();
        if(options)
        {
            return (_memory.series[options.name.replace(/\W/g, '_')] !== undefined ? true : false);
        }
        return false;
    };
    this.isGraphed.rules =
    {
        required : ['name'],
        types :
        {
            name : ['string']
        }
    };


    /**
    * Link module pointers between modules.
    *
    * @param {Object} modules   Object containing modules.
    *                           Required.
    * @version 1.0 2010-09-21
    * @author Mathias Petersson
    */
    this.linkModules = function(modules)
    {
        var options = _lib.isValidInputs();
        if(options)
        {
            var x;
            for(x in modules)
            {
                if(modules[x].toString() != _this.toString())
                {
                    _modules[x] = modules[x];
                }
            }
        }
    };
    this.linkModules.rules =
    {
        required : ['modules'],
        types :
        {
            modules : ['object']
        }
    };

    /**
    * Move the graph from it's container to another.
    *
    * @param {Object} targetElement To where the graph is moved. DOM element or element ID.
    *                               Required.
    * @version 1.0 2010-09-22
    * @author Mathias Petersson
    */
    this.moveGraphElement = function(targetElement)
    {
        var options = _lib.isValidInputs();
        if(options)
        {
            if(_memory.highchart)
            {
                if(options._types.targetElement == 'string')
                {
                    if(!(options.targetElement = _lib.getDOM(options.targetElement)))
                    {
                        return;
                    }
                }
                options.targetElement.appendChild(_this.dom.container);
                _this.dom.container = options.targetElement;
            }
        }
    };
    this.moveGraphElement.rules =
    {
        required : ['targetElement'],
        types :
        {
            targetElement : ['dom', 'string']
        }
    };

    /**
    * Remove a data serie from graph.
    *
    * @param {String} id    Graph identifier.
    *                       Required.
    * @version 1.0 2010-11-15
    * @author Mathias Petersson
    */
    this.remove = function(id)
    {
        var options = _lib.isValidInputs();
        if(options)
        {
            var x, series = {}, unitIndex;
            if(_memory.highchart)
            {
                options.id = options.id.replace(/\W/g, '_');
                if(_memory.series[options.id])
                {
                    _memory.units = [];
                    _this.settings.graph.series = [];
                    _this.settings.graph.yAxis = [];
                    for(x in _memory.series)
                    {
                        if(x != options.id)
                        {
                            unitIndex = _this.addUnit(_memory.series[x].unit, false);
                            series[x] = _memory.series[x];
                            series[x].yAxis = unitIndex;
                            _this.settings.graph.series.push(series[x]);
                        }
                        else
                        {
                            _removeLegend(_memory.series[x].id, _memory.series[x].unit);
                        }
                    }
                    _memory.series = series;
                    _memory.highchart.destroy();
                    _memory.highchart = new Highcharts.Chart(_this.settings.graph);
                }
            }
        }
    };
    this.remove.rules =
    {
        required : ['id'],
        types :
        {
            id : ['string']
        }
    };

    /**
    * Set xAxis categories.
    *
    * @param {Array} categories     Categories.
    *                               Required.
    * @version 1.0 2010-09-28
    * @author Mathias Petersson
    */
    this.setCategories = function(categories)
    {
        var options = _lib.isValidInputs();
        if(options)
        {
            var i, m;
            for(i = 0, m = options.categories.length; i < m; ++i)
            {
                if(_this.settings.xLabelsSpacing && i % _this.settings.xLabelsSpacing != 0)
                {
                    options.categories[i] = ' ';
                }
            }
            if(_memory.highchart && _memory.categories != options.categories)
            {
                _memory.highchart.xAxis[0].setCategories(options.categories);
                _memory.categories = options.categories;
                _this.settings.graph.xAxis.categories = options.categories;
            }
        }
    };
    this.setCategories.rules =
    {
        required : ['categories'],
        types :
        {
            categories : ['array']
        }
    };

    /**
    * Make graph visible.
    *
    * @version 1.0 2010-09-24
    * @author Mathias Petersson
    */
    this.show = function()
    {
        if(_this.dom.container)
        {
            _this.dom.container.style.display = 'block';
            _memory.visible = true;
        }
    };

    // Initialize script //

    _init(settings);

    return arguments.callee.toString();
};
