/*
 *
 *  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.swf.types;

import org.apache.flex.swf.tags.ICharacterReferrer;
import org.apache.flex.swf.tags.ICharacterTag;
import org.apache.flex.swf.tags.CharacterIterableFactory;

/**
 * The style change record is also a non-edge record. It can be used to do the
 * following:
 * <ol>
 * <li>Select a fill or line style for drawing.</li>
 * <li>Move the current drawing position (without drawing).</li>
 * <li>Replace the current fill and line style arrays with a new set of styles.</li>
 * </ol>
 * <p>
 * Because fill and line styles often change at the start of a new path, it is
 * useful to perform more than one action in a single record. For example, say a
 * {@code DefineShape} tag defines a red circle and a blue square. After the
 * circle is closed, it is necessary to move the drawing position, and replace
 * the red fill with the blue fill. The style change record can achieve this
 * with a single shape record.
 * </p>
 */
public class StyleChangeRecord extends ShapeRecord implements ICharacterReferrer
{
    public StyleChangeRecord()
    {
        super(ShapeRecordType.STYLE_CHANGE);
        this.stateNewStyles = false;
        this.stateLineStyle = false;
        this.stateFillStyle0 = false;
        this.stateFillStyle1 = false;
        this.stateMoveTo = false;
    }

    private boolean stateNewStyles;
    private boolean stateLineStyle;
    private boolean stateFillStyle1;
    private boolean stateFillStyle0;
    private boolean stateMoveTo;

    private int moveDeltaX;
    private int moveDeltaY;

    private IFillStyle fillStyle0;
    private IFillStyle fillStyle1;
    private ILineStyle lineStyle;

    private int numFillBits;
    private int numLineBits;

    private Styles styles;

    /**
     * Move the current drawing position (without drawing).
     * 
     * @param x delta x
     * @param y delta y
     */
    public void setMove(int x, int y)
    {
        stateMoveTo = true;
        moveDeltaX = x;
        moveDeltaY = y;
    }

    /**
     * Select styles from parent {@code ShapeWithStyle} record. The
     * {@code FillStyle} and {@code LineStyle} objects provided must be defined
     * in the parent {@code ShapeWithStyle} record. Otherwise, exception will be
     * thrown.
     * 
     * @param fillStyle0 fill0 style
     * @param fillStyle1 fill1 style
     * @param lineStyle line style
     * @throws IllegalArgumentException using styles not defined in the parent
     * shape
     */
    public void setDefinedStyles(
            final IFillStyle fillStyle0,
            final IFillStyle fillStyle1,
            final ILineStyle lineStyle,
            final Styles styleContext)
    {
        if (fillStyle0 != null && !styleContext.getFillStyles().contains(fillStyle0))
        {
            throw new IllegalArgumentException("FillStyle0 is not defined in parent shape.");
        }

        if (fillStyle1 != null && !styleContext.getFillStyles().contains(fillStyle1))
        {
            throw new IllegalArgumentException("FillStyle1 is not defined in parent shape.");
        }

        if (lineStyle != null && !styleContext.getLineStyles().contains(lineStyle))
        {
            throw new IllegalArgumentException("LineStyle is not defined in parent shape.");
        }

        this.fillStyle0 = fillStyle0;
        this.fillStyle1 = fillStyle1;
        this.lineStyle = lineStyle;
        this.stateFillStyle0 = fillStyle0 != null;
        this.stateFillStyle1 = fillStyle1 != null;
        this.stateLineStyle = lineStyle != null;
    }

    /**
     * Select styles from parent {@code ShapeWithStyle} record. The
     * {@code FillStyle} and {@code LineStyle} objects provided must be defined
     * in the parent {@code ShapeWithStyle} record. Otherwise, exception will be
     * thrown.
     * 
     * @param fillStyle0 fill0 style
     * @param fillStyle1 fill1 style
     * @param lineStyle line style
     * @throws IllegalArgumentException using styles not defined in the parent
     * shape
     */
    public void setDefinedStyles(
            final IFillStyle fillStyle0,
            final IFillStyle fillStyle1,
            final ILineStyle lineStyle,
            final boolean stateFillStyle0,
            final boolean stateFillStyle1,
            final boolean stateLineStyle,
            final Styles styleContext)
    {
        this.fillStyle0 = fillStyle0;
        this.fillStyle1 = fillStyle1;
        this.lineStyle = lineStyle;
        this.stateFillStyle0 = stateFillStyle0;
        this.stateFillStyle1 = stateFillStyle1;
        this.stateLineStyle = stateLineStyle;
    }

    /**
     * Select styles from parent {@code ShapeWithStyle} record. The
     * {@code FillStyle} and {@code LineStyle} objects provided must be defined
     * in the parent {@code ShapeWithStyle} record. Otherwise, exception will be
     * thrown.
     * 
     * @param fillStyle0Index fill0 style
     * @param fillStyle1Index fill1 style
     * @param lineStyleIndex line style
     * @throws IllegalArgumentException using styles not defined in the parent
     * shape
     */
    public void setDefinedStyles(
            final int fillStyle0Index,
            final int fillStyle1Index,
            final int lineStyleIndex,
            final Styles styleContext)
    {
        if (fillStyle0Index >= 0)
        {
            if (fillStyle0Index > 0)
            {
                this.fillStyle0 = styleContext.getFillStyles().get(fillStyle0Index - 1);
            }
            else
            {
                this.fillStyle0 = null;
            }

            stateFillStyle0 = true;
        }
        if (fillStyle1Index >= 0)
        {
            if (fillStyle1Index > 0)
            {
                this.fillStyle1 = styleContext.getFillStyles().get(fillStyle1Index - 1);
            }
            else
            {
                this.fillStyle1 = null;
            }

            stateFillStyle1 = true;
        }
        if (lineStyleIndex >= 0)
        {
            if (lineStyleIndex > 0)
            {
                this.lineStyle = styleContext.getLineStyles().get(lineStyleIndex - 1);
            }
            else
            {
                this.lineStyle = null;
            }

            stateLineStyle = true;
        }
    }

    /**
     * Select styles from parent {@code Shape} record for a font. The
     * {@code FillStyle} and {@code LineStyle} objects provided must be defined
     * in the parent {@code ShapeWithStyle} record. Otherwise, exception will be
     * thrown.
     * 
     * @param fillStyle0Index fill0 style
     * @param fillStyle1Index fill1 style
     * @param lineStyleIndex line style
     * @throws IllegalArgumentException using styles not defined in the parent
     * shape
     */
    public void setDefinedFontStyles(
            final int fillStyle0Index,
            final int fillStyle1Index,
            final int lineStyleIndex,
            final Styles styleContext)
    {
        // there shouldn't be any styles on a shape for fonts, as the
        // tag is a Shape, not ShapeWithStyle, but the fillStyle0 can be 1 because
        // of the following from the SWF spec:
        // "The first STYLECHANGERECORD of each SHAPE in the GlyphShapeTable does not use
        // the LineStyle and LineStyles fields. In addition, the first STYLECHANGERECORD of each
        // shape must have both fields StateFillStyle0 and FillStyle0 set to 1."
        if (fillStyle0Index >= 0)
        {
            fillStyle0 = null;
            stateFillStyle0 = true;
        }
        if (fillStyle1Index >= 0)
        {
            fillStyle1 = null;
            stateFillStyle1 = true;
        }
        if (lineStyleIndex >= 0)
        {
            lineStyle = null;
            stateLineStyle = true;
        }
    }

    /**
     * clears the styles of this style change record if the respective boolean is set
     * 
     * @param fill0 <code>true</code> to clear fill style 0
     * @param fill1 <code>true</code> to clear file style 1
     * @param line <code>true</code> to clear line style
     */
    public void defaultStyles(boolean fill0, boolean fill1, boolean line)
    {
        if (fill0)
        {
            this.fillStyle0 = null;
            stateFillStyle0 = true;
        }
        if (fill1)
        {
            this.fillStyle1 = null;
            stateFillStyle1 = true;
        }
        if (line)
        {
            this.lineStyle = null;
            stateLineStyle = true;
        }
    }

    /**
     * Define new styles and use the new styles.
     */
    public void setNewStyles(final Styles value)
    {
        this.stateNewStyles = true;
        this.styles = value;
    }

    /**
     * If true, this {@code StyleChangeRecord} defines new styles, and its
     * FillStyle0, FillStyle1, LineStyle points to the new styles.
     * 
     * @return true if the StyleChangeRecord defines new styles.
     */
    public boolean isStateNewStyles()
    {
        return stateNewStyles;
    }

    /**
     * If true, this {@code StyleChangeReocrd} selects a line style.
     * 
     * @return true if the StyleChangeRecord selects a line style.
     */
    public boolean isStateLineStyle()
    {
        return stateLineStyle;
    }

    /**
     * If true, this {@code StyleChangeReocrd} selects a fill style for
     * {@code FillStyle1}.
     * 
     * @return true if the StyleChangeRecord selects a fill style for
     * {@code FillStyle1}.
     */
    public boolean isStateFillStyle1()
    {
        return stateFillStyle1;
    }

    /**
     * If true, this {@code StyleChangeReocrd} selects a fill style for
     * {@code FillStyle0}.
     * 
     * @return true if the StyleChangeRecord selects a fill style for
     * {@code FillStyle0}.
     */
    public boolean isStateFillStyle0()
    {
        return stateFillStyle0;
    }

    /**
     * If true, this {@code StyleChangeReocrd} moves the draw position.
     * 
     * @return true if the StyleChangeRecord moves the draw position.
     */
    public boolean isStateMoveTo()
    {
        return stateMoveTo;
    }

    /**
     * Get move delta on X-axis.
     * 
     * @return move delta X.
     */
    public int getMoveDeltaX()
    {
        return moveDeltaX;
    }

    /**
     * Get move delta on Y-axis.
     * 
     * @return move delta Y.
     */
    public int getMoveDeltaY()
    {
        return moveDeltaY;
    }

    /**
     * Get FillStyle0.
     * 
     * @return fill0 style
     */
    public IFillStyle getFillstyle0()
    {
        return fillStyle0;
    }

    /**
     * Get FillStyle1
     * 
     * @return fill1 style
     */
    public IFillStyle getFillstyle1()
    {
        return fillStyle1;
    }

    /**
     * Get LineStyle
     * 
     * @return line style
     */
    public ILineStyle getLinestyle()
    {
        return lineStyle;
    }

    public Styles getStyles()
    {
        return styles;
    }

    /**
     * StyleChangeRecord refers to fill styles either from its parent
     * ShapeWithStyle's fill styles or from its own private fill styles (when
     * stateNewStyle is true).
     */
    @Override
    public Iterable<ICharacterTag> getReferences()
    {
        return CharacterIterableFactory.collect(
                (stateFillStyle0 && fillStyle0 != null) ? fillStyle0.getReferences() : CharacterIterableFactory.empty(),
                (stateFillStyle1 && fillStyle1 != null) ? fillStyle1.getReferences() : CharacterIterableFactory.empty());
    }

    /**
     * Get the numFillBits field read from SWF.
     * 
     * @return the numFillBits
     */
    public int getNumFillBits()
    {
        return numFillBits;
    }

    /**
     * Only SWFReader can set this field.
     * 
     * @param value the numFillBits to set
     */
    public void setNumFillBits(int value)
    {
        this.numFillBits = value;
    }

    /**
     * Get the numLineBits field read from SWF.
     * 
     * @return the numLineBits
     */
    public int getNumLineBits()
    {
        return numLineBits;
    }

    /**
     * Only SWFReader can set this field.
     * 
     * @param value the numLineBits to set
     */
    public void setNumLineBits(int value)
    {
        this.numLineBits = value;
    }
}
