/**
 * @license Highcharts JS v5.0.0 (2016-09-29)
 *
 * (c) 2009-2016 Torstein Honsi
 *
 * License: www.highcharts.com/license
 */
(function(factory) {
    if (typeof module === 'object' && module.exports) {
        module.exports = factory;
    } else {
        factory(Highcharts);
    }
}(function(Highcharts) {
    (function(H) {
        /**
         * (c) 2009-2016 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        'use strict';

        var defined = H.defined,
            isNumber = H.isNumber,
            inArray = H.inArray,
            isArray = H.isArray,
            merge = H.merge,
            Chart = H.Chart,
            extend = H.extend,
            each = H.each;

        var ALIGN_FACTOR,
            ALLOWED_SHAPES;

        ALLOWED_SHAPES = ['path', 'rect', 'circle'];

        ALIGN_FACTOR = {
            top: 0,
            left: 0,
            center: 0.5,
            middle: 0.5,
            bottom: 1,
            right: 1
        };

        function defaultOptions(shapeType) {
            var shapeOptions,
                options;

            options = {
                xAxis: 0,
                yAxis: 0,
                title: {
                    style: {},
                    text: '',
                    x: 0,
                    y: 0
                },
                shape: {
                    params: {
                        stroke: '#000000',
                        fill: 'transparent',
                        strokeWidth: 2
                    }
                }
            };

            shapeOptions = {
                circle: {
                    params: {
                        x: 0,
                        y: 0
                    }
                }
            };

            if (shapeOptions[shapeType]) {
                options.shape = merge(options.shape, shapeOptions[shapeType]);
            }

            return options;
        }

        function translatePath(d, xAxis, yAxis, xOffset, yOffset) {
            var len = d.length,
                i = 0;

            while (i < len) {
                if (isNumber(d[i]) && isNumber(d[i + 1])) {
                    d[i] = xAxis.toPixels(d[i]) - xOffset;
                    d[i + 1] = yAxis.toPixels(d[i + 1]) - yOffset;
                    i += 2;
                } else {
                    i += 1;
                }
            }

            return d;
        }


        // Define annotation prototype
        var Annotation = function() {
            this.init.apply(this, arguments);
        };
        Annotation.prototype = {
            /* 
             * Initialize the annotation
             */
            init: function(chart, options) {
                var shapeType = options.shape && options.shape.type;

                this.chart = chart;
                this.options = merge({}, defaultOptions(shapeType), options);
            },

            /*
             * Render the annotation
             */
            render: function(redraw) {
                var annotation = this,
                    chart = this.chart,
                    renderer = annotation.chart.renderer,
                    group = annotation.group,
                    title = annotation.title,
                    shape = annotation.shape,
                    options = annotation.options,
                    titleOptions = options.title,
                    shapeOptions = options.shape;

                if (!group) {
                    group = annotation.group = renderer.g();
                }


                if (!shape && shapeOptions && inArray(shapeOptions.type, ALLOWED_SHAPES) !== -1) {
                    shape = annotation.shape = renderer[options.shape.type](shapeOptions.params);
                    shape.add(group);
                }

                if (!title && titleOptions) {
                    title = annotation.title = renderer.label(titleOptions);
                    title.add(group);
                }

                group.add(chart.annotations.group);

                // link annotations to point or series
                annotation.linkObjects();

                if (redraw !== false) {
                    annotation.redraw();
                }
            },

            /*
             * Redraw the annotation title or shape after options update
             */
            redraw: function() {
                var options = this.options,
                    chart = this.chart,
                    group = this.group,
                    title = this.title,
                    shape = this.shape,
                    linkedTo = this.linkedObject,
                    xAxis = chart.xAxis[options.xAxis],
                    yAxis = chart.yAxis[options.yAxis],
                    width = options.width,
                    height = options.height,
                    anchorY = ALIGN_FACTOR[options.anchorY],
                    anchorX = ALIGN_FACTOR[options.anchorX],
                    shapeParams,
                    linkType,
                    series,
                    param,
                    bbox,
                    x,
                    y;

                if (linkedTo) {
                    linkType = (linkedTo instanceof H.Point) ? 'point' :
                        (linkedTo instanceof H.Series) ? 'series' : null;

                    if (linkType === 'point') {
                        options.xValue = linkedTo.x;
                        options.yValue = linkedTo.y;
                        series = linkedTo.series;
                    } else if (linkType === 'series') {
                        series = linkedTo;
                    }

                    if (group.visibility !== series.group.visibility) {
                        group.attr({
                            visibility: series.group.visibility
                        });
                    }
                }


                // Based on given options find annotation pixel position
                x = (defined(options.xValue) ? xAxis.toPixels(options.xValue + xAxis.minPointOffset) - xAxis.minPixelPadding : options.x);
                y = defined(options.yValue) ? yAxis.toPixels(options.yValue) : options.y;

                if (!isNumber(x) || !isNumber(y)) {
                    return;
                }


                if (title) {
                    title.attr(options.title);
                    title.css(options.title.style);
                }

                if (shape) {
                    shapeParams = extend({}, options.shape.params);

                    if (options.units === 'values') {
                        for (param in shapeParams) {
                            if (inArray(param, ['width', 'x']) > -1) {
                                shapeParams[param] = xAxis.translate(shapeParams[param]);
                            } else if (inArray(param, ['height', 'y']) > -1) {
                                shapeParams[param] = yAxis.translate(shapeParams[param]);
                            }
                        }

                        if (shapeParams.width) {
                            shapeParams.width -= xAxis.toPixels(0) - xAxis.left;
                        }

                        if (shapeParams.x) {
                            shapeParams.x += xAxis.minPixelPadding;
                        }

                        if (options.shape.type === 'path') {
                            translatePath(shapeParams.d, xAxis, yAxis, x, y);
                        }
                    }

                    // move the center of the circle to shape x/y
                    if (options.shape.type === 'circle') {
                        shapeParams.x += shapeParams.r;
                        shapeParams.y += shapeParams.r;
                    }

                    shape.attr(shapeParams);
                }

                group.bBox = null;

                // If annotation width or height is not defined in options use bounding box size
                if (!isNumber(width)) {
                    bbox = group.getBBox();
                    width = bbox.width;
                }

                if (!isNumber(height)) {
                    // get bbox only if it wasn't set before
                    if (!bbox) {
                        bbox = group.getBBox();
                    }

                    height = bbox.height;
                }

                // Calculate anchor point
                if (!isNumber(anchorX)) {
                    anchorX = ALIGN_FACTOR.center;
                }

                if (!isNumber(anchorY)) {
                    anchorY = ALIGN_FACTOR.center;
                }

                // Translate group according to its dimension and anchor point
                x = x - width * anchorX;
                y = y - height * anchorY;

                if (defined(group.translateX) && defined(group.translateY)) {
                    group.animate({
                        translateX: x,
                        translateY: y
                    });
                } else {
                    group.translate(x, y);
                }
            },

            /*
             * Destroy the annotation
             */
            destroy: function() {
                var annotation = this,
                    chart = this.chart,
                    allItems = chart.annotations.allItems,
                    index = allItems.indexOf(annotation);

                if (index > -1) {
                    allItems.splice(index, 1);
                }

                each(['title', 'shape', 'group'], function(element) {
                    if (annotation[element]) {
                        annotation[element].destroy();
                        annotation[element] = null;
                    }
                });

                annotation.group = annotation.title = annotation.shape = annotation.chart = annotation.options = null;
            },

            /*
             * Update the annotation with a given options
             */
            update: function(options, redraw) {
                extend(this.options, options);

                // update link to point or series
                this.linkObjects();

                this.render(redraw);
            },

            linkObjects: function() {
                var annotation = this,
                    chart = annotation.chart,
                    linkedTo = annotation.linkedObject,
                    linkedId = linkedTo && (linkedTo.id || linkedTo.options.id),
                    options = annotation.options,
                    id = options.linkedTo;

                if (!defined(id)) {
                    annotation.linkedObject = null;
                } else if (!defined(linkedTo) || id !== linkedId) {
                    annotation.linkedObject = chart.get(id);
                }
            }
        };


        // Add annotations methods to chart prototype
        extend(Chart.prototype, {
            annotations: {
                /*
                 * Unified method for adding annotations to the chart
                 */
                add: function(options, redraw) {
                    var annotations = this.allItems,
                        chart = this.chart,
                        item,
                        len;

                    if (!isArray(options)) {
                        options = [options];
                    }

                    len = options.length;

                    while (len--) {
                        item = new Annotation(chart, options[len]);
                        annotations.push(item);
                        item.render(redraw);
                    }
                },

                /**
                 * Redraw all annotations, method used in chart events
                 */
                redraw: function() {
                    each(this.allItems, function(annotation) {
                        annotation.redraw();
                    });
                }
            }
        });


        // Initialize on chart load
        Chart.prototype.callbacks.push(function(chart) {
            var options = chart.options.annotations,
                group;

            group = chart.renderer.g('annotations');
            group.attr({
                zIndex: 7
            });
            group.add();

            // initialize empty array for annotations
            chart.annotations.allItems = [];

            // link chart object to annotations
            chart.annotations.chart = chart;

            // link annotations group element to the chart
            chart.annotations.group = group;

            if (isArray(options) && options.length > 0) {
                chart.annotations.add(chart.options.annotations);
            }

            // update annotations after chart redraw
            H.addEvent(chart, 'redraw', function() {
                chart.annotations.redraw();
            });
        });

    }(Highcharts));
}));
