/*
 * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
 * http://paperjs.org/
 *
 * Copyright (c) 2011 - 2014, Juerg Lehni & Jonathan Puckey
 * http://scratchdisk.com/ & http://jonathanpuckey.com/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

/**
 * @name Gradient
 *
 * @class The Gradient object.
 *
 * @classexample {@paperscript height=300}
 * // Applying a linear gradient color containing evenly distributed
 * // color stops:
 *
 * // Define two points which we will be using to construct
 * // the path and to position the gradient color:
 * var topLeft = view.center - [80, 80];
 * var bottomRight = view.center + [80, 80];
 *
 * // Create a rectangle shaped path between
 * // the topLeft and bottomRight points:
 * var path = new Path.Rectangle({
 *     topLeft: topLeft,
 *     bottomRight: bottomRight,
 *     // Fill the path with a gradient of three color stops
 *     // that runs between the two points we defined earlier:
 *     fillColor: {
 *         gradient: {
 *             stops: ['yellow', 'red', 'blue']
 *         },
 *         origin: topLeft,
 *         destination: bottomRight
 *     }
 * });
 *
 * @classexample {@paperscript height=300}
 * // Create a circle shaped path at the center of the view,
 * // using 40% of the height of the view as its radius
 * // and fill it with a radial gradient color:
 * var path = new Path.Circle({
 *     center: view.center,
 *     radius: view.bounds.height * 0.4
 * });
 *
 * // Fill the path with a radial gradient color with three stops:
 * // yellow from 0% to 5%, mix between red from 5% to 20%,
 * // mix between red and black from 20% to 100%:
 * path.fillColor = {
 *     gradient: {
 *         stops: [['yellow', 0.05], ['red', 0.2], ['black', 1]],
 *         radial: true
 *     },
 *     origin: path.position,
 *     destination: path.bounds.rightCenter
 * };
 */
var Gradient = Base.extend(/** @lends Gradient# */{
    _class: 'Gradient',

    // DOCS: Document #initialize()
    initialize: function Gradient(stops, radial) {
        // Define this Gradient's unique id.
        this._id = Gradient._id = (Gradient._id || 0) + 1;
        if (stops && this._set(stops))
            stops = radial = null;
        if (!this._stops)
            this.setStops(stops || ['white', 'black']);
        if (this._radial == null)
            // Support old string type argument and new radial boolean.
            this.setRadial(typeof radial === 'string' && radial === 'radial'
                    || radial || false);
    },

    _serialize: function(options, dictionary) {
        return dictionary.add(this, function() {
            return Base.serialize([this._stops, this._radial],
                    options, true, dictionary);
        });
    },

    /**
     * Called by various setters whenever a gradient value changes
     */
    _changed: function() {
        // Loop through the gradient-colors that use this gradient and notify
        // them, so they can notify the items they belong to.
        for (var i = 0, l = this._owners && this._owners.length; i < l; i++)
            this._owners[i]._changed();
    },

    /**
     * Called by Color#setGradient()
     * This is required to pass on _changed() notifications to the _owners.
     */
    _addOwner: function(color) {
        if (!this._owners)
            this._owners = [];
        this._owners.push(color);
    },

    /**
     * Called by Color whenever this gradient stops being used.
     */
    _removeOwner: function(color) {
        var index = this._owners ? this._owners.indexOf(color) : -1;
        if (index != -1) {
            this._owners.splice(index, 1);
            if (this._owners.length === 0)
                this._owners = undefined;
        }
    },

    /**
     * @return {Gradient} a copy of the gradient
     */
    clone: function() {
        var stops = [];
        for (var i = 0, l = this._stops.length; i < l; i++)
            stops[i] = this._stops[i].clone();
        return new Gradient(stops);
    },

    /**
     * The gradient stops on the gradient ramp.
     *
     * @type GradientStop[]
     * @bean
     */
    getStops: function() {
        return this._stops;
    },

    setStops: function(stops) {
        // If this gradient already contains stops, first remove
        // this gradient as their owner.
        if (this.stops) {
            for (var i = 0, l = this._stops.length; i < l; i++)
                this._stops[i]._owner = undefined;
        }
        if (stops.length < 2)
            throw new Error(
                    'Gradient stop list needs to contain at least two stops.');
        this._stops = GradientStop.readAll(stops, 0, { clone: true });
        // Now reassign ramp points if they were not specified.
        for (var i = 0, l = this._stops.length; i < l; i++) {
            var stop = this._stops[i];
            stop._owner = this;
            if (stop._defaultRamp)
                stop.setRampPoint(i / (l - 1));
        }
        this._changed();
    },

    /**
     * Specifies whether the gradient is radial or linear.
     *
     * @type Boolean
     * @bean
     */
    getRadial: function() {
        return this._radial;
    },

    setRadial: function(radial) {
        this._radial = radial;
        this._changed();
    },

    /**
     * Checks whether the gradient is equal to the supplied gradient.
     *
     * @param {Gradient} gradient
     * @return {Boolean} {@true if they are equal}
     */
    equals: function(gradient) {
        if (gradient === this)
            return true;
        if (gradient && this._class === gradient._class
                && this._stops.length === gradient._stops.length) {
            for (var i = 0, l = this._stops.length; i < l; i++) {
                if (!this._stops[i].equals(gradient._stops[i]))
                    return false;
            }
            return true;
        }
        return false;
    }
});
