/*
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * "The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations under
 * the License.
 *
 * The Original Code is ICEpdf 3.0 open source software code, released
 * May 1st, 2009. The Initial Developer of the Original Code is ICEsoft
 * Technologies Canada, Corp. Portions created by ICEsoft are Copyright (C)
 * 2004-2009 ICEsoft Technologies Canada, Corp. All Rights Reserved.
 *
 * Contributor(s): _____________________.
 *
 * Alternatively, the contents of this file may be used under the terms of
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"
 * License), in which case the provisions of the LGPL License are
 * applicable instead of those above. If you wish to allow use of your
 * version of this file only under the terms of the LGPL License and not to
 * allow others to use your version of this file under the MPL, indicate
 * your decision by deleting the provisions above and replace them with
 * the notice and other provisions required by the LGPL License. If you do
 * not delete the provisions above, a recipient may use your version of
 * this file under either the MPL or the LGPL License."
 *
 */
package com.softwareimaging.jpdf.core.pobjects.graphics;

import com.softwareimaging.android.graphics.AffineTransform;
import com.softwareimaging.android.graphics.BasicStroke;
import com.softwareimaging.android.graphics.Canvas;
import com.softwareimaging.android.graphics.Color;
import com.softwareimaging.android.graphics.Paint;
import com.softwareimaging.geometry.PathData;
import com.softwareimaging.graphics.IClip;
import com.softwareimaging.jpdf.core.util.Defs;
import com.softwareimaging.logger.Logger;

import java.util.Vector;

/**
 * <p>The PDF viewer application maintains an internal data structure called the
 * graphics state that holds current graphics control parameters. These
 * parameters define the global framework within which the graphics operators
 * execute.</p>
 * <p/>
 * <p>The graphics state is initialized at the beginning of each page, using the
 * default values specified in Tables 4.2 and 4.3. Table 4.2 lists those
 * graphics state parameters that are device-independent and are appropriate
 * to specify in page descriptions. The parameters listed in Table 4.3 control
 * details of the rendering (scan conversion) process and are device-dependent;
 * a page description that is intended to be device-independent should not
 * modify these parameters.</p>
 * <p/>
 * <h2>Graphics State Stack Info</h2>
 * <p>A well-structured PDF document typically contains many graphical elements
 * that are essentially independent of each other and sometimes nested to
 * multiple levels. The graphics state stack allows these elements to make local
 * changes to the graphics state without disturbing the graphics state of the
 * surrounding environment. The stack is a LIFO (last in, first out) data
 * structure in which the contents of the graphics state can be saved and later
 * restored using the following operators:
 * <ul>
 * <li><p>The q operator pushes a copy of the entire graphics state onto the
 * stack. This is handled by the save() method in this class</li>
 * <li><p>The Q operator restores the entire graphics state to its former
 * value by popping it from the stack.  This is handled by the
 * restore() method in this class</p><li>
 * </ul>
 * <p>When a graphics state is saved a new GraphicsState object is created and
 * a pointer is set to the origional graphics state.  This creates a linked
 * list of graphics states that can be easily restored when needed (LIFO staack)
 * </p>
 * <h2>Shapes Stack</h2>
 * <p>The graphics state also manipulates the Shapes stack that contains all of
 * the rendering components dealing with graphics states.  As the content parser
 * encounters different graphic state manipulators they are added to the stack
 * and then when the page is rendered the stack (actually a vector) is read
 * in a FIFO to generate the drawing commands.</p>
 * <p/>
 * <h2>Device-independent graphics state parameters - (Table 4.2)</h2>
 * <table border="1" cellpadding="1" cellspacing="1" >
 * <tr>
 * <td><b> Paramater </b></td>
 * <td><b> Type</b></td>
 * <td><b> Value</b></td>
 * </tr>
 * <tr>
 * <td valign="top" >CTM</td>
 * <td valign="top" >array</td>
 * <td>The current transformation matrix, which maps positions from user
 * coordinates to device coordinates. This matrix is modified by each
 * application of the coordinate transformation operator, cm. Initial
 * value: a matrix that transforms default user coordinates to device
 * coordinates.</td>
 * </tr>
 * <tr>
 * <td valign="top" >clipping path</td>
 * <td valign="top" >(internal)</td>
 * <td>The current clipping path, which defines the boundary against which
 * all output is to be cropped. Initial value: the boundary of the
 * entire imageable portion of the output page.</td>
 * </tr>
 * <tr>
 * <td valign="top" >color space</td>
 * <td valign="top" >name or array</td>
 * <td>The current color space in which color values are to be interpreted.
 * There are two separate color space parameters: one for stroking and
 * one for all other painting operations. Initial value: DeviceGray.</td>
 * </tr>
 * <tr>
 * <td valign="top" >color</td>
 * <td valign="top" >(various</td>
 * <td>The current color to be used during painting operations. The type
 * and interpretation of this parameter depend on the current color
 * space; for most color spaces, a color value consists of one to four
 * numbers. There are two separate color parameters: one for stroking
 * and one for all other painting operations. Initial value: black.</td>
 * </tr>
 * <tr>
 * <td valign="top" >text state</td>
 * <td valign="top" >(various)</td>
 * <td>A set of nine graphics state parameters that pertain only to the
 * painting of text. These include parameters that select the font,
 * scale the glyphs to an appropriate size, and accomplish other effects.
 * The text state parameters are described in Section 5.2, "Text State
 * Parameters and Operators."</td>
 * </tr>
 * <tr>
 * <td valign="top" >line width</td>
 * <td valign="top" >number</td>
 * <td>The thickness, in user space units, of paths to be stroked.</td>
 * </tr>
 * <tr>
 * <td valign="top" >line cap</td>
 * <td valign="top" >integer</td>
 * <td>A code specifying the shape of the endpoints for any open path that
 * is stroked (see "Line Cap Style" on page 186). Initial value: 0, for
 * square butt caps.</td>
 * </tr>
 * <tr>
 * <td valign="top" >line join</td>
 * <td valign="top" >integer</td>
 * <td>A code specifying the shape of joints between connected segments of
 * a stroked path. Initial value: 0, for mitered joins.</td>
 * </tr>
 * <tr>
 * <td valign="top" >miter limit</td>
 * <td valign="top" >number</td>
 * <td>The maximum length of mitered line joins for stroked paths. This
 * parameter limits the length of "spikes" produced when line segments
 * join at sharp angles. Initial value: 10.0, for a miter cutoff below
 * approximately 11.5 degrees.</td>
 * </tr>
 * <tr>
 * <td valign="top" >dash pattern</td>
 * <td valign="top" >array and number</td>
 * <td>A description of the dash pattern to be used when paths are stroked.
 * Initial value: a solid line.</td>
 * </tr>
 * <tr>
 * <td valign="top" >rendering intent (Not supported)</td>
 * <td valign="top" >name</td>
 * <td>The rendering intent to be used when converting CIE-based colors to
 * device colors. Default value: RelativeColorimetric.</td>
 * </tr>
 * <tr>
 * <td valign="top" >stroke adjustment (Not supported)</td>
 * <td valign="top" >boolean</td>
 * <td>(PDF 1.2) A flag specifying whether to compensate for possible
 * rasterization effects when stroking a path with a line width that is
 * small relative to the pixel resolution of the output device..
 * Note that this is considered a device-independent parameter, even
 * though the details of its effects are device-dependent.
 * Initial value: false.</td>
 * </tr>
 * <tr>
 * <td valign="top" >blend mode (Not supported)</td>
 * <td valign="top" >name or array</td>
 * <td>(PDF 1.4) The current blend mode to be used in the transparent
 * imaging model. This parameter is implicitly reset to its initial
 * value at the beginning of execution of a transparency group XObject.
 * Initial value: Normal.</td>
 * </tr>
 * <tr>
 * <td valign="top" >soft mask (Not supported)</td>
 * <td valign="top" >dictionary or name</td>
 * <td>(PDF 1.4) A soft-mask dictionary specifying the mask shape or mask
 * opacity values to be used in the transparent imaging model, or the
 * name None if no such mask is specified. This parameter is implicitly
 * reset to its initial value at the beginning of execution of a
 * transparency group XObject. Initial value: None.</td>
 * </tr>
 * <tr>
 * <td valign="top" >alpha constant</td>
 * <td valign="top" >number</td>
 * <td>(PDF 1.4) The constant shape or constant opacity value to be used in
 * the transparent imaging model. There are two separate alpha constant
 * parameters: one for stroking and one for all other painting
 * operations. This parameter is implicitly reset to its initial value
 * at the beginning of execution of a transparency group XObject.
 * Initial value: 1.0.</td>
 * </tr>
 * <tr>
 * <td valign="top" >alpha source</td>
 * <td valign="top" >boolean</td>
 * <td>(PDF 1.4) A flag specifying whether the current soft mask and alpha
 * constant parameters are to be interpreted as shape values (true) or
 * opacity values (false). This flag also governs the interpretation of
 * the SMask entry, if any, in an image dictionary.
 * Initial value: false.</td>
 * </tr>
 * </table>
 * <p/>
 * <h2>Device-Dependent graphics state parameters - (Table 4.3) </h2>
 * <p><b>Currently Not supported</b></p>
 *
 * @since 1.0
 */
public class GraphicsState {

    // allow over paint support for fill and stroke.  Still experimental
    // enabled buy default but can be turned off if required.
    private static boolean enabledOverpaint;

    static {
        enabledOverpaint =
                Defs.sysPropertyBoolean("com.softwareimaging.jpdf.core.overpaint",
                        true);
    }


    // Current transformation matrix.
    private AffineTransform CTM = new AffineTransform();

    // Specifies the shape of the endpoint for any open path.
    private Paint.Cap lineCap = Paint.Cap.BUTT;

    // Specifies the thickness in user parse of a path to be stroked.
    private float lineWidth = 1;

    // Specifies the shape of the joints between connected segments.
    private Paint.Join lineJoin = Paint.Join.MITER;

    // Maximum length of mitered line join for stroked paths.
    private float miterLimit = 10;

    // Stores the lengths of the dash segments
    private float[] dashArray = null;

    // Stores the current dash phase
    private float dashPhase = 0.0f;

    // color used to fill a stroked path.
    private Color fillColor = new Color(Color.BLACK);

    // The current color to be used during a painting operation.
    private Color strokeColor = new Color(Color.BLACK);

    // Stroking alpha constant for "CA"
    private float strokeAlpha = 1.0f;

    // Fill alpha constant for "ca" or non-stroking alpha
    private float fillAlpha = 1.0f;

    // Color space of the fill color, non-stroking colour.
    private PColorSpace fillColorSpace = new DeviceGray(null, null);

    // Color space of the stroke color, stroking colour.
    private PColorSpace strokeColorSpace = new DeviceGray(null, null);

    // Set of graphics stat parameter  for painting text.
    private TextState textState = new TextState();

    // parent graphics state if it exists.
    private GraphicsState parentGraphicState = null;

    // all shapes associated with this graphics state.
    private Shapes shapes;

    // current clipping area.
    private Shapes.Clip clip;

    // over print mode
    private int overprintMode;
    // over printing stroking
    private boolean overprintStroking;
    // over printing everything other than stroking
    private boolean overprintOther;

    /**
     * Constructs a new <code>GraphicsState</code> which will have default
     * values and shapes specified by the shapes stack.
     *
     * @param shapes stack containing pages graphical elements.
     */
    public GraphicsState(Shapes shapes) {
        this.shapes = shapes;
    }

    /**
     * Constructs a new <code>GraphicsState</code> that is a copy of
     * the specified <code>GraphicsState</code> object.
     *
     * @param parentGraphicsState the <code>GraphicsState</code> object to copy
     */
    public GraphicsState(GraphicsState parentGraphicsState) {

        // copy/clone the parentGraphicsState and return the new object.

        CTM = new AffineTransform(parentGraphicsState.CTM);

        lineCap = parentGraphicsState.lineCap;
        lineWidth = parentGraphicsState.lineWidth;
        miterLimit = parentGraphicsState.miterLimit;
        lineJoin = parentGraphicsState.lineJoin;

        fillColor = new Color(parentGraphicsState.fillColor.getRGB());

        strokeColor = new Color(parentGraphicsState.strokeColor.getRGB());
        shapes = parentGraphicsState.shapes;
        if (parentGraphicsState.clip != null) {
        	clip = new Shapes.Clip(parentGraphicsState.clip);
        }

        fillColorSpace = parentGraphicsState.fillColorSpace;
        strokeColorSpace = parentGraphicsState.strokeColorSpace;
        textState = new TextState(parentGraphicsState.textState);
        dashPhase = parentGraphicsState.getDashPhase();
        dashArray = parentGraphicsState.getDashArray();

        // copy over printing attributes
        overprintMode = parentGraphicsState.getOverprintMode();
        overprintOther = parentGraphicsState.isOverprintOther();
        overprintStroking = parentGraphicsState.isOverprintStroking();

    }

    /**
     * Sets the Shapes vector.
     *
     * @param shapes shapes for a given content stream.
     */
    public void setShapes(Shapes shapes) {
        this.shapes = shapes;
    }

    /**
     * Concatenates this transform with a translation transformation specified
     * by the graphics state current CTM.  An updated CTM is added to the
     * shapes stack.
     *
     * @param x the distance by which coordinates are translated in the
     *          X axis direction
     * @param y the distance by which coordinates are translated in the
     *          Y axis direction
     */
    public void translate(float x, float y) {
        CTM.translate((float)x, (float)y);
        shapes.add(new AffineTransform(CTM));
    }

    /**
     * Concatenates this transform with a scaling transformation specified
     * by the graphics state current CTM.  An update CTM is added to the
     * shapes stack.
     *
     * @param x the factor by which coordinates are scaled along the
     *          X axis direction
     * @param y the factor by which coordinates are scaled along the
     *          Y axis direction
     */
    public void scale(float x, float y) {
        CTM.scale(x, y);
        shapes.add(new AffineTransform(CTM));
    }

    /**
     * Sets the graphics state CTM to a new transform, the old CTM transform is
     * lost.  The new CTM value is added to the shapes stack.
     *
     * @param af the AffineTranform object to set the CTM to.
     */
    public void set(AffineTransform af) {
        CTM = new AffineTransform(af);
        shapes.add(new AffineTransform(CTM));
    }

    /**
     * Saves the current graphics state.
     *
     * @return copy of the current graphics state.
     * @see #restore()
     */
    public GraphicsState save() {
        GraphicsState gs = new GraphicsState(this);
        gs.parentGraphicState = this;
        return gs;
    }

    /**
     * Concatenate the specified ExtGState to the current graphics state.
     * <b>Note: </b> currently only a few of the ExtGState attributes are
     * supported.
     *
     * @param extGState external graphics state.
     * @see com.softwareimaging.jpdf.core.pobjects.graphics.ExtGState
     */
    public void concatenate(ExtGState extGState) {
        // Map over extGState attributes if present.

        // line width
        if (extGState.getLineWidth() != null) {
            setLineWidth(extGState.getLineWidth().floatValue());
        }
        // line cap style
        if (extGState.getLineCapStyle() != null) {
            setLineCap(extGState.getLineCapStyle().intValue());
        }
        // line join style
        if (extGState.getLineJoinStyle() != null) {
            setLineJoin(extGState.getLineJoinStyle().intValue());
        }
        // line miter limit
        if (extGState.getMiterLimit() != null) {
            setMiterLimit(extGState.getMiterLimit().floatValue());
        }
        // line dash pattern
        if (extGState.getLineDashPattern() != null) {
            Vector<?> dasshPattern = extGState.getLineDashPattern();
            try {
                setDashArray((float[]) dasshPattern.elementAt(0));
                setDashPhase(((Number) dasshPattern.elementAt(1)).floatValue());
            }
            catch (ClassCastException e) {
            	if (Logger.areErrorsSupported()) {
            		Logger.outputError("Dash cast error: ", e);
            	}
            }
        }
        // Stroking alpha
        if (extGState.getNonStrokingAlphConstant() != null) {
            setFillAlpha(extGState.getNonStrokingAlphConstant().floatValue());
        }
        // none stroking alpha
        if (extGState.getStrokingAlphConstant() != null) {
            setStrokeAlpha(extGState.getStrokingAlphConstant().floatValue());
        }

        if (extGState.getOverprintMode() != null) {
            setOverprintMode(extGState.getOverprintMode().intValue());
        }

        // apply over print logic
        processOverPaint(extGState.getOverprint(), extGState.getOverprintFill());
    }

    /**
     * Process the OP and op over printing attributes.
     *
     * @param OP OP graphics state param
     * @param op op graphics state param
     */
    private void processOverPaint(Boolean OP, Boolean op) {
        // PDF 1.2 and earlier, only a single overprint parameter
        if (enabledOverpaint &&
                OP != null && op == null && overprintMode == 1) {
            overprintStroking = OP;
            overprintOther = overprintStroking;
        }
        // PDF 1.2 and over
        else if (OP != null) {
//            overprintStroking = OP.booleanValue();
//            overprintOther = op.booleanValue();
        }
        // else default inits of false for each is fine.
    }

    /**
     * Restores the previously saved graphic state.
     *
     * @return the last saved graphics state.
     * @see #save()
     */
    public GraphicsState restore() {
        // Return the graphics state which is pointed to by the
        // parentGraphicState.

        // make sure we have a parent to restore to
        if (parentGraphicState != null) {
            // Add the parents CTM to the stack,
            parentGraphicState.set(parentGraphicState.CTM);
            // Add the parents clip to the stack
            if (parentGraphicState.clip != null) {
            	Shapes.Clip newClip = new Shapes.Clip(parentGraphicState.clip);
                parentGraphicState.shapes.addClipCommand(newClip);
            } else {
                parentGraphicState.shapes.addNoClipCommand();
            }
            // Update the stack with the parentGraphicsState stack.
            parentGraphicState.shapes.add(
                    new BasicStroke((float)parentGraphicState.lineWidth,
                            parentGraphicState.lineCap,
                            parentGraphicState.lineJoin,
                            parentGraphicState.miterLimit,
                            parentGraphicState.dashArray,
                            parentGraphicState.dashPhase));

            // Note the following aren't officially part of the graphic state parameters
            // but they need to be restored in order to show some PDF content correctly

            // restore the fill color of the last paint
            parentGraphicState.shapes.add(parentGraphicState.getFillColor());

            // stroke Color
//            parentGraphicState.shapes.add(parentGraphicState.getStrokeColor());

        }

        return parentGraphicState;
    }

    /**
     * Set the graphics state clipping area.  The new clipping area is
     * intersected with the current current clip to generate the new clipping
     * area which is saved to the stack.
     *
     * @param newClip new clip for graphic state.
     */
    public void setClip(PathData newClip) {
        if (newClip != null) {
        	PathData clipPath = new PathData(newClip);
        	clipPath.setTransform(Canvas.toMatrix(getCTM()));
        	if (clip == null) {
            	clip = new Shapes.Clip(clipPath);
        	} else {
            	clip = new Shapes.Clip(clip);
        		clip.add(clipPath);
        	}

            // add new clip shape to stack
            shapes.addClipCommand(new Shapes.Clip(clip));
        } else {
            // add a null clip for a null shape, should not normally happen
            clip = null;
            shapes.addNoClipCommand();
        }
    }

    public Shapes.Clip getClip() {
        return clip;
    }

    public AffineTransform getCTM() {
        return CTM;
    }

    public void setCTM(AffineTransform ctm) {
        CTM = ctm;
    }

    public Paint.Cap getLineCap() {
        return lineCap;
    }

    public void setLineCap(Paint.Cap lineCap) {
        this.lineCap = lineCap;
    }

    public void setLineCap(int lineCap) {
    	switch (lineCap){
   		case 0:
   	    	this.lineCap = Paint.Cap.BUTT;
   	    	break;
   		case 1: 
   	    	this.lineCap = Paint.Cap.ROUND;
   	    	break;
   		case 2: 
   	    	this.lineCap = Paint.Cap.SQUARE;
   	    	break;
    	}
    }

    public float getLineWidth() {
        return lineWidth;
    }

    public void setLineWidth(float lineWidth) {
        // Automatic Stroke Adjustment
        if (lineWidth <= Float.MIN_VALUE || lineWidth >= Float.MAX_VALUE ||
                lineWidth == 0) {
            // set line width to a very small none zero number. 
            this.lineWidth = 0.001f;
        } else {
            this.lineWidth = lineWidth;
        }

    }

    public Paint.Join getLineJoin() {
        return lineJoin;
    }

    public void setLineJoin(Paint.Join lineJoin) {
        this.lineJoin = lineJoin;
    }

    public void setLineJoin(int lineJoin) {
    	switch (lineJoin){
    	case 0:
	    	this.lineJoin = Paint.Join.MITER;
    		break;
    	case 1:
	    	this.lineJoin = Paint.Join.ROUND;
    		break;
    	case 2:
	    	this.lineJoin = Paint.Join.BEVEL;
    		break;
    	}
    	
    }

    public float[] getDashArray() {
        return dashArray;
    }

    public void setDashArray(float[] dashArray) {
        this.dashArray = dashArray;
    }

    public float getDashPhase() {
        return dashPhase;
    }

    public void setDashPhase(float dashPhase) {
        this.dashPhase = dashPhase;
    }

    public float getMiterLimit() {
        return miterLimit;
    }

    public void setMiterLimit(float miterLimit) {
        this.miterLimit = miterLimit;
    }

    public Color getFillColor() {
        return fillColor;
    }

    public void setFillColor(Color fillColor) {
        this.fillColor = fillColor;
    }

    public Color getStrokeColor() {
        return strokeColor;
    }

    public void setStrokeAlpha(float alpha) {
        strokeAlpha = alpha;
    }

    public float getStrokeAlpha() {
        return strokeAlpha;
    }

    public void setFillAlpha(float alpha) {
        fillAlpha = alpha;
    }

    public float getFillAlpha() {
        return fillAlpha;
    }

    public void setStrokeColor(Color strokeColor) {
        this.strokeColor = strokeColor;
    }

    public PColorSpace getFillColorSpace() {
        return fillColorSpace;
    }

    public void setFillColorSpace(PColorSpace fillColorSpace) {
        this.fillColorSpace = fillColorSpace;
    }

    public PColorSpace getStrokeColorSpace() {
        return strokeColorSpace;
    }

    public void setStrokeColorSpace(PColorSpace strokeColorSpace) {
        this.strokeColorSpace = strokeColorSpace;
    }

    public TextState getTextState() {
        return textState;
    }

    public void setTextState(TextState textState) {
        this.textState = textState;
    }

    public int getOverprintMode() {
        return overprintMode;
    }

    public boolean isOverprintStroking() {
        return overprintStroking;
    }

    public boolean isOverprintOther() {
        return overprintOther;
    }

    public void setOverprintMode(int overprintMode) {
        this.overprintMode = overprintMode;
    }

    public void setOverprintStroking(boolean overprintStroking) {
        this.overprintStroking = overprintStroking;
    }

    public void setOverprintOther(boolean overprintOther) {
        this.overprintOther = overprintOther;
    }
}

