<commit_before>//$HeadURL$
/*----------------------------------------------------------------------------
 This file is part of deegree, http://deegree.org/
 Copyright (C) 2001-2009 by:
 Department of Geography, University of Bonn
 and
 lat/lon GmbH

 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

 Contact information:

 lat/lon GmbH
 Aennchenstr. 19, 53177 Bonn
 Germany
 http://lat-lon.de/

 Department of Geography, University of Bonn
 Prof. Dr. <NAME>
 Postfach 1147, 53001 Bonn
 Germany
 http://www.geographie.uni-bonn.de/deegree/

 e-mail: <EMAIL>
 ----------------------------------------------------------------------------*/
package org.deegree.geometry;

import static org.deegree.commons.utils.StringUtils.isSet;
import static org.slf4j.LoggerFactory.getLogger;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import javax.vecmath.Point3d;

import org.deegree.commons.uom.Length;
import org.deegree.cs.Transformer;
import org.deegree.cs.coordinatesystems.CRS;
import org.deegree.cs.coordinatesystems.ICRS;
import org.deegree.cs.exceptions.OutsideCRSDomainException;
import org.deegree.cs.exceptions.TransformationException;
import org.deegree.cs.exceptions.UnknownCRSException;
import org.deegree.cs.persistence.CRSManager;
import org.deegree.cs.transformations.Transformation;
import org.deegree.cs.transformations.TransformationFactory;
import org.deegree.geometry.Geometry.GeometryType;
import org.deegree.geometry.composite.CompositeCurve;
import org.deegree.geometry.composite.CompositeGeometry;
import org.deegree.geometry.composite.CompositeSolid;
import org.deegree.geometry.composite.CompositeSurface;
import org.deegree.geometry.i18n.Messages;
import org.deegree.geometry.multi.MultiCurve;
import org.deegree.geometry.multi.MultiGeometry;
import org.deegree.geometry.multi.MultiLineString;
import org.deegree.geometry.multi.MultiPoint;
import org.deegree.geometry.multi.MultiPolygon;
import org.deegree.geometry.multi.MultiSolid;
import org.deegree.geometry.multi.MultiSurface;
import org.deegree.geometry.multi.MultiGeometry.MultiGeometryType;
import org.deegree.geometry.points.Points;
import org.deegree.geometry.primitive.Curve;
import org.deegree.geometry.primitive.GeometricPrimitive;
import org.deegree.geometry.primitive.LineString;
import org.deegree.geometry.primitive.LinearRing;
import org.deegree.geometry.primitive.OrientableCurve;
import org.deegree.geometry.primitive.OrientableSurface;
import org.deegree.geometry.primitive.Point;
import org.deegree.geometry.primitive.Polygon;
import org.deegree.geometry.primitive.Ring;
import org.deegree.geometry.primitive.Solid;
import org.deegree.geometry.primitive.Surface;
import org.deegree.geometry.primitive.Tin;
import org.deegree.geometry.primitive.Curve.CurveType;
import org.deegree.geometry.primitive.GeometricPrimitive.PrimitiveType;
import org.deegree.geometry.primitive.Solid.SolidType;
import org.deegree.geometry.primitive.Surface.SurfaceType;
import org.deegree.geometry.primitive.patches.GriddedSurfacePatch;
import org.deegree.geometry.primitive.patches.PolygonPatch;
import org.deegree.geometry.primitive.patches.SurfacePatch;
import org.deegree.geometry.primitive.patches.Triangle;
import org.deegree.geometry.primitive.patches.GriddedSurfacePatch.GriddedSurfaceType;
import org.deegree.geometry.primitive.patches.PolygonPatch.PolygonPatchType;
import org.deegree.geometry.primitive.patches.SurfacePatch.SurfacePatchType;
import org.deegree.geometry.primitive.segments.Arc;
import org.deegree.geometry.primitive.segments.ArcByBulge;
import org.deegree.geometry.primitive.segments.ArcByCenterPoint;
import org.deegree.geometry.primitive.segments.ArcString;
import org.deegree.geometry.primitive.segments.ArcStringByBulge;
import org.deegree.geometry.primitive.segments.BSpline;
import org.deegree.geometry.primitive.segments.Bezier;
import org.deegree.geometry.primitive.segments.Circle;
import org.deegree.geometry.primitive.segments.CircleByCenterPoint;
import org.deegree.geometry.primitive.segments.Clothoid;
import org.deegree.geometry.primitive.segments.CubicSpline;
import org.deegree.geometry.primitive.segments.CurveSegment;
import org.deegree.geometry.primitive.segments.Geodesic;
import org.deegree.geometry.primitive.segments.GeodesicString;
import org.deegree.geometry.primitive.segments.LineStringSegment;
import org.deegree.geometry.primitive.segments.OffsetCurve;
import org.deegree.geometry.primitive.segments.CurveSegment.CurveSegmentType;
import org.deegree.geometry.standard.curvesegments.AffinePlacement;
import org.deegree.geometry.standard.points.PointsList;
import org.slf4j.Logger;

/**
 * 
 * Transforms a geometry defined in a {@link CRS} into a geometry defined in another {@link CRS}
 * 
 * @author <a href="mailto:<EMAIL>"><NAME></a>
 * @author last edited by: $Author$
 * 
 * @version $Revision$, $Date$
 */
public class GeometryTransformer extends Transformer {

    private static final Logger LOG = getLogger( GeometryTransformer.class );

    private static final GeometryFactory geomFactory = new GeometryFactory();

    /**
     * Creates a new GeometryTransformer object.
     * 
     * @param targetCRS
     * @throws IllegalArgumentException
     *             if the given parameter is null.
     */
    public GeometryTransformer( ICRS targetCRS ) throws IllegalArgumentException {
        super( targetCRS );
    }

    /**
     * Creates a new GeometryTransformer object, with the given id as the target CRS.
     * 
     * @param targetCRS
     *            an identifier to which all other CRS's shall be transformed.
     * @throws UnknownCRSException
     *             if the given crs name could not be mapped to a valid (configured) crs.
     * @throws IllegalArgumentException
     *             if the given parameter is null.
     */
    public GeometryTransformer( String targetCRS ) throws UnknownCRSException, IllegalArgumentException {
        super( targetCRS );
    }

    /**
     * @param definedTransformation
     *            to use instead of the CRSFactory.
     * @throws IllegalArgumentException
     *             if the given parameter is null.
     */
    public GeometryTransformer( Transformation definedTransformation ) throws IllegalArgumentException {
        super( definedTransformation );
    }

    /**
     * transforms the coordinates of a deegree geometry to the target coordinate reference system.
     * 
     * @param <T>
     * 
     * @param geo
     *            to be transformed
     * @return the same geometry in a different crs.
     * @throws TransformationException
     *             if the transformation between the source and target crs cannot be created.
     * @throws IllegalArgumentException
     *             if the coordinates system of the geometry is <code>null</code>
     * @throws UnknownCRSException
     */
    public <T extends Geometry> T transform( T geo )
                            throws TransformationException, IllegalArgumentException, UnknownCRSException {
        return transform( geo, null, false, null );
    }

    /**
     * transforms the coordinates of a deegree geometry to the target coordinate reference system. Optionally tests
     * whether the given Geometry lies within the source crs' valid domain.
     * 
     * @param geo
     *            to be transformed
     * @param testValidDomain
     *            true if the incoming geometry should be checked against the domain of validity of the CoordinateSystem
     *            it is defined in.
     * @return the same geometry in a different crs.
     * @throws TransformationException
     *             if the transformation between the source and target crs cannot be created.
     * @throws IllegalArgumentException
     *             if the coordinates system of the geometry is <code>null</code>
     * @throws UnknownCRSException
     */
    public Geometry transform( Geometry geo, boolean testValidDomain )
                            throws TransformationException, IllegalArgumentException, UnknownCRSException {
        return transform( geo, null, testValidDomain, null );
    }

    /**
     * Creates an envelope in the given CRS of the Area of Use (defined in WGS84) of the given Coordinate System.
     * 
     * @param sourceCRS
     *            to get the area of use from.
     * @return an Envelope with corresponding ordinates of the area of use BBox of the given Coordinate system, if the
     *         AreaOfUse was defined as -180,-90,180,90 BBox or if the envelope could not be transformed
     *         <code>null</code> will be returned.
     */
    public static Envelope createValidDomain( ICRS sourceCRS ) {
        double[] validDomain = sourceCRS.getValidDomain();
        if ( validDomain != null ) {
            return geomFactory.createEnvelope( new double[] { validDomain[0], validDomain[1] },
                                               new double[] { validDomain[2], validDomain[3] },
                                               CRSManager.getCRSRef( sourceCRS ) );
        }
        return null;
    }

    /**
     * transforms the coordinates of a deegree geometry to the target coordinate reference system.
     * 
     * @param geo
     *            to be transformed
     * @param sourceCRS
     *            the source CRS for the geometry. overwrites the CRS of the geometry.
     * @return the same geometry in a different crs.
     * @throws TransformationException
     *             if the transformation between the source and target crs cannot be created.
     * @throws IllegalArgumentException
     *             if the coordinates system of the geometry is <code>null</code>
     * @throws UnknownCRSException
     *             if the given CRS is not found
     */
    public Geometry transform( Geometry geo, String sourceCRS )
                            throws TransformationException, IllegalArgumentException, UnknownCRSException {
        return transform( geo, CRSManager.lookup( sourceCRS ), false, null );
    }

    /**
     * transforms the coordinates of a deegree geometry to the target coordinate reference system.
     * 
     * @param geo
     *            to be transformed
     * @param sourceCRS
     *            the source CRS for the geometry. overwrites the CRS of the geometry.
     * @return the same geometry in a different crs.
     * @throws TransformationException
     *             if the transformation between the source and target crs cannot be created.
     * @throws IllegalArgumentException
     *             if the coordinates system of the geometry is <code>null</code>
     */
    public <T extends Geometry> T transform( T geo, ICRS sourceCRS )
                            throws TransformationException, IllegalArgumentException {
        return transform( geo, sourceCRS, false, null );
    }

    /**
     * transforms the coordinates of a deegree geometry to the target coordinate reference system.
     * 
     * @param <T>
     * 
     * @param geom
     *            to be transformed
     * @param sourceCRS
     *            the source CRS for the geometry. overwrites the CRS of the geometry.
     * @param testValidArea
     *            true if the geometry should be be checked of the valid area of the source crs.
     * @param toBeUsedTransformations
     * @return the same geometry in a different crs.
     * @throws IllegalArgumentException
     *             if the coordinates system of the geometry is <code>null</code>
     * @throws TransformationException
     *             if the transformation between the source and target crs cannot be created.
     */
    public <T extends Geometry> T transform( T geom, ICRS sourceCRS, boolean testValidArea,
                                             List<Transformation> toBeUsedTransformations )
                            throws IllegalArgumentException, TransformationException {
        Envelope sourceEnv = null;
        ICRS source = sourceCRS;
        if ( source == null ) {
            source = geom.getCoordinateSystem();
        }
        if ( testValidArea ) {
            sourceEnv = createValidDomain( source );
        }
        return transform( geom, createCRSTransformation( source, toBeUsedTransformations ), sourceEnv );
    }

    /**
     * transforms the coordinates of a deegree geometry to the target coordinate reference system.
     * 
     * @param geo
     *            to be transformed
     * @return the same geometry in a different crs.
     * @throws TransformationException
     *             if the transformation between the source and target crs cannot be created.
     * @throws IllegalArgumentException
     *             if the coordinates system of the geometry is <code>null</code>
     */
    private <T extends Geometry> T transform( T geo, Transformation trans, Envelope domainOfValidity )
                            throws TransformationException, IllegalArgumentException {
        if ( TransformationFactory.isIdentity( trans ) ) {
            return geo;
        }
        if ( domainOfValidity != null ) {
            if ( !insideValidDomain( domainOfValidity, geo ) ) {
                throw new OutsideCRSDomainException( "Geometry (gml:id="
                                                     + ( isSet( geo.getId() ) ? geo.getId() : "not set" )
                                                     + ")is outside the area of validity of the source CRS." );
            }
        }
        GeometryType geometryType = geo.getGeometryType();
        try {
            switch ( geometryType ) {
            case COMPOSITE_GEOMETRY: {
                @SuppressWarnings("unchecked")
                T transformedGeometry = (T) transform( (CompositeGeometry<?>) geo, trans );
                return transformedGeometry;
            }
            case ENVELOPE: {
                @SuppressWarnings("unchecked")
                T transformedGeometry = (T) transform( (Envelope) geo, trans );
                return transformedGeometry;
            }
            case MULTI_GEOMETRY: {
                @SuppressWarnings("unchecked")
                T transformedGeometry = (T) transform( (MultiGeometry<?>) geo, trans, domainOfValidity );
                return transformedGeometry;
            }
            case PRIMITIVE_GEOMETRY: {
                @SuppressWarnings("unchecked")
                T transformedGeometry = (T) transform( (GeometricPrimitive) geo, trans );
                return transformedGeometry;
            }
            }
        } catch ( GeometryException ge ) {
            throw new TransformationException( Messages.getMessage( "CRS_TRANSFORMATION_ERROR",
                                                                    geo.getCoordinateSystem().getAlias(),
                                                                    getTargetCRS().getCodes(), ge.getMessage() ), ge );
        }
        return null;
    }

    private GeometricPrimitive transform( GeometricPrimitive geom, Transformation trans )
                            throws TransformationException {
        GeometricPrimitive result = null;
        PrimitiveType type = geom.getPrimitiveType();
        switch ( type ) {
        case Curve:
            result = transform( (Curve) geom, trans );
            break;
        case Point:
            result = transform( (Point) geom, trans );
            break;
        case Solid:
            result = transform( (Solid) geom, trans );
            break;
        case Surface:
            result = transform( (Surface) geom, trans );
            break;
        }
        return result;
    }

    private CompositeGeometry<?> transform( CompositeGeometry<?> geom, Transformation trans )
                            throws TransformationException {
        List<GeometricPrimitive> transformed = new LinkedList<GeometricPrimitive>();
        for ( GeometricPrimitive gp : geom ) {
            GeometricPrimitive tGp = transform( gp, trans );
            transformed.add( tGp );
        }
        return geomFactory.createCompositeGeometry( geom.getId(), getTargetCRS(), transformed );
    }

    private MultiGeometry<?> transform( MultiGeometry<?> geom, Transformation trans, Envelope domainOfValidity )
                            throws TransformationException {
        MultiGeometry<?> result = null;

        final MultiGeometryType geometryType = geom.getMultiGeometryType();
        switch ( geometryType ) {
        case MULTI_CURVE:
            result = transform( (MultiCurve<Curve>) geom, trans );
            break;
        case MULTI_GEOMETRY:
            List<Geometry> mg = new LinkedList<Geometry>();
            for ( Geometry geo : geom ) {
                Geometry tG = transform( geo, trans, domainOfValidity );
                mg.add( tG );
            }
            result = geomFactory.createMultiGeometry( geom.getId(), getTargetCRS(), mg );
            break;
        case MULTI_LINE_STRING:
            result = transform( (MultiLineString) geom, trans );
            break;
        case MULTI_POINT:
            result = transform( (MultiPoint) geom, trans );
            break;
        case MULTI_POLYGON:
            result = transform( (MultiPolygon) geom, trans );
            break;
        case MULTI_SOLID:
            result = transform( (MultiSolid) geom, trans );
            break;
        case MULTI_SURFACE:
            result = transform( (MultiSurface<Surface>) geom, trans );
            break;
        }
        return result;
    }

    private MultiSolid transform( MultiSolid geom, Transformation trans )
                            throws TransformationException {
        List<Solid> transformedSolids = new LinkedList<Solid>();
        for ( Solid s : geom ) {
            Solid ts = transform( s, trans );
            transformedSolids.add( ts );
        }
        return geomFactory.createMultiSolid( geom.getId(), getTargetCRS(), transformedSolids );
    }

    private Solid transform( Solid solid, Transformation trans )
                            throws TransformationException {
        SolidType type = solid.getSolidType();
        Solid result = null;
        switch ( type ) {
        case CompositeSolid:
            result = transform( (CompositeSolid) solid, trans );
            break;
        case Solid:
            Surface ext = solid.getExteriorSurface();
            Surface tExt = transform( ext, trans );
            List<Surface> inter = solid.getInteriorSurfaces();
            List<Surface> tInter = new LinkedList<Surface>();
            if ( inter != null && !inter.isEmpty() ) {
                for ( Surface in : inter ) {
                    if ( in != null ) {
                        Surface tIn = transform( in, trans );
                        tInter.add( tIn );
                    }
                }
            }
            result = geomFactory.createSolid( solid.getId(), getTargetCRS(), tExt, tInter );
            break;
        }
        return result;

    }

    private CompositeSolid transform( CompositeSolid cSolid, Transformation trans )
                            throws TransformationException {
        List<Solid> tSolids = new ArrayList<Solid>( cSolid.size() );
        for ( Solid s : cSolid ) {
            tSolids.add( transform( s, trans ) );
        }
        return geomFactory.createCompositeSolid( cSolid.getId(), getTargetCRS(), tSolids );
    }

    private boolean insideValidDomain( Envelope validDomain, Geometry geom ) {
        boolean result = false;
        if ( validDomain != null && geom != null ) {
            Geometry inSource = geom;
            ICRS sourceCRS = validDomain.getCoordinateSystem();
            if ( sourceCRS != null && geom.getCoordinateSystem() != null
                 && !sourceCRS.equals( geom.getCoordinateSystem() ) ) {
                try {
                    GeometryTransformer trans = new GeometryTransformer( sourceCRS );
                    inSource = trans.transform( geom );
                } catch ( IllegalArgumentException e ) {
                    if ( LOG.isDebugEnabled() ) {
                        LOG.debug( "No valid domain checking available: " + e.getMessage(), e );
                    }
                } catch ( TransformationException e ) {
                    if ( LOG.isDebugEnabled() ) {
                        LOG.debug( "No valid domain checking available: " + e.getMessage(), e );
                    }
                } catch ( UnknownCRSException e ) {
                    if ( LOG.isDebugEnabled() ) {
                        LOG.debug( "No valid domain checking available: " + e.getMessage(), e );
                    }
                }
            }
            try {
                result = validDomain.contains( inSource );
            } catch ( UnsupportedOperationException e ) {
                LOG.debug( "Could not determine valid domain because it is not supported: " + e.getMessage(), e );
                result = true;
            } catch ( Throwable t ) {
                LOG.debug( "No valid domain checking available: " + t.getMessage(), t );
                result = false;
            }
        }
        return result;
    }

    private Geometry transform( Envelope envelope, Transformation trans )
                            throws TransformationException {
        return transform( envelope, trans, 20 );
    }

    /**
     * Transform the geometry of an envelope. It substitutes the envelope (two points) with a polygon with
     * <code>numPoints</code> points. The points are distributed evenly on the boundary of the envelope to provide a
     * more accurate transformed envelope. This is useful if the transformation rotates and distorts.
     * 
     * @param envelope
     * @param trans
     * @param numPoints
     * @return the transformed envelope
     * @throws TransformationException<commit_msg>avoid exception, when validation of the domain is not possible<commit_after>//$HeadURL$
/*----------------------------------------------------------------------------
 This file is part of deegree, http://deegree.org/
 Copyright (C) 2001-2009 by:
 Department of Geography, University of Bonn
 and
 lat/lon GmbH

 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

 Contact information:

 lat/lon GmbH
 Aennchenstr. 19, 53177 Bonn
 Germany
 http://lat-lon.de/

 Department of Geography, University of Bonn
 Prof. Dr. <NAME>
 Postfach 1147, 53001 Bonn
 Germany
 http://www.geographie.uni-bonn.de/deegree/

 e-mail: <EMAIL>
 ----------------------------------------------------------------------------*/
package org.deegree.geometry;

import static org.deegree.commons.utils.StringUtils.isSet;
import static org.slf4j.LoggerFactory.getLogger;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import javax.vecmath.Point3d;

import org.deegree.commons.uom.Length;
import org.deegree.cs.Transformer;
import org.deegree.cs.coordinatesystems.CRS;
import org.deegree.cs.coordinatesystems.ICRS;
import org.deegree.cs.exceptions.OutsideCRSDomainException;
import org.deegree.cs.exceptions.TransformationException;
import org.deegree.cs.exceptions.UnknownCRSException;
import org.deegree.cs.persistence.CRSManager;
import org.deegree.cs.transformations.Transformation;
import org.deegree.cs.transformations.TransformationFactory;
import org.deegree.geometry.Geometry.GeometryType;
import org.deegree.geometry.composite.CompositeCurve;
import org.deegree.geometry.composite.CompositeGeometry;
import org.deegree.geometry.composite.CompositeSolid;
import org.deegree.geometry.composite.CompositeSurface;
import org.deegree.geometry.i18n.Messages;
import org.deegree.geometry.multi.MultiCurve;
import org.deegree.geometry.multi.MultiGeometry;
import org.deegree.geometry.multi.MultiLineString;
import org.deegree.geometry.multi.MultiPoint;
import org.deegree.geometry.multi.MultiPolygon;
import org.deegree.geometry.multi.MultiSolid;
import org.deegree.geometry.multi.MultiSurface;
import org.deegree.geometry.multi.MultiGeometry.MultiGeometryType;
import org.deegree.geometry.points.Points;
import org.deegree.geometry.primitive.Curve;
import org.deegree.geometry.primitive.GeometricPrimitive;
import org.deegree.geometry.primitive.LineString;
import org.deegree.geometry.primitive.LinearRing;
import org.deegree.geometry.primitive.OrientableCurve;
import org.deegree.geometry.primitive.OrientableSurface;
import org.deegree.geometry.primitive.Point;
import org.deegree.geometry.primitive.Polygon;
import org.deegree.geometry.primitive.Ring;
import org.deegree.geometry.primitive.Solid;
import org.deegree.geometry.primitive.Surface;
import org.deegree.geometry.primitive.Tin;
import org.deegree.geometry.primitive.Curve.CurveType;
import org.deegree.geometry.primitive.GeometricPrimitive.PrimitiveType;
import org.deegree.geometry.primitive.Solid.SolidType;
import org.deegree.geometry.primitive.Surface.SurfaceType;
import org.deegree.geometry.primitive.patches.GriddedSurfacePatch;
import org.deegree.geometry.primitive.patches.PolygonPatch;
import org.deegree.geometry.primitive.patches.SurfacePatch;
import org.deegree.geometry.primitive.patches.Triangle;
import org.deegree.geometry.primitive.patches.GriddedSurfacePatch.GriddedSurfaceType;
import org.deegree.geometry.primitive.patches.PolygonPatch.PolygonPatchType;
import org.deegree.geometry.primitive.patches.SurfacePatch.SurfacePatchType;
import org.deegree.geometry.primitive.segments.Arc;
import org.deegree.geometry.primitive.segments.ArcByBulge;
import org.deegree.geometry.primitive.segments.ArcByCenterPoint;
import org.deegree.geometry.primitive.segments.ArcString;
import org.deegree.geometry.primitive.segments.ArcStringByBulge;
import org.deegree.geometry.primitive.segments.BSpline;
import org.deegree.geometry.primitive.segments.Bezier;
import org.deegree.geometry.primitive.segments.Circle;
import org.deegree.geometry.primitive.segments.CircleByCenterPoint;
import org.deegree.geometry.primitive.segments.Clothoid;
import org.deegree.geometry.primitive.segments.CubicSpline;
import org.deegree.geometry.primitive.segments.CurveSegment;
import org.deegree.geometry.primitive.segments.Geodesic;
import org.deegree.geometry.primitive.segments.GeodesicString;
import org.deegree.geometry.primitive.segments.LineStringSegment;
import org.deegree.geometry.primitive.segments.OffsetCurve;
import org.deegree.geometry.primitive.segments.CurveSegment.CurveSegmentType;
import org.deegree.geometry.standard.curvesegments.AffinePlacement;
import org.deegree.geometry.standard.points.PointsList;
import org.slf4j.Logger;

/**
 * 
 * Transforms a geometry defined in a {@link CRS} into a geometry defined in another {@link CRS}
 * 
 * @author <a href="mailto:<EMAIL>"><NAME></a>
 * @author last edited by: $Author$
 * 
 * @version $Revision$, $Date$
 */
public class GeometryTransformer extends Transformer {

    private static final Logger LOG = getLogger( GeometryTransformer.class );

    private static final GeometryFactory geomFactory = new GeometryFactory();

    /**
     * Creates a new GeometryTransformer object.
     * 
     * @param targetCRS
     * @throws IllegalArgumentException
     *             if the given parameter is null.
     */
    public GeometryTransformer( ICRS targetCRS ) throws IllegalArgumentException {
        super( targetCRS );
    }

    /**
     * Creates a new GeometryTransformer object, with the given id as the target CRS.
     * 
     * @param targetCRS
     *            an identifier to which all other CRS's shall be transformed.
     * @throws UnknownCRSException
     *             if the given crs name could not be mapped to a valid (configured) crs.
     * @throws IllegalArgumentException
     *             if the given parameter is null.
     */
    public GeometryTransformer( String targetCRS ) throws UnknownCRSException, IllegalArgumentException {
        super( targetCRS );
    }

    /**
     * @param definedTransformation
     *            to use instead of the CRSFactory.
     * @throws IllegalArgumentException
     *             if the given parameter is null.
     */
    public GeometryTransformer( Transformation definedTransformation ) throws IllegalArgumentException {
        super( definedTransformation );
    }

    /**
     * transforms the coordinates of a deegree geometry to the target coordinate reference system.
     * 
     * @param <T>
     * 
     * @param geo
     *            to be transformed
     * @return the same geometry in a different crs.
     * @throws TransformationException
     *             if the transformation between the source and target crs cannot be created.
     * @throws IllegalArgumentException
     *             if the coordinates system of the geometry is <code>null</code>
     * @throws UnknownCRSException
     */
    public <T extends Geometry> T transform( T geo )
                            throws TransformationException, IllegalArgumentException, UnknownCRSException {
        return transform( geo, null, false, null );
    }

    /**
     * transforms the coordinates of a deegree geometry to the target coordinate reference system. Optionally tests
     * whether the given Geometry lies within the source crs' valid domain.
     * 
     * @param geo
     *            to be transformed
     * @param testValidDomain
     *            true if the incoming geometry should be checked against the domain of validity of the CoordinateSystem
     *            it is defined in.
     * @return the same geometry in a different crs.
     * @throws TransformationException
     *             if the transformation between the source and target crs cannot be created.
     * @throws IllegalArgumentException
     *             if the coordinates system of the geometry is <code>null</code>
     * @throws UnknownCRSException
     */
    public Geometry transform( Geometry geo, boolean testValidDomain )
                            throws TransformationException, IllegalArgumentException, UnknownCRSException {
        return transform( geo, null, testValidDomain, null );
    }

    /**
     * Creates an envelope in the given CRS of the Area of Use (defined in WGS84) of the given Coordinate System.
     * 
     * @param sourceCRS
     *            to get the area of use from.
     * @return an Envelope with corresponding ordinates of the area of use BBox of the given Coordinate system, if the
     *         AreaOfUse was defined as -180,-90,180,90 BBox or if the envelope could not be transformed
     *         <code>null</code> will be returned.
     */
    public static Envelope createValidDomain( ICRS sourceCRS ) {
        double[] validDomain = sourceCRS.getValidDomain();
        if ( validDomain != null ) {
            return geomFactory.createEnvelope( new double[] { validDomain[0], validDomain[1] },
                                               new double[] { validDomain[2], validDomain[3] },
                                               CRSManager.getCRSRef( sourceCRS ) );
        }
        return null;
    }

    /**
     * transforms the coordinates of a deegree geometry to the target coordinate reference system.
     * 
     * @param geo
     *            to be transformed
     * @param sourceCRS
     *            the source CRS for the geometry. overwrites the CRS of the geometry.
     * @return the same geometry in a different crs.
     * @throws TransformationException
     *             if the transformation between the source and target crs cannot be created.
     * @throws IllegalArgumentException
     *             if the coordinates system of the geometry is <code>null</code>
     * @throws UnknownCRSException
     *             if the given CRS is not found
     */
    public Geometry transform( Geometry geo, String sourceCRS )
                            throws TransformationException, IllegalArgumentException, UnknownCRSException {
        return transform( geo, CRSManager.lookup( sourceCRS ), false, null );
    }

    /**
     * transforms the coordinates of a deegree geometry to the target coordinate reference system.
     * 
     * @param geo
     *            to be transformed
     * @param sourceCRS
     *            the source CRS for the geometry. overwrites the CRS of the geometry.
     * @return the same geometry in a different crs.
     * @throws TransformationException
     *             if the transformation between the source and target crs cannot be created.
     * @throws IllegalArgumentException
     *             if the coordinates system of the geometry is <code>null</code>
     */
    public <T extends Geometry> T transform( T geo, ICRS sourceCRS )
                            throws TransformationException, IllegalArgumentException {
        return transform( geo, sourceCRS, false, null );
    }

    /**
     * transforms the coordinates of a deegree geometry to the target coordinate reference system.
     * 
     * @param <T>
     * 
     * @param geom
     *            to be transformed
     * @param sourceCRS
     *            the source CRS for the geometry. overwrites the CRS of the geometry.
     * @param testValidArea
     *            true if the geometry should be be checked of the valid area of the source crs.
     * @param toBeUsedTransformations
     * @return the same geometry in a different crs.
     * @throws IllegalArgumentException
     *             if the coordinates system of the geometry is <code>null</code>
     * @throws TransformationException
     *             if the transformation between the source and target crs cannot be created.
     */
    public <T extends Geometry> T transform( T geom, ICRS sourceCRS, boolean testValidArea,
                                             List<Transformation> toBeUsedTransformations )
                            throws IllegalArgumentException, TransformationException {
        Envelope sourceEnv = null;
        ICRS source = sourceCRS;
        if ( source == null ) {
            source = geom.getCoordinateSystem();
        }
        if ( testValidArea ) {
            sourceEnv = createValidDomain( source );
        }
        return transform( geom, createCRSTransformation( source, toBeUsedTransformations ), sourceEnv );
    }

    /**
     * transforms the coordinates of a deegree geometry to the target coordinate reference system.
     * 
     * @param geo
     *            to be transformed
     * @return the same geometry in a different crs.
     * @throws TransformationException
     *             if the transformation between the source and target crs cannot be created.
     * @throws IllegalArgumentException
     *             if the coordinates system of the geometry is <code>null</code>
     */
    private <T extends Geometry> T transform( T geo, Transformation trans, Envelope domainOfValidity )
                            throws TransformationException, IllegalArgumentException {
        if ( TransformationFactory.isIdentity( trans ) ) {
            return geo;
        }
        if ( domainOfValidity != null ) {
            if ( !insideValidDomain( domainOfValidity, geo ) ) {
                throw new OutsideCRSDomainException( "Geometry (gml:id="
                                                     + ( isSet( geo.getId() ) ? geo.getId() : "not set" )
                                                     + ")is outside the area of validity of the source CRS." );
            }
        }
        GeometryType geometryType = geo.getGeometryType();
        try {
            switch ( geometryType ) {
            case COMPOSITE_GEOMETRY: {
                @SuppressWarnings("unchecked")
                T transformedGeometry = (T) transform( (CompositeGeometry<?>) geo, trans );
                return transformedGeometry;
            }
            case ENVELOPE: {
                @SuppressWarnings("unchecked")
                T transformedGeometry = (T) transform( (Envelope) geo, trans );
                return transformedGeometry;
            }
            case MULTI_GEOMETRY: {
                @SuppressWarnings("unchecked")
                T transformedGeometry = (T) transform( (MultiGeometry<?>) geo, trans, domainOfValidity );
                return transformedGeometry;
            }
            case PRIMITIVE_GEOMETRY: {
                @SuppressWarnings("unchecked")
                T transformedGeometry = (T) transform( (GeometricPrimitive) geo, trans );
                return transformedGeometry;
            }
            }
        } catch ( GeometryException ge ) {
            throw new TransformationException( Messages.getMessage( "CRS_TRANSFORMATION_ERROR",
                                                                    geo.getCoordinateSystem().getAlias(),
                                                                    getTargetCRS().getCodes(), ge.getMessage() ), ge );
        }
        return null;
    }

    private GeometricPrimitive transform( GeometricPrimitive geom, Transformation trans )
                            throws TransformationException {
        GeometricPrimitive result = null;
        PrimitiveType type = geom.getPrimitiveType();
        switch ( type ) {
        case Curve:
            result = transform( (Curve) geom, trans );
            break;
        case Point:
            result = transform( (Point) geom, trans );
            break;
        case Solid:
            result = transform( (Solid) geom, trans );
            break;
        case Surface:
            result = transform( (Surface) geom, trans );
            break;
        }
        return result;
    }

    private CompositeGeometry<?> transform( CompositeGeometry<?> geom, Transformation trans )
                            throws TransformationException {
        List<GeometricPrimitive> transformed = new LinkedList<GeometricPrimitive>();
        for ( GeometricPrimitive gp : geom ) {
            GeometricPrimitive tGp = transform( gp, trans );
            transformed.add( tGp );
        }
        return geomFactory.createCompositeGeometry( geom.getId(), getTargetCRS(), transformed );
    }

    private MultiGeometry<?> transform( MultiGeometry<?> geom, Transformation trans, Envelope domainOfValidity )
                            throws TransformationException {
        MultiGeometry<?> result = null;

        final MultiGeometryType geometryType = geom.getMultiGeometryType();
        switch ( geometryType ) {
        case MULTI_CURVE:
            result = transform( (MultiCurve<Curve>) geom, trans );
            break;
        case MULTI_GEOMETRY:
            List<Geometry> mg = new LinkedList<Geometry>();
            for ( Geometry geo : geom ) {
                Geometry tG = transform( geo, trans, domainOfValidity );
                mg.add( tG );
            }
            result = geomFactory.createMultiGeometry( geom.getId(), getTargetCRS(), mg );
            break;
        case MULTI_LINE_STRING:
            result = transform( (MultiLineString) geom, trans );
            break;
        case MULTI_POINT:
            result = transform( (MultiPoint) geom, trans );
            break;
        case MULTI_POLYGON:
            result = transform( (MultiPolygon) geom, trans );
            break;
        case MULTI_SOLID:
            result = transform( (MultiSolid) geom, trans );
            break;
        case MULTI_SURFACE:
            result = transform( (MultiSurface<Surface>) geom, trans );
            break;
        }
        return result;
    }

    private MultiSolid transform( MultiSolid geom, Transformation trans )
                            throws TransformationException {
        List<Solid> transformedSolids = new LinkedList<Solid>();
        for ( Solid s : geom ) {
            Solid ts = transform( s, trans );
            transformedSolids.add( ts );
        }
        return geomFactory.createMultiSolid( geom.getId(), getTargetCRS(), transformedSolids );
    }

    private Solid transform( Solid solid, Transformation trans )
                            throws TransformationException {
        SolidType type = solid.getSolidType();
        Solid result = null;
        switch ( type ) {
        case CompositeSolid:
            result = transform( (CompositeSolid) solid, trans );
            break;
        case Solid:
            Surface ext = solid.getExteriorSurface();
            Surface tExt = transform( ext, trans );
            List<Surface> inter = solid.getInteriorSurfaces();
            List<Surface> tInter = new LinkedList<Surface>();
            if ( inter != null && !inter.isEmpty() ) {
                for ( Surface in : inter ) {
                    if ( in != null ) {
                        Surface tIn = transform( in, trans );
                        tInter.add( tIn );
                    }
                }
            }
            result = geomFactory.createSolid( solid.getId(), getTargetCRS(), tExt, tInter );
            break;
        }
        return result;

    }

    private CompositeSolid transform( CompositeSolid cSolid, Transformation trans )
                            throws TransformationException {
        List<Solid> tSolids = new ArrayList<Solid>( cSolid.size() );
        for ( Solid s : cSolid ) {
            tSolids.add( transform( s, trans ) );
        }
        return geomFactory.createCompositeSolid( cSolid.getId(), getTargetCRS(), tSolids );
    }

    private boolean insideValidDomain( Envelope validDomain, Geometry geom ) {
        boolean result = false;
        if ( validDomain != null && geom != null ) {
            Geometry inSource = geom;
            ICRS sourceCRS = validDomain.getCoordinateSystem();
            if ( sourceCRS != null && geom.getCoordinateSystem() != null
                 && !sourceCRS.equals( geom.getCoordinateSystem() ) ) {
                try {
                    GeometryTransformer trans = new GeometryTransformer( sourceCRS );
                    inSource = trans.transform( geom );
                } catch ( IllegalArgumentException e ) {
                    if ( LOG.isDebugEnabled() ) {
                        LOG.debug( "No valid domain checking available: " + e.getMessage(), e );
                    }
                } catch ( TransformationException e ) {
                    if ( LOG.isDebugEnabled() ) {
                        LOG.debug( "No valid domain checking available: " + e.getMessage(), e );
                    }
                } catch ( UnknownCRSException e ) {
                    if ( LOG.isDebugEnabled() ) {
                        LOG.debug( "No valid domain checking available: " + e.getMessage(), e );
                    }
                }
            }
            try {
                result = validDomain.contains( inSource );
            } catch ( UnsupportedOperationException e ) {
                LOG.info( "Could not determine valid domain because it is not supported: " + e.getMessage(), e );
                result = true;
            } catch ( IllegalArgumentException e ) {
                LOG.info( "Could not determine valid domain because it is not supported: " + e.getMessage(), e );
                result = true;
            } catch ( Throwable t ) {
                LOG.info( "No valid domain checking available: " + t.getMessage(), t );
                result = false;
            }
        }
        return result;
    }

    private Geometry transform( Envelope envelope, Transformation trans )
                            throws TransformationException {
        return transform( envelope, trans, 20 );
    }

    /**
     * Transform the geometry of an envelope. It substitutes the envelope (two points) with a polygon with
     * <code>numPoints</code> points. The points are distributed evenly on the boundary of the envelope to provide a
     * more accurate transformed envelope. This is useful if the transformation rotates and distorts.
     * 
     * @param envelope
     * @param trans
     * @param numPoints
     * @return the transformed envelope
     * @throws TransformationException
{'fraction_non_alphanumeric': 0.08471727737113288, 'fraction_numerical': 0.0027863160920811128, 'mean_word_length': 2.2445114076625052, 'pattern_counts': {'":': 0, '<': 101, '<?xml version=': 0, '>': 101, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 28, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5632985', 'n_tokens_mistral': 11722, 'n_tokens_neox': 11231, 'n_words': 3418}