////////////////////////////////////////////////////////////////////////////////
//
//  Licensed to the Apache Software Foundation (ASF) under one or more
//  contributor license agreements.  See the NOTICE file distributed with
//  this work for additional information regarding copyright ownership.
//  The ASF licenses this file to You under the Apache License, Version 2.0
//  (the "License"); you may not use this file except in compliance with
//  the License.  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////

package mx.effects
{

import mx.effects.effectClasses.PropertyChanges;

/**
 *  The EffectTargetFilter class defines a custom filter that is executed 
 *  by each transition effect on each target of the effect. 
 *
 *  <p>The EffectTargetFilter class defines a
 *  <code>defaultFilterFunction()</code> method that uses the
 *  <code>filterProperties</code> and <code>filterStyles</code> properties
 *  to determine whether to play the effect on each effect target.</p>
 *  
 *  <p>You can also define a custom filter function
 *  to implement your own filtering logic.
 *  To do so, define your filter function, and then specify that function
 *  to an EffectTargetFilter object using the <code>filterFunction</code>
 *  property.</p>
 *  
 *  <p>To configure an effect to use a custom filter, you pass an 
 *  EffectTargetFilter object to the <code>Effect.customFilter</code> property 
 *  of the effect.</p>
 *  
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Flex 3
 */
public class EffectTargetFilter
{
    //include "../core/Version.as";

    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------

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

    /**
     *  A function that defines custom filter logic.
     *  Flex calls this method on every target of the effect.
     *  If the function returns <code>true</code>,
     *  the effect plays on the target;
     *  if it returns <code>false</code>, the target is skipped by the effect.
     *  A custom filter function gives you greater control over filtering
     *  than the <code>Effect.filter</code> property. 
     *
     *  <p>The filter function has the following signature:</p>
     *
     *  <pre>
     *  filterFunc(propChanges:Array, instanceTarget:Object):Boolean
     *  {
     *      // Return true to play the effect on instanceTarget, 
     *      // or false to not play the effect.
     *  } 
     *  </pre>
     *
     *  <p>where:</p>
     *  
     *  <p><code>propChanges</code> - An Array of PropertyChanges objects, 
     *  one object per target component of the effect.
     *  If a property of a target is not modified by the transition,
     *  it is not included in this Array.</p>
     *  
     *  <p><code>instanceTarget</code> - The specific target component
     *  of the effect that you want to filter.
     *  Within the custom filter function, you first search the
     *  <code>propChanges</code> Array for the PropertyChanges object
     *  that matches the <code>instanceTarget</code> argument
     *  by comparing the <code>instanceTarget</code> argument
     *  to the <code>propChanges.target</code> property.</p> 
     *
     *  @see mx.effects.effectClasses.PropertyChanges 
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public var filterFunction:Function = defaultFilterFunctionEx;
        
    //----------------------------------
    //  filterProperties
    //----------------------------------

    /** 
     *  An Array of Strings specifying component properties. 
     *  If any of the properties in the Array changed on the target component, 
     *  play the effect on the target. 
     *
     *  <p>If you define a custom filter function, you can examine the 
     *  <code>filterProperties</code> property from within your function.</p>
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public var filterProperties:Array = [];
    
    //----------------------------------
    //  filterStyles
    //----------------------------------

    /** 
     *  An Array of Strings specifying style properties. 
     *  If any of the style properties in the Array changed on the target component, 
     *  play the effect on the target. 
     *
     *  <p>If you define a custom filter function, you can examine the 
     *  <code>filterStyles</code> property from within your function.</p>
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public var filterStyles:Array = [];

    //----------------------------------
    //  requiredSemantics
    //----------------------------------

    /**
     *  A collection of properties and associated values which must be associated
     *  with a target for the effect to be played.
     *
     *  <p>When working with data effects, you can use this property to filter effects. 
     *  If you want to play a data effect on all targets of a list control 
     *  that are not added by the effect, meaning targets that is removed, replaced, moved, 
     *  or affected in any other way, you can write the effect definition as shown below: </p>
     *
     *  <pre>
     *  &lt;mx:Blur&gt;
     *      &lt;mx:customFilter&gt;
     *          &lt;mx:EffectTargetFilter requiredSemantics="{{'added':false}}"/&gt;
     *      &lt;/mx:customFilter&gt;
     *  &lt;/mx:Blur&gt; </pre>
     *
     *  <p>To play a data effect on all targets that are not added or not removed by the effect, 
     *  you can write the effect definition as shown below:</p>
     *
     *  <pre>
     *  &lt;mx:Blur&gt;
     *      &lt;mx:customFilter&gt;
     *          &lt;mx:EffectTargetFilter requiredSemantics="{{'added':false}, {'removed':false}}"/&gt;
     *      &lt;/mx:customFilter&gt;
     *  &lt;/mx:Blur&gt;</pre>
     *
     *  <p>The allowed list of properties that you can specify includes <code>added</code>, 
     *  <code>removed</code>, <code>replaced</code>, and <code>replacement</code>. 
     *  The allowed values for the properties are <code>true</code> and <code>false</code>.</p>
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public var requiredSemantics:Object = null;
    
    //--------------------------------------------------------------------------
    //
    //  Methods
    //
    //--------------------------------------------------------------------------

    /** 
     *  Determines whether a target should be filtered, returning true if it should be
     *  included in an effect. 
     *
     *  The determination is made by calling filterFunction and semanticFilterFunction,
     *  returning true if and only if both functions return true. The default functions
     *  with the default values will always return true.
     *
     *  Typically, an EffectTargetFilter will use one type of filter or the other, but
     *  not both.
     *
     *  @param propChanges An Array of PropertyChanges objects. The target property of 
     *  each PropertyChanges object is equal to the effect's target. If a property of 
     *  a target is not modified by a transition, the corresponding PropertyChanges 
     *  object is not included in this array.
     *
     *  @param semanticsProvider The IEffectTargetHost used to evaluate the properties 
     *  specified in requiredSemantics for the target, normally the effectTargetHost of 
     *  the effect. For item change effects, when the targets of the effect are item 
     *  renderers, this will be the List or TileList containing the item renderers.
     *
     *  @param target The target of the EffectInstance that calls this function. If an 
     *  effect has multiple targets, this function is called once per target.
     *
     *  @return Returna <code>true</code>, if the target should be included in the effect; 
     *  otherwise returns <code>false</code>.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function filterInstance(propChanges:Array, semanticsProvider:IEffectTargetHost, 
                                   target:Object):Boolean
    {
        if (filterFunction.length == 2)
            return filterFunction(propChanges, target);
        else
            return filterFunction(propChanges, semanticsProvider, target);
    }                                   

    /** 
     *  @private
     */
    protected function defaultFilterFunctionEx(propChanges:Array, semanticsProvider:IEffectTargetHost, 
                                   target:Object):Boolean
    {
        if (requiredSemantics)
        {
            for (var prop:String in requiredSemantics)
            {
                // seems dumb to check this every time
                // if necessary, we could do something with setters and getters
                if (!semanticsProvider)
                    return false;
                if (semanticsProvider.getRendererSemanticValue(target,prop) != requiredSemantics[prop])
                    return false;
            }

            // not clear this is the right thing to do here
            // the problem is that defaultFilterFunction returns false in 
            // some cases where we might expect it to return true
            return true;
        }
        // if semantic filtering has passed, do property change filtering
        return defaultFilterFunction(propChanges, target);
    }
                                       
    /**
     *  The default filter function for the EffectTargetFilter class. 
     *  If the <code>instanceTarget</code> has different start and end values
     *  for any of the values specified by the <code>filterProperties</code>
     *  or <code>filterStyles</code> properties, play the effect on the target.
     *
     *  @param propChanges An Array of PropertyChanges objects.
     *  The <code>target</code> property of each PropertyChanges object
     *  is equal to the effect's target. 
     *  If a property of a target is not modified by a transition, the 
     *  corresponding PropertyChanges 
     *  object is not included in this array.
     *  
     *  @param instanceTarget The target of the EffectInstance
     *  that calls this function.
     *  If an effect has multiple targets,
     *  this function is called once per target. 
     *
     *  @return Returns <code>true</code> to allow the effect instance to play. 
     *
     *  @see mx.effects.effectClasses.PropertyChanges 
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    protected function defaultFilterFunction(propChanges:Array,
                                             instanceTarget:Object):Boolean
    {
        var n:int = propChanges.length;
        for (var i:int = 0; i < n; i++)
        {
            var props:PropertyChanges = propChanges[i];
            if (props.target == instanceTarget)
            {
                var triggers:Array = filterProperties.concat(filterStyles);
                var m:int = triggers.length;
                for (var j:int = 0; j < m; j++)
                {
                    if (props.start[triggers[j]] !== undefined &&
                        props.end[triggers[j]] != props.start[triggers[j]])
                    {
                        return true;
                    }
                }
            }
        }
            
        return false;
    }
}

}
