////////////////////////////////////////////////////////////////////////////////
//
//  ADOBE SYSTEMS INCORPORATED
//  Copyright 2008 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.styles
{

import mx.core.mx_internal;

/**
 *  Represents a selector node in a potential chain of selectors used to match
 *  CSS style declarations to components.
 * 
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
public class CSSSelector
{
    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------

    /**
     *  Constructor.
     * 
     *  @param subject The plain representation of this selector without
     *  conditions or ancestors. This is typically a fully-qualified class name; for example,
     *  "spark.components.Button". You can use "*" to match all components or "global" for a global selector.
     *  
     *  @param conditions  An optional Array of objects of type CSSCondition that is used to match a
     *  subset of component instances. Currently only a single or a pair of
     *  conditions are supported.
     * 
     *  @param ancestor An optional selector to match on a component that
     *  descends from an arbitrary ancestor.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function CSSSelector(subject:String,
            conditions:Array=null, ancestor:CSSSelector=null)
    {
        _subject = subject;
        _conditions = conditions;
        _ancestor = ancestor;
    }

    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------

    //----------------------------------
    //  ancestor
    //----------------------------------

    /**
     *  @private
     */ 
    private var _ancestor:CSSSelector;

    /**
     *  If this selector is part of a descendant selector it may have a further
     *  selector defined for an arbitrary ancestor.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */ 
    public function get ancestor():CSSSelector
    {
        return _ancestor;
    }

    //----------------------------------
    //  conditions
    //----------------------------------

    /**
     *  @private
     */ 
    private var _conditions:Array; // of CSSCondition

    /**
     *  This selector may match a subset of components by specifying further
     *  conditions (for example, a matching component must have a particular id,
     *  styleName (equivalent to a 'class' condition in CSS) or state
     *  (equivalent to a 'pseudo' condition in CSS)).
     *  
     *  <p>If no conditions are specified, this property is null.</p>
     *  
     *  @return Array of CSSCondition specified for this selector.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get conditions():Array // of CSSCondition
    {
        return _conditions;
    }

    //----------------------------------
    //  specificity
    //----------------------------------

    /**
     *  Calculates the specificity of a selector chain in order to determine
     *  the precedence when applying several matching style declarations. Note
     *  that id conditions contribute 100 points, pseudo and class conditions
     *  each contribute 10 points, types (including descendants in a chain of
     *  selectors) contribute 1 point. Universal selectors ("*") contribute
     *  nothing. The result is the sum of these contributions. Selectors with a
     *  higher specificity override selectors of lower specificity. If
     *  selectors have equal specificity, the declaration order determines
     *  the precedence (the last one wins).
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get specificity():int
    {
        var s:int = 0;

        if ("*" != subject && "global" != subject && "" != subject)
            s = 1;

        if (conditions != null)
        {
            for each (var condition:CSSCondition in conditions)
            {
                s += condition.specificity;
            }
        }

        if (ancestor != null)
            s += ancestor.specificity;

        return s;
    }

    //----------------------------------
    //  subject
    //----------------------------------

    /**
     *  @private
     */ 
    private var _subject:String;

    /**
     *  The subject of this selector node (only). To get a String representation
     *  of all conditions and descendants of this selector call the <code>toString()</code>
     *  method.
     * 
     *  <p>If this selector represents the root node of a potential chain of
     *  selectors, the subject also represents the subject of the entire selector
     *  expression.</p>
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */ 
    public function get subject():String
    {
        return _subject;
    }

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

    /**
     *  Determines whether this selector matches the given component.
     * 
     *  @param object The component to which the selector may apply.
     *  @return true if component is a match, or false if not. 
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */ 
    public function matchesStyleClient(object:IAdvancedStyleClient):Boolean
    {
        var match:Boolean = false;
        var condition:CSSCondition = null;

        // If we have an ancestor then this is part of a descendant selector
        if (ancestor)
        {
            if (conditions)
            {
                // First, test if the conditions match
                for each (condition in conditions)
                {
                    match = condition.matchesStyleClient(object);
                    if (!match)
                        return false;
                }
            }

            // Then reset and test if any ancestor matches
            match = false;
            var parent:IAdvancedStyleClient = object.styleParent;
            while (parent != null)
            {
                if (parent.matchesCSSType(ancestor.subject)
                        || "*" == ancestor.subject)
                {
                    match = ancestor.matchesStyleClient(parent);
                    if (match)
                        break;
                }
                parent = parent.styleParent;
            }
        }
        else
        {
            // Check the type selector matches
            if (subject == "*" || subject == "" || object.matchesCSSType(subject))
            {
                match = true;
            }

            // Then check if any conditions match 
            if (match && conditions != null)
            {
                for each (condition in conditions)
                {
                    match = condition.matchesStyleClient(object);
                    if (!match)
                        return false;
                }
            }
        }

        return match;
    }

    /**
     *  @private
     */ 
    mx_internal function getPseudoCondition():String
    {
        var result:String = null;

        if (conditions)
        {
            for each (var condition:CSSCondition in conditions)
            {
                if (condition.kind == CSSConditionKind.PSEUDO)
                {
                    result = condition.value;
                    break;
                }
            }
        }

        return result;
    }

    /**
     *  Returns a String representation of this selector.
     *  
     *  @return A String representation of this selector including all of its
     *  syntax, conditions and ancestors.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */ 
    public function toString():String
    {
        var s:String;

        if (ancestor != null)
        {
            s = ancestor.toString() + " " + subject;
        }
        else
        {
            s = subject;
        }

        if (conditions != null)
        {
            for each (var condition:CSSCondition in conditions)
            {
                s += condition.toString();
            }
        }

        return s; 
    }
}

}
