/*
 *    Geotools2 - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2002, Geotools Project Managment Committee (PMC)
 *
 *    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;
 *    version 2.1 of the License.
 *
 *    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.
 *
 */
package org.geotools.renderer.shape.shapehandler.simple;

import java.awt.geom.Point2D;
import java.nio.ByteBuffer;

import org.geotools.data.shapefile.shp.ShapeHandler;
import org.geotools.data.shapefile.shp.ShapeType;
import org.geotools.renderer.shape.GeometryHandlerUtilities;
import org.geotools.renderer.shape.ShapefileRenderer;
import org.geotools.renderer.shape.SimpleGeometry;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;

import com.vividsolutions.jts.geom.Envelope;

/**
 * A ShapeHandler that reads PointHandler objects from a file.  It returns a SimpleGeometry and decimates all points that
 * map to the same screen location.
 * 
 * @author jeichar
 * @since 2.1.x
 * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/extension/shapefile-renderer/src/main/java/org/geotools/renderer/shape/shapehandler/simple/PolygonHandler.java $
 */
public class PolygonHandler implements ShapeHandler {

	private ShapeType type;

	private Envelope bbox;

	double spanx, spany;

	private MathTransform mt;

	/**
	 * Create new instance
	 * 
	 * @param type
	 *            the type of shape.
	 * @param env
	 *            the area that is visible. If shape is not in area then skip.
	 * @param mt
	 *            the transform to go from data to the envelope (and that should
	 *            be used to transform the shape coords)
	 * @param hasOpacity 
	 */
	public PolygonHandler(ShapeType type, Envelope env, MathTransform mt, boolean hasOpacity)
			throws TransformException {
                if (mt == null) {
                    throw new NullPointerException();
                }
		this.type = type;
		this.bbox = env;
		this.mt = mt;
        	Point2D span = GeometryHandlerUtilities.calculateSpan(mt,0,0);
        	this.spanx = span.getX() ;
        	this.spany = span.getY() ;
	}

	/**
	 * @see org.geotools.data.shapefile.shp.ShapeHandler#getShapeType()
	 */
	public ShapeType getShapeType() {
		return type;
	}

	public Object read(ByteBuffer buffer, ShapeType type) {
		if (type == ShapeType.NULL) {
			return null;
		}

		Envelope geomBBox = GeometryHandlerUtilities.readBounds(buffer);

		if (!bbox.intersects(geomBBox)) {
			return null;
		}

		boolean bboxdecimate = geomBBox.getWidth() <= spanx
				&& geomBBox.getHeight() <= spany;
		int numParts = buffer.getInt();
		int numPoints = buffer.getInt(); // total number of points

		int[] partOffsets = new int[numParts];

		// points = new Coordinate[numPoints];
		for (int i = 0; i < numParts; i++) {
			partOffsets[i] = buffer.getInt();
		}
		double[][] coords = new double[numParts][];
		double[][] transformed = new double[numParts][];
		// if needed in future otherwise all references to a z are commented
		// out.
		// if( dimensions==3 )
		// z=new double[numParts][];

		int finish, start = 0;
		int length = 0;
		if (bboxdecimate) {
			coords = new double[1][];
			coords[0] = new double[4];
			transformed = new double[1][];
			transformed[0] = new double[8];
			coords[0][0] = buffer.getDouble();
			coords[0][1] = buffer.getDouble();
			try {
				mt.transform(coords[0], 0, transformed[0], 0, 1);
			} catch (Exception e) {
				ShapefileRenderer.LOGGER.severe("could not transform coordinates "
						+ e.getLocalizedMessage());
				transformed[0] = coords[0];
			}
			transformed[0][2] = transformed[0][0];
			transformed[0][3] = transformed[0][1] + 0.1;
			transformed[0][4] = transformed[0][0] + 0.1;
			transformed[0][5] = transformed[0][1] + 0.1;
			transformed[0][6] = transformed[0][0];
			transformed[0][7] = transformed[0][1];
		} else {
			Envelope partEnvelope = new Envelope();
			int partsInBBox = 0;

			for (int part = 0; part < numParts; part++) {
				start = partOffsets[part];
				partEnvelope.init();

				if (part == (numParts - 1)) {
					finish = numPoints;
				} else {
					finish = partOffsets[part + 1];
				}

				length = finish - start;
				int totalDoubles = length * 2;
				coords[part] = new double[totalDoubles];
				int readDoubles = 0;
				int currentDoubles = 0;
				for (; currentDoubles < totalDoubles;) {
					try {
						coords[part][readDoubles] = buffer.getDouble();
						readDoubles++;
						currentDoubles++;
						coords[part][readDoubles] = buffer.getDouble();
						readDoubles++;
						currentDoubles++;
					} catch (Exception e) {
						e.printStackTrace();
					}
					if (collapsePoints(coords, part, totalDoubles, readDoubles,
							currentDoubles)) {
						readDoubles -= 2;
					} else {
						partEnvelope.expandToInclude(
								coords[part][readDoubles - 2],
								coords[part][readDoubles - 1]);
					}
				}
				if (!partEnvelope.intersects(bbox)) {
					continue;
				}

                if( readDoubles<8 )
                    transformed[partsInBBox] = new double[8];                    
                else
                    transformed[partsInBBox] = new double[readDoubles];

				if (!mt.isIdentity()) {
					try {
						GeometryHandlerUtilities.transform(type, mt, coords[part], transformed[partsInBBox]);
//						mt.transform(coords[part], 0, transformed[partsInBBox],
//								0, readDoubles / 2);
					} catch (Exception e) {
						ShapefileRenderer.LOGGER
								.severe("could not transform coordinates "
										+ e.getLocalizedMessage());
						transformed[partsInBBox] = coords[part];
					}
				} else {
					System.arraycopy(coords[part], 0, transformed[partsInBBox],
							0, readDoubles / 2);
				}
				if( readDoubles<8 ){
				    for(int i=readDoubles; i<transformed[partsInBBox].length;i++){
				        transformed[partsInBBox][i]=transformed[partsInBBox][i-2];
                    }
                }
				partsInBBox++;
			}
			if (partsInBBox == 0)
				return null;
			if (partsInBBox != numParts) {
				double[][] tmp = new double[partsInBBox][];
				System.arraycopy(transformed, 0, tmp, 0, partsInBBox);
				transformed = tmp;
			}
		}
		return createGeometry(type, geomBBox, transformed);
	}


        protected Object createGeometry(ShapeType type, Envelope geomBBox, double[][] transformed) {
            return new SimpleGeometry(type, transformed, geomBBox);
        }
        
	/**
	 * Return true if the current point and the last point should be collapsed
	 * into a single point. The first and last point must be the same so if the
	 * current doubles is the 1-2 second double or if one of the last two the it
	 * will return false. Otherwise it will return true if the distance in the y
	 * axis is less than the distance of one pixel... similar comparison is done
	 * along the x-axis.
	 * 
	 * @return true if the current point and the last point should be collapsed
	 *         into a single point.
	 */
	private boolean collapsePoints(double[][] coords, int part,
			int totalDoubles, int readDoubles, int currentDoubles) {
		return currentDoubles > 3
				&& currentDoubles < totalDoubles - 1
				&& Math.abs(coords[part][readDoubles - 4]
						- coords[part][readDoubles - 2]) <= spanx
				&& Math.abs(coords[part][readDoubles - 3]
						- coords[part][readDoubles - 1]) <= spany;
	}

	/**
	 * @see org.geotools.data.shapefile.shp.ShapeHandler#write(java.nio.ByteBuffer,
	 *      java.lang.Object)
	 */
	public void write(ByteBuffer buffer, Object geometry) {
		// This handler doesnt write
		throw new UnsupportedOperationException(
				"This handler is only for reading");
	}

	/**
	 * @see org.geotools.data.shapefile.shp.ShapeHandler#getLength(java.lang.Object)
	 */
	public int getLength(Object geometry) {
		// TODO Auto-generated method stub
		return 0;
	}

}
