/**
* ===========================================
* Java Pdf Extraction Decoding Access Library
* ===========================================
*
* Project Info:  http://www.jpedal.org
* (C) Copyright 1997-2008, IDRsolutions and Contributors.
*
* 	This file is part of JPedal
*
    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


*
* ---------------
* PatternColorSpace.java
* ---------------
*/
package org.jpedal.color;

import org.jpedal.exception.PdfException;
import org.jpedal.io.ColorSpaceConvertor;
import org.jpedal.io.ObjectStore;
import org.jpedal.io.PdfObjectReader;
import org.jpedal.objects.GraphicsState;
import org.jpedal.objects.PdfPageData;
import org.jpedal.objects.raw.*;
import org.jpedal.parser.PdfStreamDecoder;
import org.jpedal.render.DynamicVectorRenderer;
import org.jpedal.utils.Matrix;

import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.util.Map;
import java.util.HashMap;


/**
 * handle Pattern ColorSpace (there is also a shading class)
 */
public class PatternColorSpace extends GenericColorSpace{

    //lookup tables for stored previous values
    private Map cachedPaints=new HashMap(),colorspacesObjects;

    //local copy so we can access File data
	PdfObjectReader currentPdfFile=null;

	private boolean isPrinting=false,colorsReversed;

    /**
     * Just initialises variables
     * @param isPrinting
     * @param currentPdfFile
     * @param colorspacesObjects
     */
	public PatternColorSpace(boolean isPrinting, PdfObjectReader currentPdfFile, Map colorspacesObjects){

		value = ColorSpaces.Pattern;

        //local handles
        this.isPrinting=isPrinting;
        this.colorspacesObjects=colorspacesObjects;
		this.currentPdfFile = currentPdfFile;

        //default value for color
        currentColor = new PdfColor(1.0f,1.0f,1.0f);
    }

	/**
	 * convert color value to pattern
	 */
	public void setColor(String[] value_loc,int operandCount){

        PatternObject PatternObj=(PatternObject) patterns.get(value_loc[0]);


        //if already setup just reuse
        String ref=PatternObj.getObjectRefAsString();
        if(ref!=null && cachedPaints.containsKey(ref)){
            currentColor = (PdfPaint) cachedPaints.get(ref);

            return;
        }


        /**
         * decode Pattern on first use
         */

        //ensure read
        currentPdfFile.checkResolved(PatternObj);

        //lookup table
        byte[] streamData=currentPdfFile.readStream(PatternObj,true,true,true, false,false, null);

        //type of Pattern (shading or tiling)
        final int shadingType= PatternObj.getInt(PdfDictionary.PatternType);

        // get optional matrix values
		float[][] matrix=null;
        float[] inputs=PatternObj.getFloatArray(PdfDictionary.Matrix);

        if(inputs!=null){
			
			if(shadingType==1){
				float[][] Nmatrix={{inputs[0],inputs[1],0f},{inputs[2],inputs[3],0f},{0f,0f,1f}};

                if(inputs[5]<0){ //check against adobe/PP_download and adobe/source.pdf
                    inputs[4]=0;
                    inputs[5]=0;
                }
                matrix=Nmatrix;
			}else{
				float[][] Nmatrix={{inputs[0],inputs[1],0f},{inputs[2],inputs[3],0f},{inputs[4],inputs[5],1f}};

				if(Nmatrix[2][0]<0)
					colorsReversed=true;
				else
					colorsReversed=false;

				matrix=Matrix.multiply(Nmatrix,CTM);
			}
		}


        /**
		 * setup appropriate type
		 */
		if(shadingType == 1) //tiling
            	currentColor = setupTiling(PatternObj, inputs, matrix, streamData);
		else if(shadingType == 2) //shading
			currentColor = setupShading(PatternObj,matrix);

	}

	/**
     * handle pattern made of constantly repeated pattern. If it is rotated, it gets rather messy.
	 */
	private PdfPaint setupTiling(PdfObject PatternObj,float[] inputs, float[][] matrix,byte[] streamData) {

        this.inputs=inputs;

        PdfPaint paint=null;

        boolean tesslateOntoImage =true;

        BufferedImage img=null;

        AffineTransform imageScale=null;

        //create copy of matrix unchanged;
        float[][] rawMatrix=null;
        if(matrix!=null){
            rawMatrix=new float[3][3];
            for(int y=0;y<3;y++){
                for(int x=0;x<3;x++){
                    rawMatrix[x][y]=matrix[x][y];
                }
            }
        }
        /**
         * ignore silly rotations and inputs
         */
        if(matrix!=null && matrix[0][0]!=0 && matrix[0][1]<0.001 && matrix[0][1]>-0.001)
            matrix[0][1]=0;
        if(matrix!=null && matrix[1][1]!=0 && matrix[1][0]<0.001 && matrix[1][0]>-0.001)
            matrix[1][0]=0;

        if(inputs!=null){
	        for(int aa=0;aa<6;aa++){
	            if(inputs[aa]!=0 && inputs[aa]<0.001 && inputs[aa]>-0.001)
	                inputs[aa]=0;
	        }
        }

        if(matrix!=null && matrix[0][0]<0 && matrix[1][1]<0){
           
        	matrix[0][0]=-matrix[0][0];
            matrix[1][1]=-matrix[1][1];

            if(inputs!=null){
                inputs[0]=-inputs[0];
                inputs[3]=-inputs[3];
            }
        }

        //flag to track rotation which needs custom handling
        boolean isRotated=false,isUpsideDown=false,isSideways=false;

        /**
         * work out if upside down and manipulate matrix so will appear on tile
         */
        if(matrix!=null){

            isRotated=matrix[1][0]!=0 && matrix[0][1]!=0 && matrix[0][0]!=0 && matrix[1][1]!=0;

            //ignore slight rotations
            if(isRotated && matrix[0][0]!=0 && matrix[0][0]<0.001 && matrix[1][1]!=0 && matrix[1][1]<0.001){
                isRotated=false;
                matrix[0][0]=-matrix[0][1];
                matrix[1][1]=-matrix[1][0];

                matrix[1][0]=0;
                matrix[0][1]=0;
            }

            if(isRotated && matrix[0][0]>0 && matrix[0][1]<0 &&
            	 matrix[1][0]>0 && matrix[1][1]>0){
            }else if(isRotated && matrix[0][0]<0 && matrix[0][1]<0 &&
               	 matrix[1][0]<0 && matrix[1][1]>0){

            	matrix[0][0]=-matrix[0][0];
            	matrix[1][0]=-matrix[1][0];

            	isSideways=true;
            }
            
            isUpsideDown=(matrix[1][1]<0 || matrix[0][1]<0);

            //allow for this special case
            if(matrix[0][0]>0 && matrix[0][1]<0 && matrix[1][0]>0 && matrix[1][1]>0)
                isUpsideDown=false;

            //breaks Scooby page so ignore
            if(matrix[0][0]>0.1f && (isRotated || isUpsideDown))
            	tesslateOntoImage =false;
            
            //used by rotation code
            if(isUpsideDown && matrix[0][1]>0 && matrix[1][0]>0)
                isUpsideDown=false;

        }

        /**
         * get values for pattern for PDF object
         */
        int XStep=(int) PatternObj.getFloatNumber(PdfDictionary.XStep);
        int YStep=(int) PatternObj.getFloatNumber(PdfDictionary.YStep);

        //ensure positive
        if(XStep<0)
            XStep=-XStep;
        if(YStep<0)
            YStep=-YStep;
        
        float dx=0,dy=0;

        //position of tile if inputs not null and less than 1
        int input_dxx=0, input_dyy=0;

        /**
         * adjust matrix to suit
         **/
        if(matrix!=null ){

            //allow for upside down
            if(matrix[1][1]<0)
                matrix[2][1]=YStep;
                
            // needed for reporttype file
            if(matrix[1][0]!=0.0)
                matrix[2][1]=-matrix[1][0];

        }

        

        /**
         * convert stream into an DynamicVector object we can then draw onto screen or tile
         */
        ObjectStore localStore = new ObjectStore();
        DynamicVectorRenderer glyphDisplay = decodePatternContent(PatternObj, matrix, streamData, localStore);

        /**
         * if image is generated larger than slot we draw it into we
         * will lose definition. To avoid this, we draw at full size and
         * scale only when drawn onto page
         */
        //workout unscaled tile size
        float rawWidth=0,rawHeight=0;
        boolean isDownSampled=false;

        if(matrix!=null){
            rawWidth=matrix[0][0];
            if(rawWidth==0)
                rawWidth=matrix[0][1];
            if(rawWidth<0)
                rawWidth=-rawWidth;
            rawHeight=matrix[1][1];
            if(rawHeight==0)
                rawHeight=matrix[1][0];
            if(rawHeight<0)
                rawHeight=-rawHeight;
        }

        if(matrix!=null){ //this scales/rotated image onto tile - Java cannot handle this so we do it onto an unrotated tile

            //image scaled up to fit so create large tile and then draw upscaled version onto it
            if(inputs!=null && inputs[0]>1 && inputs[3]>1 && !isRotated){

                img=new BufferedImage((int)(XStep*rawWidth),(int)(YStep*rawHeight), BufferedImage.TYPE_INT_ARGB);
                Graphics2D g2=img.createGraphics();
                g2.setClip(new Rectangle(0,0,img.getWidth(),img.getHeight()));
                glyphDisplay.paint(g2,null,new AffineTransform(matrix[0][0], matrix[0][1], matrix[1][0],matrix[1][1], inputs[4],-inputs[5]/2) ,null,false,false);
              
            }else{ //buffer onto unrotated tile

                BufferedImage tileImg=null; //tile for image as faster to draw ONCE and then replicate image

                /**
                 * workout dx,dy and ensure positive and scaled
                 */
                dx=matrix[0][0];
                dy=matrix[1][1];

                if(dx==0)
                    dx=matrix[0][1];
                if(dx<0)
                    dx=-dx;

                if(dy==0)
                    dy=matrix[1][0];
                if(dy<0)
                    dy=-dy;

                dx=dx*XStep;
                dy=dy*YStep;

                /**
                 * workout size required for image
                 */
                int imgW=XStep,imgH=YStep; //default values for tile size

                if(!isRotated){
                /**
                 * special cases
                 */
                if(isUpsideDown){

                    int xCount=(int)(XStep/dx),yCount=(int)(YStep/dy);

                    if(xCount>0 && yCount>0){
                        imgW=(int)((xCount+1)*dx);
                        imgH=(int)((yCount+1)*dy);

                        XStep=imgW;
                        YStep=imgH;
                    }
                }else if(inputs!=null && inputs[0]>0 && inputs[0]<1 && inputs[3]>0 && inputs[3]<1){
                    imgW=(int)(dx);
                    imgH=(int)(dy);

                    if(imgH==0)
                    imgH=1;

                    //workout offsets
                    input_dxx=(int)inputs[4];
                    if(input_dxx>XStep){
                        while(input_dxx>0){
                            input_dxx=input_dxx-XStep;

                            if(input_dxx==0)
                                break;
                        }
                        
                        input_dxx=input_dxx/2;
                    }

                    //workout offsets
                    input_dyy=(int)inputs[5];
                    if(input_dyy>imgH){
                        while(input_dyy>0)
                        input_dyy=input_dyy-imgH;
                    }
                }

                

                if(isDownSampled){
                    img=new BufferedImage((int)(rawWidth+.5f),(int)(rawHeight+.5f),BufferedImage.TYPE_INT_ARGB);
                    imageScale=AffineTransform.getScaleInstance(XStep/rawWidth,YStep/rawHeight);
                }else
                    img=new BufferedImage(imgW,imgH,BufferedImage.TYPE_INT_ARGB);

                Graphics2D g2=img.createGraphics();
                AffineTransform defaultAf=g2.getTransform();

                g2.setClip(new Rectangle(0,0,img.getWidth(),img.getHeight()));

                /**
                 * allow for tile not draw from 0,0
                 */
                int startX=0;
                Rectangle actualTileRect=glyphDisplay.getOccupiedArea().getBounds();


                int tileW,tileH;
                int dxx=0,dyy=0;
                if(actualTileRect.x<0){
                    tileW=actualTileRect.width-actualTileRect.x;
                    dxx=actualTileRect.x;
                }else
                    tileW=actualTileRect.width+actualTileRect.x;

                if(actualTileRect.y<0){
                    tileH=actualTileRect.height-actualTileRect.y;
                    dyy=actualTileRect.y;
                }else
                    tileH=actualTileRect.height+actualTileRect.y;

                if(tileH==0)
                tileH=1;
                
                if(tesslateOntoImage){
                    tileImg=new BufferedImage(tileW, tileH, BufferedImage.TYPE_INT_ARGB);

                    Graphics2D tileG2=tileImg.createGraphics();
                    tileG2.translate(-dxx,-dyy);
                    glyphDisplay.paint(tileG2,null,null,null,false,false);
                }

                int rectX=actualTileRect.x;
                if(rectX<0 && !tesslateOntoImage)
                    startX= (int) (-rectX*matrix[0][0]);

                
                    //if tile is smaller than Xstep,Ystep, tesselate to fill
                    float max=YStep;
                    if(tesslateOntoImage)
                    max=YStep+(tileImg.getHeight()*2);

                    for(float y=0;y<max;y=y+dy){
                        for(float x=startX;x<XStep;x=x+dx){

                            if(isUpsideDown)
                                g2.translate(x,-y);
                            else
                                g2.translate(x,y);

                            if(tesslateOntoImage){
                            	AffineTransform tileAff=new AffineTransform();
                                ColorSpaceConvertor.drawImage(g2,tileImg,tileAff,null);
                            }else
                                glyphDisplay.paint(g2,null,imageScale,null,false,false);

                            g2.setTransform(defaultAf);
                        }
                    }
                }
            }
        }else{  //no matrix value so just draw onto tile

            if(isDownSampled){
                img=new BufferedImage((int)(rawWidth+.5f),(int)(rawHeight+.5f),BufferedImage.TYPE_INT_ARGB); //.5 to allow for rounding of decimal
                imageScale=AffineTransform.getScaleInstance(XStep/rawWidth,YStep/rawHeight);
            }else
                img=new BufferedImage(XStep,YStep,BufferedImage.TYPE_INT_ARGB);

            Graphics2D g2=img.createGraphics();

            glyphDisplay.paint(g2,null,null,null,false,false);

            //flip now once for speed if upside down
            if(isUpsideDown && img.getHeight()>1){
                AffineTransform flip=new AffineTransform();
                flip.translate(0, img.getHeight());
                flip.scale(1, -1);
                AffineTransformOp invert =new AffineTransformOp(flip,ColorSpaces.hints);
                img=invert.filter(img,null);
            }
        }

        //now delete any stored images used in content
        localStore.flush();

        /**
         * create paint using image or decoded content if rotated
         */
        if(img!=null)
            paint=new PdfTexturePaint(img,  new Rectangle(input_dxx, input_dyy, img.getWidth() , img.getHeight()));

        if(isRotated){

            /** useful debug code to isolate just one pattern
            System.out.println(PatternObj.getObjectRefAsString());
            if(!PatternObj.getObjectRefAsString().equals("19 0 R")){
                paint=new PdfColor(255,0,0);
                cachedPaints.put(PatternObj.getObjectRefAsString(),paint);
                return paint;
            }
            /**/
            paint = new RotatedTexturePaint(isSideways, rawMatrix, PatternObj, tesslateOntoImage, glyphDisplay, matrix, XStep, YStep, dx, dy, imageScale);
        }


        if(paint!=null)
            cachedPaints.put(PatternObj.getObjectRefAsString(),paint);

        return paint;
    }

    private DynamicVectorRenderer decodePatternContent(PdfObject PatternObj, float[][] matrix, byte[] streamData, ObjectStore localStore) {
        PdfObject Resources=PatternObj.getDictionary(PdfDictionary.Resources);

        //decode and create graphic of glyph
        PdfStreamDecoder glyphDecoder=new PdfStreamDecoder(colorspacesObjects);
        glyphDecoder.setStreamType(PdfStreamDecoder.PATTERN);

        glyphDecoder.setStore(localStore);

        //glyphDecoder.setMultiplier(multiplyer);


        DynamicVectorRenderer glyphDisplay=new DynamicVectorRenderer(0,false,20,localStore);
        glyphDisplay.setOptimisedRotation(false);

        try{
            glyphDecoder.init(false,true,7,0,new PdfPageData(),0,glyphDisplay,currentPdfFile);

            /**read the resources for the page*/
            if (Resources != null){
                currentPdfFile.checkResolved(Resources);
                glyphDecoder.readResources(Resources,true);
            }
            glyphDecoder.setDefaultColors(gs.getStrokeColor(),gs.getNonstrokeColor());

            /**
             * setup matrix so scales correctly
             **/
            GraphicsState currentGraphicsState=new GraphicsState(0,0);
            //multiply to get new CTM
            if(matrix!=null)
                currentGraphicsState.CTM =matrix;


            glyphDecoder.decodePageContent(null,0,0, currentGraphicsState, streamData);


        } catch (PdfException e1) {
            e1.printStackTrace();
        }


        //flush as image now created
        glyphDecoder=null;
        return glyphDisplay;
    }

    /**
	 */
	private PdfPaint setupShading(PdfObject PatternObj,float[][] matrix) {

		/**
		 * get the shading object
		 */

		PdfObject Shading=PatternObj.getDictionary(PdfDictionary.Shading);
		
		/**
		 * work out colorspace
		 */
		PdfObject ColorSpace=Shading.getDictionary(PdfDictionary.ColorSpace);
		
		//convert colorspace and get details
		GenericColorSpace newColorSpace=ColorspaceFactory.getColorSpaceInstance(false, currentPdfFile,
                ColorSpace, colorspacesObjects, null, false);

        //use alternate as preference if CMYK
        if(newColorSpace.getID()==ColorSpaces.ICC && ColorSpace.getParameterConstant(PdfDictionary.Alternate)==ColorSpaces.DeviceCMYK)
                newColorSpace=new DeviceCMYKColorSpace();
        
		
		if(Shading==null){
			return null;
		}else{
            return null;
            /**/
        }
	}
}
