/*
 *    GeoTools - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2004-2006, 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.geometry.jts;

import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;


/**
 * Service object that takes a geometry an applies a MathTransform on top
 * of it.
 * @author Andrea Aime
 * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/api/src/main/java/org/geotools/geometry/jts/GeometryCoordinateSequenceTransformer.java $
 */
public class GeometryCoordinateSequenceTransformer {
    private MathTransform transform;
    private CoordinateSequenceTransformer csTransformer;
    private CoordinateReferenceSystem crs;

    public GeometryCoordinateSequenceTransformer() {
        csTransformer = new DefaultCoordinateSequenceTransformer();
    }

    public GeometryCoordinateSequenceTransformer(CoordinateSequenceTransformer transformer) {
        csTransformer = transformer;
    }

    /**
     * Sets the math transform to be used for transformation
     * @param transform
     */
    public void setMathTransform(MathTransform transform) {
        this.transform = transform;
    }

    /**
     * Sets the target coordinate reference system.
     * <p>
     * This value is used to set the coordinate reference system of geometries
     * after they have been transformed.
     * </p>
     * @param crs The target coordinate reference system.
     */
    public void setCoordinateReferenceSystem(CoordinateReferenceSystem crs) {
        this.crs = crs;
    }

    /**
     * Applies the transform to the provided geometry, given
     * @param g
     * @throws TransformException
     */
    public Geometry transform(Geometry g) throws TransformException {
        GeometryFactory factory = g.getFactory();

        Geometry transformed = null;

        if (g instanceof Point) {
            transformed = transformPoint((Point) g, factory);
        } else if (g instanceof MultiPoint) {
            MultiPoint mp = (MultiPoint) g;
            Point[] points = new Point[mp.getNumGeometries()];

            for (int i = 0; i < points.length; i++) {
                points[i] = transformPoint((Point) mp.getGeometryN(i), factory);
            }

            transformed = factory.createMultiPoint(points);
        } else if (g instanceof LineString) {
            transformed = transformLineString((LineString) g, factory);
        } else if (g instanceof MultiLineString) {
            MultiLineString mls = (MultiLineString) g;
            LineString[] lines = new LineString[mls.getNumGeometries()];

            for (int i = 0; i < lines.length; i++) {
                lines[i] = transformLineString((LineString) mls.getGeometryN(i), factory);
            }

            transformed = factory.createMultiLineString(lines);
        } else if (g instanceof Polygon) {
            transformed = transformPolygon((Polygon) g, factory);
        } else if (g instanceof MultiPolygon) {
            MultiPolygon mp = (MultiPolygon) g;
            Polygon[] polygons = new Polygon[mp.getNumGeometries()];

            for (int i = 0; i < polygons.length; i++) {
                polygons[i] = transformPolygon((Polygon) mp.getGeometryN(i), factory);
            }

            transformed = factory.createMultiPolygon(polygons);
        } else if (g instanceof GeometryCollection) {
            GeometryCollection gc = (GeometryCollection) g;
            Geometry[] geoms = new Geometry[gc.getNumGeometries()];

            for (int i = 0; i < geoms.length; i++) {
                geoms[i] = transform(gc.getGeometryN(i));
            }

            transformed = factory.createGeometryCollection(geoms);
        } else {
            throw new IllegalArgumentException("Unsupported geometry type " + g.getClass());
        }

        //copy over user data, do a special check for coordinate reference 
        // systme
        transformed.setUserData(g.getUserData());

        if ((g.getUserData() == null) || g.getUserData() instanceof CoordinateReferenceSystem) {
            //set the new one to be the target crs
            if (crs != null) {
                transformed.setUserData(crs);
            }
        }

        return transformed;
    }

    /**
     *
     * @throws TransformException
     */
    public LineString transformLineString(LineString ls, GeometryFactory gf)
        throws TransformException {
        CoordinateSequence cs = projectCoordinateSequence(ls.getCoordinateSequence());

        if (ls instanceof LinearRing) {
            return gf.createLinearRing(cs);
        } else {
            return gf.createLineString(cs);
        }
    }

    /**
     * @param point
     *
     * @throws TransformException
     */
    public Point transformPoint(Point point, GeometryFactory gf)
        throws TransformException {
        CoordinateSequence cs = projectCoordinateSequence(point.getCoordinateSequence());

        return gf.createPoint(cs);
    }

    /**
     * @param cs a CoordinateSequence
     *
     * @throws TransformException
     */
    public CoordinateSequence projectCoordinateSequence(CoordinateSequence cs)
        throws TransformException {
        return csTransformer.transform(cs, transform);
    }

    /**
     * @param polygon
     * @throws TransformException
     */
    public Polygon transformPolygon(Polygon polygon, GeometryFactory gf)
        throws TransformException {
        LinearRing exterior = (LinearRing) transformLineString(polygon.getExteriorRing(), gf);
        LinearRing[] interiors = new LinearRing[polygon.getNumInteriorRing()];

        for (int i = 0; i < interiors.length; i++) {
            interiors[i] = (LinearRing) transformLineString(polygon.getInteriorRingN(i), gf);
        }

        return gf.createPolygon(exterior, interiors);
    }
}
