////////////////////////////////////////////////////////////////////////////////
//
//  ADOBE SYSTEMS INCORPORATED
//  Copyright 2009 Adobe Systems Incorporated
//  All Rights Reserved.
//
//  NOTICE: Adobe permits you to use, modify, and distribute this file
//  in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////

package mx.charts.chartClasses
{

import flash.display.DisplayObject;
import flash.display.Sprite;
import flash.geom.Rectangle;
import mx.collections.ArrayCollection;
import mx.collections.ICollectionView;
import mx.collections.IList;
import mx.collections.IViewCursor;
import mx.collections.ListCollectionView;
import mx.collections.XMLListCollection;
import mx.core.mx_internal;
import mx.events.CollectionEvent;
import mx.events.CollectionEventKind;
import mx.charts.chartClasses.ChartBase;
import flash.geom.Point;

use namespace mx_internal;
/**
 *  The ChartElement class is the base class for visual chart elements.
 *  
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Flex 3
 */
public class ChartElement extends DualStyleObject implements IChartElement2
{
    include "../../core/Version.as";

    //--------------------------------------------------------------------------
    //
    //  Class variables
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private
     */
    private static var nextID:uint = 0;

    //--------------------------------------------------------------------------
    //
    //  Class methods
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private
     */
    private static function generateGlyphID():uint
    {
        return nextID++;
    }

    //--------------------------------------------------------------------------
    //
    //  Constructor 
    //
    //--------------------------------------------------------------------------
    
    /**
     *  Constructor.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function ChartElement()
    {
        super();

        _glyphID = generateGlyphID();
    }

    //--------------------------------------------------------------------------
    //
    //  Variables
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private
     */
    private var _userDataProvider:Object;
    
    /**
     *  @private
     */
    private var _glyphID:uint;
    
    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------
    
    //----------------------------------
    //  chart
    //----------------------------------

    /**
     *  Refers to the chart component containing this element.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */ 
    protected function get chart():ChartBase
    {
        var p:DisplayObject = parent;

        while (!(p is ChartBase) && p)
        {
            p = p.parent;
        }   
                
        return p as ChartBase;
    }

    //----------------------------------
    //  chartDataProvider
    //----------------------------------

    [Inspectable(environment="none")]

    /**
     *  The data provider assigned to the enclosing chart.
     *  Element types can choose to inherit the data provider
     *  from the enclosing chart if necessary, or allow developers
     *  to assign data providers specifically to the element.
     *  Not all elements are necessarily driven by a data provider.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function set chartDataProvider(value:Object):void
    {
        if (_userDataProvider == null)
            processNewDataProvider(value);
    }
    
    //----------------------------------
    //  cursor
    //----------------------------------

    /**
     *  Each ChartElement carries a cursor associated with their dataProvider
     *  for their own internal use.
     *  ChartElements have sole ownership of this cursor;
     *  they can assume no other code will modify its position.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    protected var cursor:IViewCursor;

    //----------------------------------
    //  dataProvider
    //----------------------------------

    /**
     *  @private
     */
    private var _dataProvider:ICollectionView;
    
    [Inspectable]

    /**
     *  A data provider assigned to the this specific element.
     *  In general, elements inherit the dataProvider from the enclosing chart.
     *  But individual elements can override with a specific dataProvider
     *  of their own.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get dataProvider():Object
    {
        return _dataProvider;
    }

    /**
     *  @private
     */
    public function set dataProvider(value:Object):void
    {
        if(_userDataProvider == value)
            return;
        _userDataProvider = value;
        processNewDataProvider(value);
    }

    //----------------------------------
    //  dataTransform
    //----------------------------------

    /**
     *  @private
     */
    private var _dataTransform:DataTransform;

    [Inspectable(environment="none")]
    
    /**
     *  The DataTransform object that the element is associated with.
     *  A DataTransform object represents an association between a set of elements
     *  and a set of axis objects used to transform those elements
     *  from data space to screen coordinates and back.
     *  A chart element uses its associated DataTransform object to calculate
     *  how to render its data.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get dataTransform():DataTransform
    {
        return _dataTransform;
    }

    /**
     *  @private
     */
    public function set dataTransform(value:DataTransform):void
    {
        _dataTransform = value;
        var n:int = numChildren;
        for (var i:uint = 0; i < n; i++)
        {
            var g:DisplayObject = getChildAt(i);
            if (_dataTransform && g is IChartElement)
                IChartElement(g).dataTransform = _dataTransform;
        }

        if (_dataTransform)
            _dataTransform.dataChanged();

        invalidateDisplayList();
    }

    //----------------------------------
    //  labelContainer
    //----------------------------------

    [Inspectable(environment="none")]
    
    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get labelContainer():Sprite 
    {
        return null;
    }

    //--------------------------------------------------------------------------
    //
    //  Overridden methods: UIComponent
    //
    //--------------------------------------------------------------------------

    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    override public function addChild(child:DisplayObject):DisplayObject
    {
        super.addChild(child);
        
        if (_dataTransform && child is IChartElement)
            IChartElement(child).dataTransform = _dataTransform;

        return child;
    }

    /**
     * Adds a child DisplayObject instance to this DisplayObjectContainer 
     * instance. The child is added
     * at the index position specified. An index of 0 represents the back (bottom) 
     * of the display list for this DisplayObjectContainer object.
     * 
     * <p>If you add a child object that already has a different display object container as
     * a parent, the object is removed from the child list of the other display object container. </p>
     *
     * @return The DisplayObject instance that you pass in the 
     * <code>child</code> parameter.
     *
     * @param child The DisplayObject instance to add as a child of this 
     * DisplayObjectContainer instance.
     * 
     * @param index The index position to which the child is added. If you specify a 
     * currently occupied index position, the child object that exists at that position and all
     * higher positions are moved up one position in the child list. 
         *
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    override public function addChildAt(child:DisplayObject,
                                        index:int):DisplayObject
    {
        super.addChildAt(child, index);
        
        if (_dataTransform && child is IChartElement)
            IChartElement(child).dataTransform = _dataTransform;

        return child;
    }

    //--------------------------------------------------------------------------
    //
    //  Methods
    //
    //--------------------------------------------------------------------------

    /** 
     *  Called when a new dataProvider is assigned to the element.
     *  Subclasses can override and define custom behavior
     *  when a new dataProvider is assigned.
     *  This method is called when either the <code>dataProvider</code> property
     *  is set, or when the <code>chartDataProvider</code> property is set
     *  if no specific dataProvider has been assigned directly.
     *  
     *  @param value The data provider. This is typically an Array, ArrayCollection, XMLList, XMLListCollection,
     *  or similar class.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    protected function processNewDataProvider(value:Object):void
    {
        if (_dataProvider != null)
        {
            _dataProvider.removeEventListener(
                CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler);
        }

        if (value is Array)
        {
            value = new ArrayCollection(value as Array);
        }
        else if (value is ICollectionView)
        {
        }
        else if (value is IList)
        {
            value = new ListCollectionView(IList(value));
        }
        else if (value is XMLList)
        {
            value = new XMLListCollection(XMLList(value));
        }
        else if (value != null)
        {
            value = new ArrayCollection([ value ]);
        }
        else
        {
            value = new ArrayCollection();
        }

        _dataProvider = ICollectionView(value);

        cursor = _dataProvider.createCursor();

        if (_dataProvider != null) //[Matt] not sure this would ever be null
        {
            // Weak listeners to collections and dataproviders.
            _dataProvider.addEventListener(CollectionEvent.COLLECTION_CHANGE,
                collectionChangeHandler, false, 0, true);
        }

        dataChanged();
    }

    /**
     *  Called when the mapping of one or more associated axes changes.
     *  The DataTransform assigned to this ChartElement will call this method
     *  when any of the axes it represents is modified in some way.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function mappingChanged():void
    {
    }

    /**
     *  Indicates the underlying data represented by the element has changed.
     *  You should call this method whenever the data your series or element
     *  is displaying changes.  
     *  It gives any associated axes a chance to update their ranges
     *  if appropriate.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    protected function dataChanged():void
    {
        invalidateDisplayList();
        if (_dataTransform)
            _dataTransform.dataChanged();
    }

    /** 
     *  Finds the closest data point represented by the element
     *  under the given coordinates.
     *
     *  <p>This method returns either an array of  HitData structures
     *  describing the datapoints within range.
     *  Individual ChartElements may choose to only return a single dataPoint
     *  if their dataPoints are guaranteed not to overlap.</p>
     *
     *  @param x The X coordinate.
     *
     *  @param y The Y coordinate.
     *
     *  @param sensitivity2 How close, in pixels, the pointer must be
     *  to the exact coordinates to be considered "under".
     *  This property is similar to the value of the mouseSensitivity
     *  property of the chart control.
     * 
     *  @return An array of HitData.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function findDataPoints(x:Number, y:Number,
                                   sensitivity2:Number):Array /* of HitData */
    {
        var result:Array /* of HitData */ = [];
        var n:int = numChildren;
        for (var i:int = n - 1; i >= 0; i--)
        {
            var g:IChartElement = getChildAt(i) as IChartElement;
            if (!g || g.visible == false)
                continue;
                
            var hds:Array /* of HitData */ = g.findDataPoints(x, y, sensitivity2);
            if (hds.length == 0)
                continue;

            result = result.concat(hds);
        }   
        
        return result;
    }
    
    /**
     *  Returns an array of HitData of the items of all the underlying 
     *  ChartElements whose dataTips are to be shown when 
     *  <code>showAllDataTips</code> is set to <code>true</code> on 
     *  chart
     * 
     *  @return The HitData objects describing the data points.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function getAllDataPoints():Array /* of HitData */
    {
        var result:Array /* of HitData */ = [];
        var n:int = numChildren;
        for (var i:int = n - 1; i >= 0; i--)
        {
            var g:IChartElement2 = getChildAt(i) as IChartElement2;
            if (!g || g.visible == false)
                continue;
                
            var hds:Array /* of HitData */ = g.getAllDataPoints();
            if (hds.length == 0)
                continue;

            result = result.concat(hds);
        }   
        
        return result;
    }
    
    /**
     *  Creates a unique id to represent the dataPoint
     *  for comparison purposes.
     *  Derived classes can call this function with a locally unique
     *  data point ID to generate an ID that is unique across the application.
     *  
     *  @param dataPointID The dataPoint's current ID.
     *  
     *  @return An ID for the dataPoint that is unique across the application.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    protected function createDataID(dataPointID:Number):Number
    {
        return (_glyphID << 16) + dataPointID;
    }

    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function describeData(dimension:String,
                                 requiredFields:uint):Array /* of BoundedValue */
    {
        return [];
    }
    
    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function chartStateChanged(oldState:uint, v:uint):void
    {
        var n:int = numChildren;
        for (var i:int = 0; i < n; i++)
        {
            var c:IChartElement = getChildAt(i) as IChartElement;
            if (c)
                c.chartStateChanged(oldState, v);
        }
    }

    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function collectTransitions(chartState:Number,
                                       transitions:Array /* of IEffectInstance */):void
    {
        var n:int = numChildren;
        for (var i:int = 0; i < n; i++)
        {
            var c:IChartElement = getChildAt(i) as IChartElement;
            if (c)
                c.collectTransitions(chartState, transitions);
        }
    }

    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function claimStyles(styles:Array /* of Object */, firstAvailable:uint):uint
    {
        return firstAvailable;
    }
    
    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function dataToLocal(... dataValues):Point
    {
        return null;
    }
    
    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function localToData(pt:Point):Array /* of Object */
    {
        return null;
    }

    //--------------------------------------------------------------------------
    //
    //  Event handlers
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    private function collectionChangeHandler(event:CollectionEvent = null):void
    {
        if (event && event.kind == CollectionEventKind.RESET)
            cursor = _dataProvider.createCursor();
    
        dataChanged();
    }
}

}
