/*
 *
 *  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 org.apache.flex.compiler.fxg.swf;

import java.awt.geom.Ellipse2D;
import java.awt.geom.PathIterator;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import org.apache.flex.compiler.definitions.ITypeDefinition;
import org.apache.flex.compiler.fxg.FXGConstants;
import org.apache.flex.compiler.fxg.IFXGTranscoder;
import org.apache.flex.compiler.fxg.dom.IFXGNode;
import org.apache.flex.compiler.fxg.flex.FXGSymbolClass;
import org.apache.flex.compiler.fxg.resources.IFXGResourceResolver;
import org.apache.flex.compiler.internal.fxg.dom.AbstractShapeNode;
import org.apache.flex.compiler.internal.fxg.dom.BitmapGraphicNode;
import org.apache.flex.compiler.internal.fxg.dom.DefinitionNode;
import org.apache.flex.compiler.internal.fxg.dom.EllipseNode;
import org.apache.flex.compiler.internal.fxg.dom.IFillNode;
import org.apache.flex.compiler.internal.fxg.dom.IFilterNode;
import org.apache.flex.compiler.internal.fxg.dom.GradientEntryNode;
import org.apache.flex.compiler.internal.fxg.dom.GraphicContentNode;
import org.apache.flex.compiler.internal.fxg.dom.GraphicContext;
import org.apache.flex.compiler.internal.fxg.dom.GraphicNode;
import org.apache.flex.compiler.internal.fxg.dom.GroupDefinitionNode;
import org.apache.flex.compiler.internal.fxg.dom.GroupNode;
import org.apache.flex.compiler.internal.fxg.dom.LineNode;
import org.apache.flex.compiler.internal.fxg.dom.IMaskableNode;
import org.apache.flex.compiler.internal.fxg.dom.IMaskingNode;
import org.apache.flex.compiler.internal.fxg.dom.PathNode;
import org.apache.flex.compiler.internal.fxg.dom.PlaceObjectNode;
import org.apache.flex.compiler.internal.fxg.dom.RectNode;
import org.apache.flex.compiler.internal.fxg.dom.RichTextNode;
import org.apache.flex.compiler.internal.fxg.dom.IStrokeNode;
import org.apache.flex.compiler.internal.fxg.dom.TextGraphicNode;
import org.apache.flex.compiler.internal.fxg.dom.fills.BitmapFillNode;
import org.apache.flex.compiler.internal.fxg.dom.fills.LinearGradientFillNode;
import org.apache.flex.compiler.internal.fxg.dom.fills.RadialGradientFillNode;
import org.apache.flex.compiler.internal.fxg.dom.fills.SolidColorFillNode;
import org.apache.flex.compiler.internal.fxg.dom.filters.BevelFilterNode;
import org.apache.flex.compiler.internal.fxg.dom.filters.BlurFilterNode;
import org.apache.flex.compiler.internal.fxg.dom.filters.ColorMatrixFilterNode;
import org.apache.flex.compiler.internal.fxg.dom.filters.DropShadowFilterNode;
import org.apache.flex.compiler.internal.fxg.dom.filters.GlowFilterNode;
import org.apache.flex.compiler.internal.fxg.dom.filters.GradientBevelFilterNode;
import org.apache.flex.compiler.internal.fxg.dom.filters.GradientGlowFilterNode;
import org.apache.flex.compiler.internal.fxg.dom.strokes.AbstractStrokeNode;
import org.apache.flex.compiler.internal.fxg.dom.strokes.LinearGradientStrokeNode;
import org.apache.flex.compiler.internal.fxg.dom.strokes.RadialGradientStrokeNode;
import org.apache.flex.compiler.internal.fxg.dom.strokes.SolidColorStrokeNode;
import org.apache.flex.compiler.internal.fxg.dom.transforms.ColorTransformNode;
import org.apache.flex.compiler.internal.fxg.dom.transforms.MatrixNode;
import org.apache.flex.compiler.internal.fxg.dom.types.BevelType;
import org.apache.flex.compiler.internal.fxg.dom.types.BlendMode;
import org.apache.flex.compiler.internal.fxg.dom.types.Caps;
import org.apache.flex.compiler.internal.fxg.dom.types.InterpolationMethod;
import org.apache.flex.compiler.internal.fxg.dom.types.Joints;
import org.apache.flex.compiler.internal.fxg.dom.types.MaskType;
import org.apache.flex.compiler.internal.fxg.dom.types.ScaleMode;
import org.apache.flex.compiler.internal.fxg.dom.types.ScalingGrid;
import org.apache.flex.compiler.internal.fxg.dom.types.SpreadMethod;
import org.apache.flex.compiler.internal.fxg.dom.types.Winding;
import org.apache.flex.compiler.internal.fxg.swf.DefineImage;
import org.apache.flex.compiler.internal.fxg.swf.ImageHelper;
import org.apache.flex.compiler.internal.fxg.swf.ShapeHelper;
import org.apache.flex.compiler.internal.fxg.swf.TypeHelper;
import org.apache.flex.compiler.internal.fxg.types.FXGMatrix;
import org.apache.flex.compiler.problems.FXGErrorEmbeddingImageProblem;
import org.apache.flex.compiler.problems.FXGMissingAttributeProblem;
import org.apache.flex.compiler.problems.FXGMissingGroupChildNodeProblem;
import org.apache.flex.compiler.problems.ICompilerProblem;
import org.apache.flex.swf.ISWFConstants;
import org.apache.flex.swf.builders.IShapeIterator;
import org.apache.flex.swf.builders.ShapeBuilder;
import org.apache.flex.swf.tags.DefineScalingGridTag;
import org.apache.flex.swf.tags.DefineShape4Tag;
import org.apache.flex.swf.tags.DefineShapeTag;
import org.apache.flex.swf.tags.DefineSpriteTag;
import org.apache.flex.swf.tags.ICharacterTag;
import org.apache.flex.swf.tags.ITag;
import org.apache.flex.swf.tags.PlaceObject3Tag;
import org.apache.flex.swf.types.BevelFilter;
import org.apache.flex.swf.types.BlurFilter;
import org.apache.flex.swf.types.CXFormWithAlpha;
import org.apache.flex.swf.types.DropShadowFilter;
import org.apache.flex.swf.types.FillStyle;
import org.apache.flex.swf.types.FillStyleArray;
import org.apache.flex.swf.types.Filter;
import org.apache.flex.swf.types.FocalGradient;
import org.apache.flex.swf.types.GlowFilter;
import org.apache.flex.swf.types.GradRecord;
import org.apache.flex.swf.types.Gradient;
import org.apache.flex.swf.types.GradientBevelFilter;
import org.apache.flex.swf.types.GradientGlowFilter;
import org.apache.flex.swf.types.LineStyle;
import org.apache.flex.swf.types.LineStyle2;
import org.apache.flex.swf.types.LineStyleArray;
import org.apache.flex.swf.types.Matrix;
import org.apache.flex.swf.types.RGB;
import org.apache.flex.swf.types.RGBA;
import org.apache.flex.swf.types.Rect;
import org.apache.flex.swf.types.Shape;
import org.apache.flex.swf.types.ShapeRecord;
import org.apache.flex.swf.types.ShapeWithStyle;
import org.apache.flex.swf.types.Styles;

/**
 * Transcodes an FXG DOM into a tree of SWF DefineSpriteTags which use SWF graphics
 * primitives to draw the document.
 * Note that in this implementation, since FTE based text
 * has no equivalent in SWF tags, text nodes are ignored.
 */
public class FXG2SWFTranscoder implements IFXGTranscoder
{
    protected FXGSymbolClass graphicClass;
    protected HashMap<String, DefineSpriteTag> definitions;
    protected Stack<DefineSpriteTag> spriteStack;
    protected Map<DefineSpriteTag, Integer> depthMap;
    protected IFXGResourceResolver resourceResolver;
    protected Map<ITag, ITag> extraTags;
    protected Map<String, DefineImage> imageMap;
    protected Collection<ICompilerProblem> problems;
    
    public FXG2SWFTranscoder newInstance()
    {
        FXG2SWFTranscoder transcoder = new FXG2SWFTranscoder();;
        transcoder.extraTags = extraTags;
        transcoder.imageMap = imageMap;
        transcoder.depthMap = depthMap;
        return transcoder;
    }
    
    @Override
    public void setResourceResolver(IFXGResourceResolver resolver)
    {
        resourceResolver = resolver;
    }

    protected int getSpriteDepth(DefineSpriteTag sprite)
    {
        if(depthMap.containsKey(sprite))
            return depthMap.get(sprite);
        else
            depthMap.put(sprite, 0);
            return 0;
    }
    
    private void setSpriteDepth(DefineSpriteTag sprite, Integer depth)
    {
        depthMap.put(sprite, depth);
    }
    
    @Override
    public FXGSymbolClass transcode(IFXGNode fxgNode, String packageName, String className, Map<ITag, ITag> extraTags, Collection<ICompilerProblem> problems)
    {
        this.problems = problems;
        this.extraTags = extraTags;
        graphicClass = new FXGSymbolClass();
        graphicClass.setPackageName(packageName);
        graphicClass.setClassName(className);
        
        GraphicNode node = (GraphicNode)fxgNode;
        DefineSpriteTag sprite = createDefineSpriteTag("Graphic");
        spriteStack.push(sprite);
        
        // Process mask (if present)
        if (node.mask != null)
            mask(node, sprite);

        // Handle 'scale 9' grid definition
        if (node.definesScaleGrid())
        {
            DefineScalingGridTag grid = createDefineScalingGridTag(node.getScalingGrid());
            grid.setCharacter(sprite);
            extraTags.put(sprite, grid);
            //sprite.scalingGrid = grid;
        }

        // Process child nodes
        if (node.children != null)
            graphicContentNodes(node.children);

        spriteStack.pop();
        
        graphicClass.setSymbol(sprite);
        return graphicClass;
    }

    public FXG2SWFTranscoder()
    {
        spriteStack = new Stack<DefineSpriteTag>();
        depthMap = new HashMap<DefineSpriteTag, Integer>();
        imageMap = new HashMap<String, DefineImage>();
    }
    

    private PlaceObject3Tag bitmapWithClip(DefineImage defImage, BitmapGraphicNode node)
    {
        GraphicContext context = node.createGraphicContext();

        //process the filters later to avoid masking
        List<IFilterNode> filters = null;
        if (context.filters != null)       
        {   
            filters = context.filters;
            context.filters = null;
            DefineSpriteTag filterSprite = createDefineSpriteTag("MaskFilter");
            spriteStack.push(filterSprite);
        }
        
        DefineSpriteTag imageSprite = createDefineSpriteTag("BitmapGraphic");
        spriteStack.push(imageSprite);
        
        // First, generate the clipping mask
        DefineSpriteTag clipSprite = createDefineSpriteTag("BitmapGraphic_Clip");
        spriteStack.push(clipSprite);
        
        double width = (defImage.getWidth() < node.width) ? defImage.getWidth() : node.width;
        double height = (defImage.getHeight() < node.height) ? defImage.getHeight() : node.height;
        List<ShapeRecord> shapeRecords = ShapeHelper.rectangle(0.0, 0.0, width, height);
        DefineShapeTag clipShape = createDefineShapeTag(null, shapeRecords, new SolidColorFillNode(), null, context.getTransform());
        PlaceObject3Tag(clipShape, new GraphicContext());
        spriteStack.pop();
        
        //place the clipping mask in the imageSprite
        PlaceObject3Tag po3clip = PlaceObject3Tag(clipSprite, context);
        po3clip.setClipDepth(po3clip.getDepth()+1); 
        po3clip.setHasClipDepth(true);
        // Then, process the image
        DefineShapeTag imageShape = ImageHelper.createShapeForImage(defImage, node);
        PlaceObject3Tag(imageShape, context);        
        spriteStack.pop();
        
        PlaceObject3Tag po3 = PlaceObject3Tag(imageSprite, new GraphicContext());
        
        // If filters were not processed, place the topmost sprite in display list and apply filters 
        // This is done to force processing of masks before filters
        if (filters != null)
        {
            DefineSpriteTag sprite = spriteStack.pop();
            GraphicContext gc = new GraphicContext();
            gc.filters = filters;
            PlaceObject3Tag poFilter = PlaceObject3Tag(sprite, gc);
            return poFilter;            
        }
        return po3;
    }
    
    // --------------------------------------------------------------------------
    //
    // Graphic Content Nodes
    //
    // --------------------------------------------------------------------------
    protected PlaceObject3Tag bitmap(BitmapGraphicNode node)
    {
        GraphicContext context = node.createGraphicContext();
        String source = parseSource(node.source);

        if (source == null)
        {
            // Missing source attribute in <BitmapGraphic> or <BitmapFill>.
            problems.add(new FXGMissingAttributeProblem(node.getDocumentPath(), node.getStartLine(), 
                    node.getStartColumn(), FXGConstants.FXG_SOURCE_ATTRIBUTE, node.getNodeName()));
            return null;
       }
        
        DefineImage imageTag = createDefineBitsTag(node, source);
        if(imageTag == null)
            return null;
        
        if ((node.visible) && (!node.isPartofClipMask))
        {
       
            DefineShapeTag imageShape;
            ScalingGrid scalingGrid = context.scalingGrid;
            if (scalingGrid != null)
            {
                Rect grid = TypeHelper.rect(scalingGrid.scaleGridLeft, scalingGrid.scaleGridTop, scalingGrid.scaleGridRight, scalingGrid.scaleGridBottom);
                imageShape = ImageHelper.create9SlicedShape(imageTag, grid, Double.NaN, Double.NaN);
                PlaceObject3Tag po3 = PlaceObject3Tag(imageShape, context);
                return po3;
            }
            else
            {
            	if (ImageHelper.bitmapImageNeedsClipping(imageTag, node))
            	{
            		PlaceObject3Tag p03 = bitmapWithClip(imageTag, node);
            		return p03;
            	}
            	else
            	{
            		imageShape = ImageHelper.createShapeForImage(imageTag, node);
            		PlaceObject3Tag po3 = PlaceObject3Tag(imageShape, context);
            		return po3;
            	}
            }            
        }
        else
        {
        	if (!ImageHelper.bitmapImageNeedsClipping(imageTag, node))
        	{       		
        	     double width = (Double.isNaN(node.width)) ? imageTag.getWidth() : node.width;
                 double height = (Double.isNaN(node.height)) ? imageTag.getHeight() : node.height;
        		 List<ShapeRecord>  shapeRecords = ShapeHelper.rectangle(0.0, 0.0, width, height);        
        	     DefineShapeTag shape = createDefineShapeTag(null, shapeRecords, new SolidColorFillNode(), null, context.getTransform());
        		 PlaceObject3Tag po3 = PlaceObject3Tag(shape, context);
        		 return po3;
        	}
        	else
        	{
                double width = ((imageTag.getWidth() < node.width) || Double.isNaN(node.width)) ? imageTag.getWidth() : node.width;
                double height = ((imageTag.getHeight() < node.height) || (Double.isNaN(node.height))) ? imageTag.getHeight() : node.height;
       		 	List<ShapeRecord>  shapeRecords = ShapeHelper.rectangle(0.0, 0.0, width, height);        
       	        DefineShapeTag shape = createDefineShapeTag(null, shapeRecords, new SolidColorFillNode(), null, context.getTransform());
       		 	PlaceObject3Tag po3 = PlaceObject3Tag(shape, context);
       		 	return po3;
        	}
        }
    }

    protected void graphicContentNodes(List<GraphicContentNode> nodes)
    {
        if (nodes == null) return;
        Iterator<GraphicContentNode> iterator = nodes.iterator();
        while (iterator.hasNext())
        {
            GraphicContentNode node = iterator.next();
            graphicContentNode(node);
        }
    }

    protected PlaceObject3Tag graphicContentNode(GraphicContentNode node)
    {
        PlaceObject3Tag po3 = null;

    	if (!node.visible)
        {
            ColorTransformNode ct = new ColorTransformNode();
            ct.alphaMultiplier = 0;
            ct.alphaOffset = 0;
            ct.blueMultiplier = 1;
            ct.blueOffset = 0;
            ct.greenMultiplier = 1;
            ct.greenOffset = 0;
            ct.redMultiplier = 1;
            ct.redOffset = 0;
            node.colorTransform = ct;
            
            if (node instanceof AbstractShapeNode) 
            {
                AbstractShapeNode shapeNode = (AbstractShapeNode)node;
                shapeNode.fill = null;
                shapeNode.stroke = null;
            }
            
        }

        if (node instanceof GroupNode)
        {
            group((GroupNode)node);
        }
        else
        {
            if (node.blendMode == BlendMode.AUTO)
                node.blendMode = BlendMode.NORMAL;
            
            // For non-group nodes, we process mask to clip only this shape
            // node. Process the mask first to ensure the depth is correct.
            List<IFilterNode> filters = null;
            if (node.mask != null)
            {
                // Remove the filters from context and process them later to force Flash Player to process the masks first
                if (node.filters != null)
                {   
                    filters = node.filters;
                    node.filters = null;
                    DefineSpriteTag filterSprite = createDefineSpriteTag("MaskFilter");
                    spriteStack.push(filterSprite);
                }
                DefineSpriteTag parentSprite = spriteStack.peek();
                mask(node, parentSprite);
            }
            
            if (node instanceof EllipseNode)
                po3 = ellipse((EllipseNode)node);
            else if (node instanceof LineNode)
                po3 = line((LineNode)node);
            else if (node instanceof PathNode)
                po3 = path((PathNode)node);
            else if (node instanceof RectNode)
                po3 = rect((RectNode)node);
            else if (node instanceof PlaceObjectNode)
                po3 = PlaceObject3TagInstance((PlaceObjectNode)node);
            else if (node instanceof BitmapGraphicNode)
                po3 = bitmap((BitmapGraphicNode)node);
            else if (node instanceof TextGraphicNode)
                po3 = text((TextGraphicNode)node);
            else if (node instanceof RichTextNode)
                po3 = richtext((RichTextNode)node);
            
            // If filters were not processed, place the topmost sprite in display list and apply filters 
            // This is done to force processing of masks before filters
            if (filters != null)
            {
                DefineSpriteTag sprite = spriteStack.pop();
                GraphicContext gc = new GraphicContext();
                gc.filters = filters;
                PlaceObject3Tag poFilter = PlaceObject3Tag(sprite, gc);
                return poFilter;            
            }
        }

        return po3;
    }

    protected PlaceObject3Tag ellipse(EllipseNode node)
    {
        // Note that we will apply node.x and node.y as a translation operation
        // in the PlaceObject3Tag3 Matrix and instead start the shape from the
        // origin (0.0, 0.0).
        Ellipse2D.Double ellipse = new Ellipse2D.Double(0.0, 0.0, node.width, node.height);
        
        ShapeBuilder builder = new ShapeBuilder();
        IShapeIterator iterator = new PathIteratorWrapper(ellipse.getPathIterator(null));
        builder.processShape(iterator);
        Shape shape = builder.build();
 
        return placeDefineShapeTag(node, shape.getShapeRecords(), node.fill, node.stroke, node.createGraphicContext());
    }

    protected PlaceObject3Tag group(GroupNode node)
    {
    	//handle blendMode "auto"
        if (node.blendMode == BlendMode.AUTO)
        {
        	if ((node.alpha == 0) || (node.alpha == 1))
        		node.blendMode = BlendMode.NORMAL;
        	else
        		node.blendMode = BlendMode.LAYER;
        }
        
        DefineSpriteTag groupSprite = createDefineSpriteTag("Group");
        GraphicContext context = node.createGraphicContext();
        
        // Handle 'scale 9' grid definition
        if (node.definesScaleGrid())
        {
            DefineScalingGridTag grid = createDefineScalingGridTag(context.scalingGrid);
            grid.setCharacter(groupSprite);
            extraTags.put(groupSprite,grid);
            //groupSprite.scalingGrid = grid;
        }

        PlaceObject3Tag po3 = PlaceObject3Tag(groupSprite, context);
        spriteStack.push(groupSprite);
        
        // First, process mask (if present)
        List <IFilterNode> filters = null;
        if (node.mask != null)
        {    
            // Remove the filters from context and process them later to force Flash Player to process the masks first
            filters = node.filters;
            if (filters == null)
            {
                List<GraphicContentNode> children = node.children;
                if (children != null)
                {
                    GraphicContentNode gcNode0 = (GraphicContentNode) children.get(0);
                    filters = gcNode0.filters;
                    if (filters != null)
                    {
                        //check if all the nodes share the same filter
                        for (int i = 1; ((i < children.size()) && filters!= null); i++)
                        {
                            GraphicContentNode gcNodeI = (GraphicContentNode) children.get(i);
                            if (gcNodeI.filters != filters)
                                filters = null;
                        }
                    }

                    if (filters != null)
                    {
                        for (int i = 0; (i < children.size()) ; i++)
                        {
                            GraphicContentNode gcNodeI = (GraphicContentNode) children.get(i);
                            gcNodeI.filters = null;
                        }                        
                    }
                    
                }
            }
            else
            {
                node.filters = null;
            }
 
            if (filters != null)       
            {    
                DefineSpriteTag filterSprite = createDefineSpriteTag("MaskFilter");
                spriteStack.push(filterSprite);
            }
            DefineSpriteTag sprite = spriteStack.peek();
            mask(node, sprite);
        }

        // Then process child nodes.
        if (node.children != null)
            graphicContentNodes(node.children);
        
        // If filters were not processed, place the topmost sprite in display list and apply filters 
        // This is done to force processing of masks before filters
        if (filters != null)
        {
            DefineSpriteTag sprite = spriteStack.pop();
            GraphicContext gc = new GraphicContext();
            gc.filters = filters;
            PlaceObject3Tag poFilter = PlaceObject3Tag(sprite, gc);
            return poFilter;            
        }
        spriteStack.pop();
        return po3;
    }

    protected PlaceObject3Tag line(LineNode node)
    {
        List<ShapeRecord> shapeRecords = ShapeHelper.line(node.xFrom, node.yFrom, node.xTo, node.yTo);
        GraphicContext context = node.createGraphicContext();
        PlaceObject3Tag po3 = placeDefineShapeTag(node, shapeRecords, node.fill, node.stroke, context);
        return po3;
    }

    protected PlaceObject3Tag mask(IMaskableNode node, DefineSpriteTag parentSprite)
    {
        PlaceObject3Tag po3 = null;

        IMaskingNode mask = node.getMask();
        if (mask instanceof GroupNode)
        {
            // According to FXG Spec.: The masking element inherits the target 
            // group's coordinate space, as though it were a direct child 
            // element. In the case when mask is inside a shape, it doesn't 
            // automatically inherit the coordinates from the shape node 
            // but inherits from its parent node which is also parent of 
            // the shape node. To fix it, specifically concatenating the 
            // shape node matrix to the masking node matrix.
            if (!(node instanceof GroupNode || node instanceof GraphicNode))
            {
                FXGMatrix nodeMatrix = null;
                MatrixNode matrixNodeShape = ((GraphicContentNode)node).matrix;
                if (matrixNodeShape == null)
                    // Convert shape node's discreet transform attributes to 
                    // matrix.
                    nodeMatrix = FXGMatrix.convertToMatrix(((GraphicContentNode)node).scaleX, ((GraphicContentNode)node).scaleY, ((GraphicContentNode)node).rotation, ((GraphicContentNode)node).x, ((GraphicContentNode)node).y);
                else
                    nodeMatrix = new FXGMatrix(matrixNodeShape);
                // Get masking node matrix.
                MatrixNode matrixNodeMasking = ((GraphicContentNode)mask).matrix;
                // Create a new MatrixNode if the masking node doesn't have one.
                if (matrixNodeMasking == null)
                {
                    // Convert masking node's transform attributes to matrix
                    // so we can concatenate the shape node's matrix to it.
                    ((GraphicContentNode)mask).convertTransformAttrToMatrix(problems);
                    matrixNodeMasking = ((GraphicContentNode)mask).matrix;
                }
                FXGMatrix maskMatrix = new FXGMatrix(matrixNodeMasking);
                // Concatenate the shape node's matrix to the masking node's 
                // matrix.
                maskMatrix.concat(nodeMatrix);
                // Set the masking node's matrix with the concatenated values.
                maskMatrix.setMatrixNodeValue(matrixNodeMasking);
            }
            
            markLeafNodesAsMask(node, (GroupNode) mask);
            po3 = group((GroupNode)mask);
        }
        else if (mask instanceof PlaceObjectNode)
        {
            po3 = PlaceObject3TagInstance((PlaceObjectNode)mask);
        }

        if (po3 != null)
        {
            int clipDepth = 1;
            // If we had a graphic or group, clip the depths for all children.
            if (node instanceof GroupNode)
            {
                GroupNode group = (GroupNode)node;
                if (group.children != null)
                    clipDepth = getSpriteDepth(parentSprite) + group.children.size();
            }
            else if (node instanceof GraphicNode)
            {
                GraphicNode graphic = (GraphicNode)node;
                if (graphic.children != null)
                    clipDepth = getSpriteDepth(parentSprite) + graphic.children.size();
            }
            // ... otherwise, just clip the shape itself.
            else
            {//TODO
                clipDepth = po3.getDepth() + 1;
            }

            po3.setClipDepth(clipDepth);
            po3.setHasClipDepth(true);
            if (node.getMaskType() == MaskType.ALPHA)
            {
                po3.setHasCacheAsBitmap(true);
            }
        }

        return po3;
    }

    protected PlaceObject3Tag path(PathNode node)
    {
        List<ShapeRecord> shapeRecords = ShapeHelper.path(node, (node.fill != null), problems);
        GraphicContext context = node.createGraphicContext();
        Winding winding[] = new Winding[1];
        winding[0] = node.winding;
        PlaceObject3Tag po3 = placeDefineShapeTag(node, shapeRecords, node.fill, node.stroke, context, winding);
        return po3;
    }

    protected void setPixelBenderBlendMode(PlaceObject3Tag po, BlendMode blendMode)
    {
        
    }
    
    protected void setAlphaMask(PlaceObject3Tag po)
    {
        po.setHasCacheAsBitmap(true);
    }
    
    protected void setLuminosityMask(PlaceObject3Tag po)
    {
       
    }
    
    protected PlaceObject3Tag PlaceObject3Tag(ICharacterTag symbol, GraphicContext context)
    {
        DefineSpriteTag sprite = spriteStack.peek();

        PlaceObject3Tag po3 = new PlaceObject3Tag();
        // po3.setName(name);
        po3.setCharacter(symbol);
        assert symbol != sprite;
        po3.setHasCharacter(true);
        Integer depthCount = getSpriteDepth(sprite) + 1;
        
        setSpriteDepth(sprite, depthCount);
        
        po3.setDepth(depthCount);
        if (context.blendMode != null)
        {
            if (!context.blendMode.needsPixelBenderSupport())
            {
                int blendMode = createBlendMode(context.blendMode);
                po3.setBlendMode(blendMode);
                po3.setHasBlendMode(true);
            }
            else
            {
                setPixelBenderBlendMode(po3, context.blendMode);
            }
        }
        
        if (context.filters != null)
        {
            List<Filter> filters = createFilters(context.filters);
            Filter filterArray[] = {};
            po3.setSurfaceFilterList(filters.toArray(filterArray));
            po3.setHasFilterList(true);
        }

        // FXG angles are always clockwise.
        Matrix matrix = context.getTransform().toSWFMatrix();
        po3.setMatrix(matrix);
        po3.setHasMatrix(true);
        if (context.colorTransform != null)
        {
            ColorTransformNode t = context.colorTransform;
            CXFormWithAlpha cx = TypeHelper.cxFormWithAlpha(t.alphaMultiplier, t.redMultiplier, t.greenMultiplier, t.blueMultiplier, t.alphaOffset, t.redOffset, t.greenOffset, t.blueOffset);
            po3.setColorTransform(cx);
            po3.setHasColorTransform(true);
        }

        
        if (context.maskType == MaskType.ALPHA)
        {
            setAlphaMask(po3);
        }
        else if (context.maskType == MaskType.LUMINOSITY)
        {
            setLuminosityMask(po3);
        }
        sprite.getControlTags().add(po3);
        return po3;
    }


    protected PlaceObject3Tag rect(RectNode node)
    {
        // Note that we will apply node.x and node.y as a translation operation
        // in the PlaceObject3Tag3 Matrix and instead start the shape from the
        // origin (0.0, 0.0).
        GraphicContext context = node.createGraphicContext();
        List<ShapeRecord> shapeRecords;
        if (node.radiusX != 0.0 || node.radiusY != 0.0 
        		|| !Double.isNaN(node.topLeftRadiusX) || !Double.isNaN(node.topLeftRadiusY)
        		|| !Double.isNaN(node.topRightRadiusX) || !Double.isNaN(node.topRightRadiusY)
        		|| !Double.isNaN(node.bottomLeftRadiusX) || !Double.isNaN(node.bottomLeftRadiusY)
        		|| !Double.isNaN(node.bottomRightRadiusX) || !Double.isNaN(node.bottomRightRadiusY))
        {
             shapeRecords  = ShapeHelper.rectangle(0.0, 0.0, node.width, node.height, 
            		 node.radiusX, node.radiusY, node.topLeftRadiusX, node.topLeftRadiusY,
            		 node.topRightRadiusX, node.topRightRadiusY, node.bottomLeftRadiusX, node.bottomLeftRadiusY,
            		 node.bottomRightRadiusX, node.bottomRightRadiusY);
        }
        else
        {
             shapeRecords = ShapeHelper.rectangle(0.0, 0.0, node.width, node.height);
        }
        
        PlaceObject3Tag po3 = placeDefineShapeTag(node, shapeRecords, node.fill, node.stroke, context);
        return po3;
    }

    protected PlaceObject3Tag text(TextGraphicNode node)
    {
        // No operation - text is ignored in this implementation.
        return null;
    }

    protected PlaceObject3Tag richtext(RichTextNode node)
    {
        // No operation - richtext is ignored in this implementation.
        return null;
    }

    // --------------------------------------------------------------------------
    //
    // FXG Library Definitions
    //
    // --------------------------------------------------------------------------

    protected PlaceObject3Tag PlaceObject3TagInstance(PlaceObjectNode node)
    {
        String definitionName = node.getNodeName();

        if (definitions == null)
            definitions = new HashMap<String, DefineSpriteTag>();

        DefineSpriteTag definitionSprite = definitions.get(definitionName);
        if (definitionSprite == null)
        {
            definitionSprite = createDefineSpriteTag("Definition");
            FXG2SWFTranscoder graphics = newInstance();
            graphics.setResourceResolver(resourceResolver);
            definitions.put(definitionName, definitionSprite);
            graphics.definitions = definitions;
            graphics.problems = problems;
            graphics.definition(node.definition, definitionSprite);
        }

        PlaceObject3Tag po3 = PlaceObject3Tag(definitionSprite, node.createGraphicContext());
        return po3;
    }

    protected void definition(DefinitionNode node, DefineSpriteTag definitionSprite)
    {
        GroupDefinitionNode groupDefinition = node.groupDefinition;
        
        if (groupDefinition == null) 
        {
            // Definitions must define a single Group child node.
            problems.add(new FXGMissingGroupChildNodeProblem(node.getDocumentPath(), node.getStartLine(), 
                    node.getStartColumn()));
            return;
        }
        spriteStack.push(definitionSprite);
        
        if (groupDefinition.definesScaleGrid())
        {
            DefineScalingGridTag scalingGrid = createDefineScalingGridTag(groupDefinition.getScalingGrid());
            scalingGrid.setCharacter(definitionSprite);
            extraTags.put(definitionSprite,scalingGrid);
            //definitionSprite.scalingGrid = scalingGrid;
            
        }

        graphicContentNodes(groupDefinition.children);

        spriteStack.pop();
    }

    // --------------------------------------------------------------------------
    //
    // SWF Tags and Types Helper Methods
    //
    // --------------------------------------------------------------------------

    protected DefineImage createDefineBitsTag(IFXGNode node, String source)
    {
        DefineImage imageTag = imageMap.get(source);
        if (imageTag == null)
        {
            try
            {                
                InputStream stream = resourceResolver.openStream(source);
                imageTag = ImageHelper.createDefineBits(stream, ImageHelper.guessMimeType(source));
                imageMap.put(source, imageTag);
            }
            catch (IOException ioe)
            {
                // Error {0} occurred while embedding image {1}.
                problems.add(new FXGErrorEmbeddingImageProblem(node.getDocumentPath(), node.getStartLine(), 
                        node.getStartColumn(), ioe.getMessage(), source));
                return null;
            }
        }
        return imageTag;
    }

    protected DefineScalingGridTag createDefineScalingGridTag(ScalingGrid grid)
    {
        DefineScalingGridTag scalingGrid = new DefineScalingGridTag();
        scalingGrid.setSplitter(TypeHelper.rect(grid.scaleGridLeft, grid.scaleGridTop, grid.scaleGridRight, grid.scaleGridBottom));
        return scalingGrid;
    }

    protected DefineSpriteTag createDefineSpriteTag(String name)
    {
        DefineSpriteTag sprite = new DefineSpriteTag(0, new ArrayList<ITag>());
        if (name == null) 
            name = "";
        return sprite;
    }
 
    protected DefineShapeTag createDefineShapeTag(AbstractShapeNode node, List<ShapeRecord> shapeRecords, IFillNode fill,
            IStrokeNode stroke, FXGMatrix transform, Winding... windings)
    {
        // Calculate the bounds of the shape outline (without strokes) - edgeBounds
        Rect edgeBounds = (node == null) ? ShapeHelper.getBounds(shapeRecords, null, (AbstractStrokeNode)stroke) : node.getBounds(shapeRecords, null);
        
        
        Rect shapeBounds;

        
        int lineStyleIndex = stroke == null ? 0 : 1;
        int fillStyle0Index = fill == null ? 0 : 1;
        int fillStyle1Index = 0;
        
        FillStyleArray fillStyles = new FillStyleArray(1);
        LineStyleArray lineStyles = new LineStyleArray();
        if (fill != null)
        {
            FillStyle fillStyle = createFillStyle(fill, edgeBounds);
            if(fillStyle != null)
                fillStyles.add(fillStyle);
        }

        if (stroke != null)
        {
        	//find the shapeBounds with stroke
        	LineStyle ls = createGenericLineStyle((AbstractStrokeNode)stroke);
            shapeBounds = (node == null) ? ShapeHelper.getBounds(shapeRecords, ls, (AbstractStrokeNode)stroke) : node.getBounds(shapeRecords, ls);        	

            LineStyle lineStyle = createLineStyle(stroke, shapeBounds);
            lineStyles.add(lineStyle);            
        }
        else
        {
        	shapeBounds = edgeBounds;
        }


        Styles styles = new Styles(fillStyles, lineStyles);


        if (windings.length > 0)
            ShapeHelper.setPathStyles(shapeRecords, lineStyleIndex, fillStyle0Index, fillStyle1Index, styles);
        else
            ShapeHelper.setStyles(shapeRecords, lineStyleIndex, fillStyle0Index, fillStyle1Index, styles);

        
        ShapeWithStyle sws = new ShapeWithStyle(styles);
        sws.addShapeRecords(shapeRecords);
        
        DefineShape4Tag DefineShapeTag4 = new DefineShape4Tag();
        DefineShapeTag4.setShapes(sws);
        DefineShapeTag4.setShapeBounds(shapeBounds);
        DefineShapeTag4.setEdgeBounds(edgeBounds);
        if ((fill != null) &&( windings.length > 0))
        {
        	Winding windingValue = windings[0];
            DefineShapeTag4.setUsesFillWindingRule(windingValue == Winding.NON_ZERO);
        }
        
        return DefineShapeTag4;
    }
    
    
    protected PlaceObject3Tag placeDefineShapeTag(AbstractShapeNode node, List<ShapeRecord> shapeRecords, 
            IFillNode fill, IStrokeNode stroke, GraphicContext context, Winding... windings )
    {
 
        if (node != null && fill!= null && !node.isPartofClipMask && ImageHelper.isBitmapFillWithClip(fill)) 
        {
            /* Support of fillMode=clip/scale is complicated since SWF does not 
             * support proper clipping of bitmaps. For fillMode=clip/scale, FXG defines 
             * the area outside of the bitmap fill area to be transparent.
             * In SWF, the bitmap bleeds to fill the rest of the path/shape 
             * if bitmap is specified to be a clipping bitmap.
             * 
             * In order to get the effect that FXG wants with SWF tags, the
             * the path/shape is split into two ShapeRecords for a path 
             * with a stroke & fill. A clipping mask is applied to the fill 
             * but not the stroke.
             */
            
            BitmapFillNode fillNode = (BitmapFillNode) fill;

            // Calculate the bounds of the shape outline (without strokes)
            Rect edgeBounds = node.getBounds(shapeRecords, null);           
            
            String source = parseSource(fillNode.source);
            if (source == null)
            {
                // Missing source attribute in <BitmapGraphic> or <BitmapFill>.
                problems.add(new FXGMissingAttributeProblem(node.getDocumentPath(), node.getStartLine(), 
                        node.getStartColumn(), FXGConstants.FXG_SOURCE_ATTRIBUTE, node.getNodeName()));
                return null;
            }
            DefineImage defImage = createDefineBitsTag(fill, source);
            if(defImage == null)
                return null;

            //process the filters later to avoid masking
            List<IFilterNode> filters = null;
            if (context.filters != null)       
            {   
                filters = context.filters;
                context.filters = null;
                DefineSpriteTag filterSprite = createDefineSpriteTag("MaskFilter");
                spriteStack.push(filterSprite);
            }

            DefineSpriteTag imageSprite = createDefineSpriteTag("BitmapFill");
            spriteStack.push(imageSprite);
            
            // First, generate the clipping mask
            DefineSpriteTag clipSprite = createDefineSpriteTag("BitmapFill_Clip");
            spriteStack.push(clipSprite);
            
            List<ShapeRecord> clipRectRecords = ShapeHelper.rectangle(0.0, 0.0, defImage.getWidth(), defImage.getHeight());
            DefineShapeTag clipShape = createDefineShapeTag(null, clipRectRecords, new SolidColorFillNode(), null, context.getTransform());
            FXGMatrix bitmapMatrix = TypeHelper.bitmapFillMatrix(fillNode, defImage, edgeBounds);
            FXGMatrix clipMatrix = new FXGMatrix(bitmapMatrix.a, bitmapMatrix.b, bitmapMatrix.c, bitmapMatrix.d, 0, 0);
            clipMatrix.scale(1.0/ISWFConstants.TWIPS_PER_PIXEL, 1.0/ISWFConstants.TWIPS_PER_PIXEL);
            clipMatrix.translate(bitmapMatrix.tx, bitmapMatrix.ty);
            GraphicContext clipContext = new GraphicContext();
            clipContext.setTransform(clipMatrix);
            PlaceObject3Tag(clipShape, clipContext); 
            spriteStack.pop();
            
            // Set the depth of the mask to that of the bitmap image fill
            clipContext.setTransform(context.getTransform());
            PlaceObject3Tag po3clip = PlaceObject3Tag(clipSprite, clipContext); 
            po3clip.setClipDepth(po3clip.getDepth() + 1);
            po3clip.setHasClipDepth(true);
            
            Styles styles = new Styles(new FillStyleArray(), new LineStyleArray());
            // Then, process the bitmap image fill
            ShapeWithStyle sws = new ShapeWithStyle(styles);
            
            int lineStyleIndex = 0;
            int fillStyle0Index = 1;
            int fillStyle1Index = 0;

            FillStyle fillStyle = createFillStyle(fill, edgeBounds);
            if(fillStyle != null)
                sws.getFillStyles().add(fillStyle);

            if (windings.length > 0)
                ShapeHelper.setPathStyles(shapeRecords, lineStyleIndex, fillStyle0Index, fillStyle1Index, styles);
            else
                ShapeHelper.setStyles(shapeRecords, lineStyleIndex, fillStyle0Index, fillStyle1Index, styles);
            
            sws.addShapeRecords(shapeRecords);
            
            DefineShape4Tag imageShape = new DefineShape4Tag();
            imageShape.setShapes(sws);
            imageShape.setShapeBounds(edgeBounds);
            imageShape.setEdgeBounds(edgeBounds);
            if ((fill != null) &&( windings.length > 0))
            {
                Winding windingValue = windings[0];
                imageShape.setUsesFillWindingRule(windingValue == Winding.NON_ZERO);
            }
            PlaceObject3Tag po3 = PlaceObject3Tag(imageShape, context);        
             
            if (stroke != null)
            {
                //make a copy of ShapeRecord for strokes
                ArrayList<ShapeRecord> shapeRecords2 = new ArrayList<ShapeRecord>(shapeRecords);
                Collections.copy(shapeRecords2, shapeRecords);

                Styles strokeStyles = new Styles(new FillStyleArray(), new LineStyleArray());

                //generate the define sprite for the stroke object with no clipping
                ShapeWithStyle swsStroke = new ShapeWithStyle(strokeStyles);

                // Consider linestyle stroke widths with bounds calculation               
                AbstractStrokeNode strokeNode = (AbstractStrokeNode) stroke;
                LineStyle ls = createGenericLineStyle(strokeNode);
                Rect shapeBounds =  node.getBounds(shapeRecords2, ls);     
                
                LineStyle lineStyle = createLineStyle(stroke, shapeBounds);
                swsStroke.getLineStyles().add(lineStyle);
                
                lineStyleIndex = 1;
                fillStyle0Index = 0;
                fillStyle1Index = 0;
                ShapeHelper.replaceStyles(shapeRecords2, lineStyleIndex, fillStyle0Index, fillStyle1Index, strokeStyles);
         
                swsStroke.addShapeRecords(shapeRecords2);

                DefineShape4Tag strokeShape = new DefineShape4Tag();
                strokeShape.setShapes(swsStroke);
                strokeShape.setShapeBounds(shapeBounds);
                strokeShape.setEdgeBounds(edgeBounds);
                po3 = PlaceObject3Tag(strokeShape, context);    
            }             
            spriteStack.pop();
            
            po3 = PlaceObject3Tag(imageSprite, new GraphicContext());
            
            // If filters were not processed, place the topmost sprite in display list and apply filters 
            // This is done to force processing of masks before filters
            if (filters != null)
            {
                DefineSpriteTag sprite = spriteStack.pop();
                
                GraphicContext gc = new GraphicContext();
                gc.filters = filters;
                PlaceObject3Tag poFilter = PlaceObject3Tag(sprite, gc);
                return poFilter;            
            }
            
            return po3;
            
        }
        else
        {
        	DefineShapeTag shape = createDefineShapeTag(node, shapeRecords, fill, stroke, context.getTransform(), windings);
        	PlaceObject3Tag po3 = PlaceObject3Tag(shape, context);
        	return po3;
        } 
       
    }
    
    protected FillStyle createFillStyle(IFillNode fill, Rect bounds)
     {
        if (fill instanceof SolidColorFillNode)
            return createFillStyle((SolidColorFillNode)fill);
        else if (fill instanceof LinearGradientFillNode)
            return createFillStyle((LinearGradientFillNode)fill, bounds);
        else if (fill instanceof RadialGradientFillNode)
            return createFillStyle((RadialGradientFillNode)fill, bounds);
        else if (fill instanceof BitmapFillNode)
            return createFillStyle((BitmapFillNode)fill, bounds);
        else
            return null;
    }

    protected FillStyle createFillStyle(SolidColorFillNode fill)
    {
        FillStyle fs = new FillStyle();
        fs.setColor(TypeHelper.splitColor(TypeHelper.colorARGB(fill.color, fill.alpha)));
        fs.setFillStyleType(FillStyle.SOLID_FILL);
        return fs;
    }

    protected FillStyle createFillStyle(BitmapFillNode fill, Rect bounds)
    {
        FillStyle fs = new FillStyle();
        
        if (ImageHelper.bitmapFillModeIsRepeat(fill))
            fs.setFillStyleType(FillStyle.REPEATING_BITMAP_FILL);
        else
            fs.setFillStyleType(FillStyle.CLIPPED_BITMAP_FILL);

        String sourceFormatted = parseSource(fill.source);
        
        if (sourceFormatted == null)
        {
            // Source is required after FXG 1.0
            // Missing source attribute in <BitmapGraphic> or <BitmapFill>.
            problems.add(new FXGMissingAttributeProblem(fill.getDocumentPath(), fill.getStartLine(), 
                    fill.getStartColumn(), FXGConstants.FXG_SOURCE_ATTRIBUTE, fill.getNodeName()));
            return null;
        }

        DefineImage img = createDefineBitsTag(fill, sourceFormatted); 
        if(img != null)
        {
            fs.setBitmapCharacter(img.getTag());
        
            fs.setBitmapMatrix(TypeHelper.bitmapFillMatrix(fill, img, bounds).toSWFMatrix());
        }
        return fs;
    }

    protected FillStyle createFillStyle(LinearGradientFillNode node, Rect bounds)
    {
        FillStyle fs = new FillStyle();
        fs.setFillStyleType(FillStyle.LINEAR_GRADIENT_FILL);
        fs.setGradientMatrix(TypeHelper.linearGradientMatrix(node, bounds));
        Gradient gradient = new Gradient();
        populateGradient(gradient, node.entries, node.interpolationMethod, node.spreadMethod);
        fs.setGradient(gradient);

        return fs;
    }

    protected FillStyle createFillStyle(LinearGradientStrokeNode node, Rect bounds)
    {
        FillStyle fs = new FillStyle();
        fs.setFillStyleType(FillStyle.LINEAR_GRADIENT_FILL);
        fs.setGradientMatrix(TypeHelper.linearGradientMatrix(node, bounds));
        Gradient gradient = new Gradient();
        populateGradient(gradient, node.entries, node.interpolationMethod, node.spreadMethod);
        fs.setGradient(gradient);

        return fs;
    }

    protected FillStyle createFillStyle(RadialGradientFillNode node, Rect bounds)
    {
        FillStyle fs = new FillStyle();
        fs.setFillStyleType(FillStyle.FOCAL_RADIAL_GRADIENT_FILL);
        fs.setGradientMatrix(TypeHelper.radialGradientMatrix(node, bounds));
        FocalGradient gradient = new FocalGradient();
        populateGradient(gradient, node.entries, node.interpolationMethod, node.spreadMethod);
        gradient.setFocalPoint((float)node.focalPointRatio);
        fs.setGradient(gradient);

        return fs;
    }

    protected FillStyle createFillStyle(RadialGradientStrokeNode node, Rect bounds)
    {
        FillStyle fs = new FillStyle();
        fs.setFillStyleType(FillStyle.FOCAL_RADIAL_GRADIENT_FILL);
        fs.setGradientMatrix(TypeHelper.radialGradientMatrix(node, bounds));
        FocalGradient gradient = new FocalGradient();
        populateGradient(gradient, node.entries, node.interpolationMethod, node.spreadMethod);
        gradient.setFocalPoint((float)node.focalPointRatio);
        fs.setGradient(gradient);

        return fs;
    }
    
    protected LineStyle createLineStyle(IStrokeNode stroke, Rect bounds)
    {
        if (stroke instanceof SolidColorStrokeNode)
            return createLineStyle((SolidColorStrokeNode)stroke);
        else if (stroke instanceof LinearGradientStrokeNode)
            return createLineStyle((LinearGradientStrokeNode)stroke, bounds);
        else if (stroke instanceof RadialGradientStrokeNode)
            return createLineStyle((RadialGradientStrokeNode)stroke, bounds);
        else
            return null;
    }

    private LineStyle2 createGenericLineStyle(AbstractStrokeNode stroke)
    {
        LineStyle2 ls = new LineStyle2();
        ls.setWidth((int)StrictMath.rint(stroke.getWeight() * ISWFConstants.TWIPS_PER_PIXEL));

        int startCapStyle = createCaps(stroke.caps);
        int endCapStyle = startCapStyle;
        int jointStyle = createJoints(stroke.joints);
        
        boolean noHScaleFlag = stroke.scaleMode == ScaleMode.VERTICAL || stroke.scaleMode == ScaleMode.NONE;
        boolean noVScaleFlag = stroke.scaleMode == ScaleMode.HORIZONTAL || stroke.scaleMode == ScaleMode.NONE;
        
        // The 4.5.1 Flex Compiler switches these two flags.                   
        // A bug has been logged in JIRA against the old compiler for this issue
        // http://bugs.adobe.com/jira/browse/SDK-31114
        ls.setNoHScaleFlag(noHScaleFlag);
        ls.setNoVScaleFlag(noVScaleFlag);
        
        ls.setJoinStyle(jointStyle);
        ls.setStartCapStyle(startCapStyle);
        ls.setEndCapStyle(endCapStyle);
        ls.setPixelHintingFlag(stroke.pixelHinting);
        
        if (jointStyle == 2)
        {
            // Encoded in SWF as an 8.8 fixed point value
            ls.setMiterLimitFactor((float)(stroke.miterLimit));
        }
        
        return ls;
    }
    protected LineStyle createLineStyle(SolidColorStrokeNode stroke)
    {
        LineStyle ls = createGenericLineStyle(stroke);
        ls.setColor(TypeHelper.splitColor(TypeHelper.colorARGB(stroke.color, stroke.alpha)));
        return ls;
    }
    
    protected LineStyle2 createLineStyle(LinearGradientStrokeNode stroke, Rect bounds)
    {
        LineStyle2 ls = createGenericLineStyle(stroke);
        ls.setFillType(createFillStyle(stroke, bounds));
        ls.setHasFillFlag(true);
        return ls;
    }

    protected LineStyle2 createLineStyle(RadialGradientStrokeNode stroke, Rect edgeBounds)
    {
        LineStyle2 ls = createGenericLineStyle(stroke);
        ls.setFillType(createFillStyle(stroke, edgeBounds));
        ls.setHasFillFlag(true);
        return ls;
    }

    protected int createCaps(Caps value)
    {
        if (value != null)
            return value.ordinal();
        else
            return Caps.NONE.ordinal();
    }

    protected int createJoints(Joints value)
    {
        if (value != null)
            return value.ordinal();
        else
            return Joints.ROUND.ordinal();
    }

    protected int createSpreadMode(SpreadMethod value)
    {
        return value.ordinal();
    }

    protected int createBlendMode(BlendMode value)
    {
        return value.ordinal();
    }

    protected int createInterpolationMode(InterpolationMethod value)
    {
        return value.ordinal();
    }

    protected List<Filter> createFilters(List<IFilterNode> list)
    {
        List<Filter> filters = new ArrayList<Filter>(list.size());
        Iterator<IFilterNode> iterator = list.iterator();
        while (iterator.hasNext())
        {
            IFilterNode f = iterator.next();
            if (f instanceof BevelFilterNode)
            {
                BevelFilterNode node = (BevelFilterNode)f;
                BevelFilter filter = createBevelFilter(node);
                Filter rec = new Filter();
                rec.setFilterID(Filter.BEVEL);
                rec.setBevelFilter(filter);
                filters.add(rec);
            }
            else if (f instanceof BlurFilterNode)
            {
                BlurFilterNode node = (BlurFilterNode)f;
                BlurFilter filter = createBlurFilter(node);
                Filter rec = new Filter();
                rec.setFilterID(Filter.BLUR);
                rec.setBlurFilter(filter);
                filters.add(rec);
            }
            else if (f instanceof ColorMatrixFilterNode)
            {
                ColorMatrixFilterNode node = (ColorMatrixFilterNode)f;
                Filter rec = new Filter();
                rec.setFilterID(Filter.COLOR_MATRIX);
                rec.setColorMatrixFilter(node.matrix);
                filters.add(rec);
            }
            else if (f instanceof DropShadowFilterNode)
            {
                DropShadowFilterNode node = (DropShadowFilterNode)f;
                DropShadowFilter filter = createDropShadowFilter(node);
                Filter rec = new Filter();
                rec.setFilterID(Filter.DROP_SHADOW);
                rec.setDropShadowFilter(filter);
                filters.add(rec);
            }
            else if (f instanceof GlowFilterNode)
            {
                GlowFilterNode node = (GlowFilterNode)f;
                GlowFilter filter = createGlowFilter(node);
                Filter rec = new Filter();
                rec.setFilterID(Filter.GLOW);
                rec.setGlowFilter(filter);
                filters.add(rec);
            }
            else if (f instanceof GradientBevelFilterNode)
            {
                GradientBevelFilterNode node = (GradientBevelFilterNode)f;
                GradientBevelFilter filter = createGradientBevelFilter(node);
                Filter rec = new Filter();
                rec.setFilterID(Filter.GRADIENT_BEVEL);
                rec.setGradientBevelFilter(filter);
                filters.add(rec);
            }
            else if (f instanceof GradientGlowFilterNode)
            {
                GradientGlowFilterNode node = (GradientGlowFilterNode)f;
                GradientGlowFilter filter = createGradientGlowFilter(node);
                Filter rec = new Filter();
                rec.setFilterID(Filter.GRADIENT_GLOW);
                rec.setGradientGlowFilter(filter);
                filters.add(rec);
            }
        }
        return filters;
    }

    protected BevelFilter createBevelFilter(BevelFilterNode node)
    {
        BevelFilter filter = new BevelFilter();
        filter.setAngle((float)(node.angle*Math.PI/180.0));
        filter.setBlurX((float)node.blurX);
        filter.setBlurY((float)node.blurY);
        filter.setDistance((float)node.distance);
        filter.setStrength((float)node.strength);
        filter.setShadowColor(TypeHelper.splitColor(TypeHelper.colorARGB(node.shadowColor, node.shadowAlpha)));
        filter.setHighlightColor(TypeHelper.splitColor(TypeHelper.colorARGB(node.highlightColor, node.highlightAlpha)));
        
        
        filter.setOnTop(node.type == BevelType.FULL);
        
        filter.setInnerShadow(node.type == BevelType.INNER);
        filter.setPasses(node.quality);
        filter.setKnockout(node.knockout);
        filter.setCompositeSource(true);
        return filter;
    }

    protected BlurFilter createBlurFilter(BlurFilterNode node)
    {
        BlurFilter filter = new BlurFilter();
        filter.setBlurX((float)(node.blurX));
        filter.setBlurY((float)(node.blurY));
        filter.setPasses(node.quality);
        return filter;
    }

    protected DropShadowFilter createDropShadowFilter(DropShadowFilterNode node)
    {
        DropShadowFilter filter = new DropShadowFilter();
        filter.setDropShadowColor(TypeHelper.splitColor(TypeHelper.colorARGB(node.color, node.alpha)));
        filter.setAngle((float)(node.angle*Math.PI/180.0));
        filter.setBlurX((float)(node.blurX));
        filter.setBlurY((float)(node.blurY));
        filter.setDistance((float)(node.distance));
        filter.setStrength((float)(node.strength));
        filter.setPasses(node.quality);
        filter.setCompositeSource(!node.hideObject);
        filter.setKnockout(node.knockout);
        filter.setInnerShadow(node.inner);
        return filter;
    }

    protected GlowFilter createGlowFilter(GlowFilterNode node)
    {
        GlowFilter filter = new GlowFilter();
        filter.setGlowColor(TypeHelper.splitColor(TypeHelper.colorARGB(node.color, node.alpha)));
        filter.setBlurX((float)(node.blurX));
        filter.setBlurY((float)(node.blurY));
        filter.setStrength((float)(node.strength));
        filter.setKnockout(node.knockout);
        filter.setInnerGlow(node.inner);
        filter.setCompositeSource(true);
        filter.setPasses(node.quality);
        
        return filter;
    }

    protected GradientBevelFilter createGradientBevelFilter(
            GradientBevelFilterNode node)
    {
        GradientBevelFilter filter = new GradientBevelFilter();
        if (node.entries != null)
        {
            byte count = (byte)node.entries.size();
            filter.setNumColors(count);
            RGBA gradientColors[] = new RGBA[count];
            int gradientRatios[] = new int[count];
            filter.setGradientColors(gradientColors);
            filter.setGradientRatio(gradientRatios);

            GradRecord[] records = createGradRecords(node.entries);
            for (int i = 0; i < records.length; i++)
            {
                GradRecord record = records[i];
                RGB color = record.getColor();
                if (color instanceof RGBA)
                    gradientColors[i] = (RGBA)color;
                else
                    gradientColors[i] = new RGBA(color.getRed(), color.getGreen(), color.getBlue(), 0xFF);
                gradientRatios[i] = record.getRatio();
            }
        }

        filter.setAngle((float)(node.angle*Math.PI/180.0));
        filter.setBlurX((float)(node.blurX));
        filter.setBlurY((float)(node.blurY));
        filter.setDistance((float)(node.distance));
        filter.setStrength((float)(node.strength));
        filter.setKnockout(node.knockout);
        filter.setPasses(node.quality);
        filter.setCompositeSource(true);
        filter.setInnerShadow(node.type == BevelType.INNER);
        filter.setOnTop(node.type == BevelType.FULL);

        return filter;
    }

    protected GradientGlowFilter createGradientGlowFilter(
            GradientGlowFilterNode node)
    {
        GradientGlowFilter filter = new GradientGlowFilter();

        if (node.entries != null)
        {
            byte count = (byte)node.entries.size();
            filter.setNumColors(count);
            RGBA gradientColors[] = new RGBA[count];
            int gradientRatio[] = new int[count];

            GradRecord[] records = createGradRecords(node.entries);
            for (int i = 0; i < records.length; i++)
            {
                GradRecord record = records[i];
                RGB color = record.getColor();
                if (color instanceof RGBA)
                    gradientColors[i] = (RGBA)color;
                else
                    gradientColors[i] = new RGBA(color.getRed(), color.getGreen(), color.getBlue(), 0xFF);
                gradientRatio[i] = record.getRatio();
            }
            filter.setGradientColors(gradientColors);
            filter.setGradientRatio(gradientRatio);
        }

        filter.setAngle((float)(node.angle*Math.PI/180.0));
        filter.setBlurX((float)node.blurX);
        filter.setBlurY((float)node.blurY);
        filter.setDistance((float)node.distance);
        filter.setStrength((float)node.strength);
        
        filter.setPasses(node.quality);
        filter.setKnockout(node.knockout);
        filter.setInnerGlow(node.inner);
        filter.setCompositeSource(true);

        return filter;
    }

    protected void populateGradient(Gradient gradient,
            List<GradientEntryNode> entries, InterpolationMethod interpolation,
            SpreadMethod spread)
    {
        gradient.setGradientRecords(Arrays.asList(createGradRecords(entries)));

        if (interpolation != null)
            gradient.setInterpolationMode(createInterpolationMode(interpolation));

        if (spread != null)
            gradient.setSpreadMode(createSpreadMode(spread));
    }

    protected GradRecord[] createGradRecords(List<GradientEntryNode> entries)
    {
        int count = entries.size();
        GradRecord[] records = new GradRecord[count];
        double previousRatio = 0.0;
        for (int currentIndex = 0; currentIndex < count; currentIndex++)
        {
            GradientEntryNode entry = entries.get(currentIndex);
            double thisRatio = entry.ratio;

            // Auto-calculate gradient ratio if omitted from an entry.
            if (Double.isNaN(thisRatio))
            {
                // The first ratio is assumed to be 0.0.
                if (currentIndex == 0)
                {
                    thisRatio = 0.0;
                }
                // The last ratio is assumed to be 1.0.
                else if (currentIndex == count - 1)
                {
                    thisRatio = 1.0;
                }
                else
                {
                    // Other omitted ratios are divided evenly between the last
                    // ratio and the next specified ratio (or 1.0 if none).
                    double nextRatio = 1.0;
                    int nextIndex = count - 1;
                    for (int i = currentIndex; i < count; i++)
                    {
                        GradientEntryNode nextEntry = entries.get(i);
                        if (!Double.isNaN(nextEntry.ratio))
                        {
                            nextRatio = nextEntry.ratio;
                            nextIndex = i;
                            break;
                        }
                    }

                    int entryGap = nextIndex - (currentIndex - 1);
                    if (entryGap > 0)
                    {
                        thisRatio = previousRatio + ((nextRatio - previousRatio) / (entryGap));
                    }
                    else
                    {
                        thisRatio = previousRatio;
                    }
                }
            }

            GradRecord record = new GradRecord(TypeHelper.gradientRatio(thisRatio), TypeHelper.splitColor(TypeHelper.colorARGB(entry.color, entry.alpha)));
            records[currentIndex] = record;

            // Remember this ratio as the last one specified
            previousRatio = thisRatio;
        }

        return records;
    }

    protected String parseSource(String source)
    {
        // TODO: Create a standard @Embed() parser.
        if (source != null)
        {
            source = source.trim();

            if (source.startsWith("@Embed("))
            {
                source = source.substring(7).trim();

                if (source.endsWith(")"))
                {
                    source = source.substring(0, source.length() - 1).trim();
                }

                if (source.charAt(0) == '\'' && source.charAt(source.length() - 1) == '\'')
                {
                    source = source.substring(1, source.length() - 1).trim();
                }
            }
        }

        return source;
    }
    
    private void  markLeafNodesAsMask(IMaskableNode maskableNode, GroupNode mask)
    {
        if ((mask == null) || (mask.children == null))
            return;
    	Iterator<GraphicContentNode> iter = mask.children.iterator();
    	while (iter.hasNext()) 
    	{
    		GraphicContentNode gcNode = iter.next();
    		if (gcNode instanceof GroupNode)
    		{
    			markLeafNodesAsMask(maskableNode, (GroupNode) gcNode);
    		}
    		else
    		{
     		    if (maskableNode.getMaskType() == MaskType.CLIP)
    		        gcNode.isPartofClipMask = true; 
    		}
    	}
    }

    @Override
    public ITypeDefinition[] getDependencies()
    {
        return new ITypeDefinition[0];
    }
    
    /**
     * This class wraps a PathIterator and adds a IShapeIterator
     * implemenation.
     */
    private static class PathIteratorWrapper implements IShapeIterator
    {
        private PathIterator pi;

        public PathIteratorWrapper(PathIterator pi)
        {
            this.pi = pi;
        }

        @Override
        public short currentSegment(double[] coords)
        {
            int code = pi.currentSegment(coords);
            return (short) code;
        }

        @Override
        public boolean isDone()
        {
            return pi.isDone();
        }

        @Override
        public void next()
        {
            pi.next();
        }
    }
}
