/*
 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package com.sun.pdfview.pattern;

import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.PaintContext;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import com.sun.pdfview.PDFObject;
import com.sun.pdfview.PDFPage;
import com.sun.pdfview.PDFPaint;
import com.sun.pdfview.PDFParser;
import com.sun.pdfview.PDFRenderer;

/**
 * A type 1 (tiling) pattern
 */
public class PatternType1 extends PDFPattern {
    /** paint types */
    public static final int PAINT_COLORED = 1;
    public static final int PAINT_UNCOLORED = 2;
   
    /** tiling types */
    public static final int TILE_CONSTANT = 1;
    public static final int TILE_NODISTORT = 2;
    public static final int TILE_FASTER = 3;
    
    /** the resources used by the image we will tile */
    private HashMap<String,PDFObject> resources;
    
    /** the paint type (colored or uncolored) */
    private int paintType;
    
    /** the tiling type (constant, no distort or faster) */
    private int tilingType;
    
    /** the bounding box of the tile, in tile space */
    private Rectangle2D bbox;
    
    /** the horiztonal tile spacing, in tile space */
    private int xStep;
    
    /** the vertical spacing, in tile space */
    private int yStep;
    
    /** the stream data */
    private byte[] data;
    
    /** Creates a new instance of PatternType1 */
    public PatternType1() {
        super(1);    
    }
    
    /**
     * Parse the pattern from the PDFObject
     *
     * Note the resources passed in are ignored...
     */
    @Override
	protected void parse(PDFObject patternObj, Map rsrc) throws IOException
    {
        this.data = patternObj.getStream();
        
        this.resources = patternObj.getDictRef("Resources").getDictionary();
        this.paintType = patternObj.getDictRef("PaintType").getIntValue();
        this.tilingType = patternObj.getDictRef("TilingType").getIntValue();
        
        PDFObject bboxObj = patternObj.getDictRef("BBox");
	this.bbox= new Rectangle2D.Float(bboxObj.getAt(0).getFloatValue(),
                                    bboxObj.getAt(1).getFloatValue(),
                                    bboxObj.getAt(2).getFloatValue(),
                                    bboxObj.getAt(3).getFloatValue());
        
        this.xStep = patternObj.getDictRef("XStep").getIntValue();
        this.yStep = patternObj.getDictRef("YStep").getIntValue();
    }
    
    /** 
     * Create a PDFPaint from this pattern and set of components.  
     * This creates a buffered image of this pattern using
     * the given paint, then uses that image to create the correct 
     * TexturePaint to use in the PDFPaint.
     *
     * @param basePaint the base paint to use, or null if not needed
     */
    @Override
	public PDFPaint getPaint(PDFPaint basePaint) {
        // create the outline of the pattern in user space by creating
        // a box with width xstep and height ystep.  Transform that
        // box using the pattern's matrix to get the user space
        // bounding box
        Rectangle2D anchor = new Rectangle2D.Double(getBBox().getMinX(),
                                                    getBBox().getMinY(),
                                                    getXStep(),
                                                    getYStep());
        //anchor = getTransform().createTransformedShape(anchor).getBounds2D();
        
        // now create a page bounded by the pattern's user space size
        final PDFPage page = new PDFPage(getBBox(), 0);
        
        // set the base paint if there is one
        if (basePaint != null) {
            page.addFillPaint(basePaint);
            page.addStrokePaint(basePaint);
        }
        
        // undo the page's transform to user space
        /*
        AffineTransform xform =
            new AffineTransform(1, 0, 0, -1, 0, getYStep());
            //new AffineTransform(1, 0, 0, -1, 0, getBBox().getHeight());
        page.addXform(xform);
        */
        
        // now parse the pattern contents
        PDFParser prc = new PDFParser(page, this.data, getResources());
        prc.go(true);
        
        int width = (int) getBBox().getWidth();
        int height = (int) getBBox().getHeight();
        
        // get actual image
        Paint paint = new Paint() {
            @Override
			public PaintContext createContext(ColorModel cm, 
                                              Rectangle deviceBounds, 
                                              Rectangle2D userBounds,
                                              AffineTransform xform,
                                              RenderingHints hints) 
            {
                ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
                ColorModel model = new ComponentColorModel(cs, 
                                                           true, 
                                                           false, 
                                                           Transparency.TRANSLUCENT,
                                                           DataBuffer.TYPE_BYTE);
           
                Rectangle2D devBBox = 
                    xform.createTransformedShape(getBBox()).getBounds2D();
                
                double[] steps = new double[] { getXStep(), getYStep() };
                xform.deltaTransform(steps, 0, steps, 0, 1);
                
                int width = (int) Math.ceil(devBBox.getWidth());
                int height = (int) Math.ceil(devBBox.getHeight());
                
                BufferedImage img = (BufferedImage) page.getImage(width, height,  
                                                                  null, null, 
                                                                  false, true);
                                                                  
                return new Type1PaintContext(model, devBBox, 
                                             (float) steps[0],
                                             (float) steps[1],
                                             img.getData());
            }
            
            @Override
			public int getTransparency() {
                return Transparency.TRANSLUCENT;
            }
        };
                                            
        
        return new TilingPatternPaint(paint, this);
    }
    
    /** get the associated resources */
    public HashMap<String,PDFObject> getResources() {
        return this.resources;
    }
    
    /** get the paint type */
    public int getPaintType() {
        return this.paintType;
    }
    
    /** get the tiling type */
    public int getTilingType() {
        return this.tilingType;
    }
    
    /** get the bounding box */
    public Rectangle2D getBBox() {
        return this.bbox;
    }
    
    /** get the x step */
    public int getXStep() {
        return this.xStep;
    }
    
    /** get the y step */
    public int getYStep() {
        return this.yStep;
    }
    
    /** 
     * This class overrides PDFPaint to paint in the pattern coordinate space
     */
    class TilingPatternPaint extends PDFPaint {
        /** the pattern to paint */
        private PatternType1 pattern;
        
        /** Create a tiling pattern paint */
        public TilingPatternPaint(Paint paint, PatternType1 pattern) {
            super (paint);
            
            this.pattern = pattern;
        }
        
        /**
         * fill a path with the paint, and record the dirty area.
         * @param state the current graphics state
         * @param g the graphics into which to draw
         * @param s the path to fill
         * @param drawn a Rectangle2D into which the dirty area (area drawn)
         * will be added.
         */
        @Override
		public Rectangle2D fill(PDFRenderer state, Graphics2D g,
                                GeneralPath s) {                        
            // first transform s into device space
            AffineTransform at = g.getTransform();
            Shape xformed = s.createTransformedShape(at);
                                    
            // push the graphics state so we can restore it
            state.push();
            
            // set the transform to be the inital transform concatentated
            // with the pattern matrix
            state.setTransform(state.getInitialTransform());
            state.transform(this.pattern.getTransform());
            
            // now figure out where the shape should be
            try {
                at = state.getTransform().createInverse();
            } catch (NoninvertibleTransformException nte) {
                // oh well (?)
            }
            xformed = at.createTransformedShape(xformed);
            
            // set the paint and draw the xformed shape
            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
            g.setPaint(getPaint());
            g.fill(xformed);
            
            // restore the graphics state
            state.pop();
            
            // return the area changed
            return s.createTransformedShape(g.getTransform()).getBounds2D();
        }
    }
    
    /** 
     * A simple paint context that uses an existing raster in device
     * space to generate pixels
     */
    class Type1PaintContext implements PaintContext {
        /** the color model */
        private ColorModel colorModel;
        
        /** the anchor box */
        private Rectangle2D bbox;
        
        /** the x offset */
        private float xstep;
        
        /** the y offset */
        private float ystep;
        
        /** the image data, as a raster in device coordinates */
        private Raster data;
        
        /**
         * Create a paint context
         */
        Type1PaintContext(ColorModel colorModel, Rectangle2D bbox,
                          float xstep, float ystep, Raster data) 
        {
            this.colorModel = colorModel;
            this.bbox = bbox;
            this.xstep = xstep;
            this.ystep = ystep;
            this.data = data;
        }
        
        @Override
		public void dispose() {
            this.colorModel = null;
            this.bbox = null;
            this.data = null;
        }
        
        @Override
		public ColorModel getColorModel() {
            return this.colorModel;
        }
        
        @Override
		public Raster getRaster(int x, int y, int w, int h) {
            ColorSpace cs = getColorModel().getColorSpace();
       
            int numComponents = cs.getNumComponents();
  
            // all the data, plus alpha channel
            int[] imgData = new int[w * h * (numComponents + 1)];
  
	    // the x and y step, as ints	
            int useXStep = (int) Math.abs(Math.ceil(this.xstep));
	    int useYStep = (int) Math.abs(Math.ceil(this.ystep));
         
            // a completely transparent pixel (alpha of 0)
            int[] emptyPixel = new int[numComponents + 1];
            int[] usePixel = new int[numComponents + 1];
       
            // for each device coordinate
            for (int j = 0; j < h; j++) {
                for (int i = 0; i < w; i ++) {
                    // figure out what pixel we are at relative to the image
                    int xloc = (x + i) - (int) Math.ceil(this.bbox.getX());
                    int yloc = (y + j) - (int) Math.ceil(this.bbox.getY());
                    
                    xloc %= useXStep;
                    yloc %= useYStep;
                    
                    if (xloc < 0) {
                        xloc = useXStep + xloc;
                    }
                    if (yloc < 0) {
                        yloc = useYStep + yloc;
                    }
                    
                    int[] pixel = emptyPixel;
                
                    // check if we are inside the image
                    if (xloc < this.data.getWidth() &&
                        yloc < this.data.getHeight()) {
                        pixel = this.data.getPixel(xloc, yloc, usePixel); 
                    } 
                            
                    int base = (j * w + i) * (numComponents + 1);
                    for (int c = 0; c < pixel.length; c++) {
                        imgData[base + c] = pixel[c];
                    }
                }
            }
            
            WritableRaster raster =
                getColorModel().createCompatibleWritableRaster(w, h);
            raster.setPixels(0, 0, w, h, imgData);
          
            Raster child = raster.createTranslatedChild(x, y);
      
            return child;
        }
    }
}