/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package eu.dnetlib.espas.util;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.util.GeometryEditor;
import eu.dnetlib.espas.cxform.CRSTransformProxy;
import static eu.dnetlib.espas.util.GMLTransformationHandler.SUPPORT_GML_SPEC;
import eu.dnetlib.espas.util.PointCoordinateEditor.SUPPORTED_GEOMETRY;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.QName;
import org.apache.log4j.Logger;
import org.apache.log4j.Priority;
import org.geotoolkit.gml.xml.AbstractGeometry;
import org.geotoolkit.gml.xml.AbstractRingProperty;
import org.geotoolkit.gml.xml.Coordinates;
import org.geotoolkit.gml.xml.DirectPosition;
import org.geotoolkit.gml.xml.LinearRing;
import org.geotoolkit.gml.xml.Point;
import org.geotoolkit.gml.xml.Polygon;
import org.geotoolkit.gml.xml.Ring;
import org.geotoolkit.gml.xml.v321.AbstractGeometryType;
import org.geotoolkit.gml.xml.v321.CoordinatesType;
import org.geotoolkit.gml.xml.v321.DirectPositionType;
import org.geotoolkit.gml.xml.v321.DirectPositionListType;
import org.geotoolkit.gml.xml.v321.GeometryArrayPropertyType;
import org.geotoolkit.gml.xml.v321.LinearRingType;
import org.geotoolkit.gml.xml.v321.MultiGeometryType;
import org.geotoolkit.gml.xml.v321.PointArrayPropertyType;
import org.geotoolkit.gml.xml.v321.PointPropertyType;
import org.geotoolkit.gml.xml.v321.PointType;
import org.geotoolkit.gml.xml.v321.PolygonPatchType;
import org.geotoolkit.gml.xml.v321.PolygonType;
import org.geotoolkit.gml.xml.v321.RingType;
import org.geotoolkit.gml.xml.v321.SurfacePatchArrayPropertyType;
import org.geotoolkit.gml.xml.v321.SurfaceType;
import org.geotoolkit.gml.xml.v321.LinearRingPropertyType;
import org.opengis.geometry.UnmodifiableGeometryException;
import org.w3c.dom.Element;
import org.w3c.dom.Node;


/** This class is responsible for handling the transformation of given GML representations into WKT representations that are fed to the ESPAS postgis 
 * db. The provided implementation is package visible and should not be further extended.
 * 
 * @author gathanas
 */
class GMLGeometryTransformationHandler extends GMLTransformationHandler {

   private static final Logger _logger = Logger.getLogger(GMLGeometryTransformationHandler.class);

   public GMLGeometryTransformationHandler() {
      super();
   }

   /**
    * Returns the srs name assigned to the given geometry description. This in ESPAS corresponds to the URI of the CRS system in the ESPAS Ontology.
    */
   public String getGeometrySrsName(Node gmlNode) throws JAXBException {
      String result = "";

      if (!(gmlNode instanceof Element) || !((Element) gmlNode).getNamespaceURI().equalsIgnoreCase(SUPPORT_GML_SPEC))
         return null;

      Unmarshaller unMarshaller = acquireUnMarshaller();

      Object gmlObject = unMarshaller.unmarshal(gmlNode);

      if (gmlObject instanceof JAXBElement)
         gmlObject = ((JAXBElement) gmlObject).getValue();
//        if this is not a geometry object then an empty result is returned
      if ((gmlObject instanceof AbstractGeometry))
         result = ((AbstractGeometry) gmlObject).getSrsName();

//      free resources by releasing unmashaller instance
      releaseUnMarshaller(unMarshaller);
      return result;
   }

   /** Accepts a GML geomgetry element representation and transforms it to the selected representation.
    */
   public String transformGeometryRepresentation(Date transformationTime, Node gmlNode, String toCRS) throws JAXBException{
      String result = "";

      if (!(gmlNode instanceof Element) || !((Element) gmlNode).getNamespaceURI().equalsIgnoreCase(SUPPORT_GML_SPEC))
         return null;

      Unmarshaller unMarshaller = acquireUnMarshaller();

      Object gmlObject = unMarshaller.unmarshal(gmlNode);
//      free resources by releasing unmashaller instance
      releaseUnMarshaller(unMarshaller);

      if (gmlObject instanceof JAXBElement)
         gmlObject = ((JAXBElement) gmlObject).getValue();
//        if this is not a geometry object then an empty result is returned
      if ((gmlObject instanceof AbstractGeometry))
         if (gmlObject instanceof Point)
            result = transformPointGeoRepresentation(transformationTime, (PointType) gmlObject, toCRS);
         else if (gmlObject instanceof Polygon)
            result = transformPolygonGeoRepresentation(transformationTime, (PolygonType) gmlObject, toCRS);
         else if(gmlObject instanceof org.geotoolkit.gml.xml.v321.MultiPointType)
             result = transformMultiPointGeoRepresentation(transformationTime,(org.geotoolkit.gml.xml.v321.MultiPointType) gmlObject, toCRS);
         else if (gmlObject instanceof SurfaceType)
            result = transformPolyhedralSurfaceRepresentation(transformationTime, (SurfaceType) gmlObject, toCRS);

      return result;
   }

   public String getGeometryRepresentation(Date phenomenonTime, Node gmlNode, String toCRS) throws JAXBException {
      String result = "";

      if (!(gmlNode instanceof Element) || !((Element) gmlNode).getNamespaceURI().equalsIgnoreCase(SUPPORT_GML_SPEC))
         return null;

      _logger.debug("getGeography Representation: node is gml element");

      Unmarshaller unMarshaller = acquireUnMarshaller();

      Object gmlObject = unMarshaller.unmarshal(gmlNode);

      if (gmlObject instanceof JAXBElement)
         gmlObject = ((JAXBElement) gmlObject).getValue();
//        if this is not a geometry object then an emty result is returned

      _logger.debug("getGeography Representation: is gml object ::" + gmlObject.toString());

      if ((gmlObject instanceof AbstractGeometry))
         if (gmlObject instanceof Point)
            result = getPointGeoRepresentation(phenomenonTime, (Point) gmlObject, toCRS);
         else if (gmlObject instanceof Polygon)
            result = getPolygonGeoRepresentation(phenomenonTime, (Polygon) gmlObject, toCRS);

//      free resources by releasing unmashaller instance
      releaseUnMarshaller(unMarshaller);
      _logger.info("GML geometry is :\n" + result);
      return result;
   }

   /**
    * Returns the geometry representation of a simple gml:AbstractGeometry instance. Current implementation accommodates Point(s) and Polygon(s)
    */
   @Deprecated
   public String getGeometryRepresentation(Node gmlNode) throws JAXBException {
      String result = getGeometryRepresentation(null, gmlNode,null);
      return result;
   }

   public String getBoundingPolygonRepresentation(Date phenomenonTime, Node[] gmlElements, String toCRS) {
      String result = "";
      try {
         Unmarshaller unMarshaller = acquireUnMarshaller();
         List<AbstractGeometryType> geometryList = new ArrayList<AbstractGeometryType>();
//            traverse all comprising primitive nodes; these are assumed to be Point(s)

         for (Node gmlNode : gmlElements)
            if ((gmlNode instanceof Element) && ((Element) gmlNode).getNamespaceURI().equalsIgnoreCase(SUPPORT_GML_SPEC) && ((Element) gmlNode).getLocalName().
                    equalsIgnoreCase("Point"))
               try {
                  _logger.debug("getGeography Representation: node is gml Point element");

                  Object gmlObject = unMarshaller.unmarshal(gmlNode);
                  PointType source;
                  org.opengis.geometry.primitive.Point primObect;

                  GeometryEditor geoEditor = new GeometryEditor();
                  if (gmlObject instanceof JAXBElement)
                     source = (PointType) ((JAXBElement) gmlObject).getValue();
                  else
                     source = (PointType) gmlObject;

                  PointType point = processPointType(phenomenonTime, null, (PointType) source, toCRS);

                  geometryList.add(point);

               }
               catch (Exception ex) {
                  if((ex instanceof org.opengis.util.FactoryException) && !_logger.isDebugEnabled())
                     ;
                  else
                     _logger.error("Exception while processing gml element", ex);
                  continue;
               }

            else if ((gmlNode instanceof Element) && ((Element) gmlNode).getNamespaceURI().equalsIgnoreCase(SUPPORT_GML_SPEC) && ((Element) gmlNode).
                    getLocalName().equalsIgnoreCase("MultiPoint")) {
               _logger.debug("getGeography Representation: node is gml MultiPoint element");

               Object gmlObject = unMarshaller.unmarshal(gmlNode);
               org.geotoolkit.gml.xml.v321.MultiPointType source;

               GeometryEditor geoEditor = new GeometryEditor();
               if (gmlObject instanceof JAXBElement)
                  source = (org.geotoolkit.gml.xml.v321.MultiPointType) ((JAXBElement) gmlObject).getValue();
               else
                  source = (org.geotoolkit.gml.xml.v321.MultiPointType) gmlObject;

               org.geotoolkit.gml.xml.v321.MultiPointType mPointType = source;
               final CRSMapping pMapping = MetadataHandler.getSupportedCRSMapping(mPointType.getSrsName());

               if (mPointType.getPointMember() != null)
                  for (PointPropertyType pointPropertyType : mPointType.getPointMember())
                     pointPropertyType.setPoint(this.processPointType(phenomenonTime, pMapping, pointPropertyType.getPoint(),toCRS));

               else if (mPointType.getPointMembers() != null) {
                  PointArrayPropertyType pArrayType = mPointType.getPointMembers();
                  List<PointType> pointTypes = pArrayType.getPoint();
                  for (int l = 0; l < pointTypes.size(); l++)
                     pointTypes.set(l, this.processPointType(phenomenonTime, pMapping, pointTypes.get(l),toCRS));
                  mPointType.setPointMembers(pArrayType);
               }
               geometryList.add(mPointType);
            } else if ((gmlNode instanceof Element) && ((Element) gmlNode).getNamespaceURI().equalsIgnoreCase(SUPPORT_GML_SPEC) && ((Element) gmlNode).
                    getLocalName().equalsIgnoreCase("Polygon")) {
               _logger.debug("getGeography Representation: node is gml MultiPoint element");

               Object gmlObject = unMarshaller.unmarshal(gmlNode);
               org.geotoolkit.gml.xml.v321.PolygonType source;

               GeometryEditor geoEditor = new GeometryEditor();
               if (gmlObject instanceof JAXBElement)
                  source = (org.geotoolkit.gml.xml.v321.PolygonType) ((JAXBElement) gmlObject).getValue();
               else
                  source = (org.geotoolkit.gml.xml.v321.PolygonType) gmlObject;

               source = this.processPolygonType(phenomenonTime, null, source,toCRS);

               geometryList.add(source);
            } else if ((gmlNode instanceof Element) && ((Element) gmlNode).getNamespaceURI().equalsIgnoreCase(SUPPORT_GML_SPEC) && ((Element) gmlNode).
                    getLocalName().equalsIgnoreCase("PolyhedralSurface")) {
               _logger.debug("getGeography Representation: node is gml MultiPoint element");

               Object gmlObject = unMarshaller.unmarshal(gmlNode);
               org.geotoolkit.gml.xml.v321.SurfaceType source;

               GeometryEditor geoEditor = new GeometryEditor();
               if (gmlObject instanceof JAXBElement)
                  source = (org.geotoolkit.gml.xml.v321.SurfaceType) ((JAXBElement) gmlObject).getValue();
               else
                  source = (org.geotoolkit.gml.xml.v321.SurfaceType) gmlObject;

               source = this.processPolyhedralSurface(phenomenonTime, null, source,toCRS);

               geometryList.add(source);
            } else {
               _logger.error("Error while processing gml representation. Either a not supported or not valid gml representation was provided ");
               continue;
            }

         JAXBElement rootElement;

         if (geometryList.size() > 1) {
            AbstractGeometryType geometryType = new MultiGeometryType();
            GeometryArrayPropertyType arrayType = new GeometryArrayPropertyType();
            for (AbstractGeometryType member : geometryList)
               arrayType.getAbstractGeometry().add(new JAXBElement(new QName(MetadataHandler.GML32_NAMESPACE, member.getClass().getSimpleName().replace("Type",
                       "")), member.getClass(), member));
            ((MultiGeometryType) geometryType).setGeometryMembers(arrayType);
            rootElement = new JAXBElement(new QName(MetadataHandler.GML32_NAMESPACE, "MultiGeometry"), MultiGeometryType.class, geometryType);
         } else
        if (geometryList.get(0).getClass().getSimpleName().replace("Type", "").equalsIgnoreCase("Surface"))
               rootElement = new JAXBElement(new QName(MetadataHandler.GML32_NAMESPACE, "PolyhedralSurface"),
                       geometryList.get(0).getClass(), geometryList.get(0));
            else
               rootElement = new JAXBElement(new QName(MetadataHandler.GML32_NAMESPACE, geometryList.get(0).getClass().getSimpleName().replace("Type", "")),
                       geometryList.get(0).getClass(), geometryList.get(0));

//                serialize the content of the update polygon to retrieve the gml content that will be inserted in the db
         StringWriter strWriter = new StringWriter();
         Marshaller _marshaller = this.acquireMarshaller();
         _marshaller.marshal(rootElement, strWriter);
         releaseMarshaller(_marshaller);
         strWriter.flush();
         result = strWriter.getBuffer().toString();
         if(result.contains("PolygonPatch") && result.contains(":patches"))
             result = result.replaceAll(":patches", ":polygonPatches");
      }
      catch (Exception ex) {
         _logger.error("Exception while processing gml element", ex);
      }

      finally {
         return result;
      }
   }
   
    
////////////////////////////////////////////////////////////////////////
//     Internal methods used for processing gml instances
    
    /**
     */
   private String getPointGeoRepresentation(Date phenomenonTime, Point point, String toCRS) {
        _logger.debug("getPointGeoRepresentation: point ::"+point.toString());
        String result = "";
        try {
            point  = processPointType(phenomenonTime,null,(PointType)point, toCRS);
            
            StringWriter strWriter = new StringWriter();
            JAXBElement rootElement = new JAXBElement(new QName(MetadataHandler.GML32_NAMESPACE,"Point"),PointType.class,(PointType)point);
            Marshaller _marshaller = this.acquireMarshaller();
            _marshaller.marshal(rootElement, strWriter);
            releaseMarshaller(_marshaller);
            strWriter.flush();            
            result = strWriter.getBuffer().toString();
            _logger.debug("getPointGeoRepresentation: result is ::"+result);
            
        } catch (Exception ex) {
            _logger.error("Error in getPolygonGeoRepresentation", ex);
        } finally {
            _logger.debug("getPointGeoRepresentation:: final is reached");
            return result;
        }
   }
   
   private PointType processPointType(Date phenomenonTime, CRSMapping origMapping, PointType point, String toCRS) throws UnmodifiableGeometryException, GMLTransformationException {
        int dimensions = point.getPos()!=null?point.getPos().getValue().size():point.getCoordinates().getValues().size();
//            makes sure that in case of two dimensional points the z vertice is set to null so as to support proper serialization to WKT format
        CRSMapping mapping = GeometryMetadataHandler.getSupportedCRSMapping(point.getSrsName());
        if(mapping==null)
            mapping=origMapping;
        
        CRSMapping destMapping = mapping;
        PointCoordinateEditor pointEditor = new PointCoordinateEditor(mapping,SUPPORTED_GEOMETRY._3D);

        if(toCRS!=null){
            destMapping = GeometryMetadataHandler.getSupportedCRSMapping(toCRS);
            pointEditor.setDefaultDestinationMapping(destMapping);
        }
        
        _logger.debug("About to edit points !!");
        if(point.getPos()!=null){
            List<DirectPositionType> positionList = pointEditor.editDirectPositionList(phenomenonTime, Arrays.asList(new DirectPosition[]{point.getPos()}));
            point.setDirectPosition(positionList.get(0));
        }
        else{
            List<CoordinatesType> cordList = pointEditor.editCordinates(phenomenonTime, Arrays.asList(new Coordinates[]{point.getCoordinates()}));
            point.setCoordinates(cordList.get(0));
        }
        
        if(destMapping!=null)
            point.setSrsName(destMapping.getSridCode());
        
        return point;
   }

   private String getPolygonGeoRepresentation(Date phenomenonTime, Polygon polygon, String toCRS) {
         _logger.debug("getPolygonGeoRepresentation: polygon is ::"+polygon.toString());
        String result = "";
        try {
           polygon= processPolygonType(phenomenonTime,null,polygon,toCRS);
                
//                serialize the content of the update polygon to retrieve the gml content that will be inserted in the db
            StringWriter strWriter = new StringWriter();
            JAXBElement rootElement = new JAXBElement(new QName(MetadataHandler.GML32_NAMESPACE,"Polygon"),PolygonType.class,(PolygonType)polygon);
            Marshaller _marshaller = this.acquireMarshaller();
            _marshaller.marshal(rootElement, strWriter);
            releaseMarshaller(_marshaller);
            strWriter.flush();            
            result = strWriter.getBuffer().toString();
            
            _logger.debug("getPolygonGeoRepresentation: result is ::"+result);

        } catch (Exception ex) {
            _logger.error("Error in getPolygonGeoRepresentation", ex);
        }
        return result;
    }

   private PolygonType processPolygonType(Date phenomenonTime, CRSMapping pMapping, Polygon polygon, String toCRS) throws UnmodifiableGeometryException, IndexOutOfBoundsException{
        CRSMapping mapping = GeometryMetadataHandler.getSupportedCRSMapping(polygon.getSrsName());
        if(mapping==null)
            mapping=pMapping;
                
        CRSMapping destMapping = mapping;

        if(toCRS!=null){
            destMapping = GeometryMetadataHandler.getSupportedCRSMapping(toCRS);
        }
        
        AbstractRingProperty extRingProp= polygon.getExterior();
        processAbstractRing(phenomenonTime, extRingProp,mapping,toCRS);
        
        if(destMapping!=null)
            polygon.setSrsName(destMapping.getSridCode());

        return (PolygonType)polygon;
   }

   private String getPolyhedralSurfaceRepresentation(Date phenomenoTime, SurfaceType surface, String toCRS){
         _logger.debug("getPolygonGeoRepresentation: polygon is ::"+surface.toString());
        String result = "";
        try {
           surface= processPolyhedralSurface(phenomenoTime,null,surface ,toCRS);
//                serialize the content of the update surface to retrieve the gml content that will be inserted in the db
            StringWriter strWriter = new StringWriter();
            JAXBElement rootElement = new JAXBElement(new QName(MetadataHandler.GML32_NAMESPACE,"PolyhedralSurface"),SurfaceType.class,(SurfaceType)surface);
            Marshaller _marshaller = this.acquireMarshaller();
            _marshaller.marshal(rootElement, strWriter);
            releaseMarshaller(_marshaller);
            strWriter.flush();            
            result = strWriter.getBuffer().toString();
            
            _logger.debug("getPolyhedralSurfaceRepresentation: result is ::"+result);

        } catch (Exception ex) {
            _logger.error("Error in getPolyhedralSurfaceRepresentation", ex);
        }
        return result;
      
   }
   
//   processing of polyhedral surface types 
   private SurfaceType processPolyhedralSurface(Date phenomenonTime, CRSMapping pMapping,  SurfaceType polySurface, String toCRS) {
       CRSMapping mapping = GeometryMetadataHandler.getSupportedCRSMapping(polySurface.getSrsName());
       if(mapping==null)
           mapping=pMapping;

//       PointCoordinateEditor pointEditor = new PointCoordinateEditor(mapping,SUPPORTED_GEOMETRY._3D);
       SurfacePatchArrayPropertyType patchArray = polySurface.getPatches().getValue();
//        go through the list of supported patches
       for(JAXBElement patchJXB: patchArray.getAbstractSurfacePatch()){
//           the list of supported patches in a surface includes:
          if(patchJXB.getValue() instanceof PolygonPatchType){
//              process exterior ring of surface patch
             processAbstractRing(phenomenonTime,((PolygonPatchType)patchJXB.getValue()).getExterior(), mapping,toCRS);
          }
//          in case of other type of srufaces; currently not supporting anything else
          else
             ;
        }
       
      polySurface = addMissingPolygonPatches(polySurface);
      
      return  polySurface;
   }

   private SurfaceType addMissingPolygonPatches(SurfaceType polySurface) {
    String srsName = polySurface.getSrsName();
    SurfacePatchArrayPropertyType patchArray = polySurface.getPatches().getValue();
    
    if(patchArray.getAbstractSurfacePatch().size()==2){
        Double[][][] patchCoordinates  = new Double[6][5][3];

        int i=0;
        for(JAXBElement patchJXB: patchArray.getAbstractSurfacePatch()){
//           the list of supported patches in a surface includes:
          if(patchJXB.getValue() instanceof PolygonPatchType){
              AbstractRingProperty extRingProp = ((PolygonPatchType)patchJXB.getValue()).getExterior();
              if (extRingProp != null && extRingProp.getAbstractRing().getClass() == LinearRingType.class) {
//                    go through all pos or point references in the exterion LinearRing
                  List<JAXBElement<?>> posOrPointList = ((LinearRingType) extRingProp.getAbstractRing()).getPosOrPointPropertyOrPointRep();
                  int j=0;
                  for (JAXBElement posOrPoint : posOrPointList){ 
//                        if the element if the list is a direct position then just edit the coordinates and place the updated value in the element
                      if (posOrPoint.getDeclaredType() == DirectPositionType.class) {
                          double[] coords = ((DirectPositionType)posOrPoint.getValue()).getCoordinate();
//                          get the first available srsName
                          if(srsName==null||srsName.isEmpty())
                              srsName = ((DirectPositionType)posOrPoint.getValue()).getSrsName();
                          
                          int k=0;
                          for(double val:coords){
                              patchCoordinates[i][j][k]=val;
                              k++;
                          }
                      } //                      if the element is a Point then update the internal pos coordinates and set the srs name to the one used throughout the polygon
                      else if (posOrPoint.getDeclaredType() == PointType.class) {
                          PointType pointT = (PointType) posOrPoint.getValue();

//                          get the first available srsName
                          if(srsName==null||srsName.isEmpty())
                              srsName = pointT.getSrsName();

                          double[] coords = pointT.getDirectPosition().getCoordinate();
                          int k=0;
                          for(double val:coords){
                              patchCoordinates[i][j][k]=val;
                              k++;
                          }
                      }
                      j++;
                  }
              }
          }
            i++;
       }
//        max and min altitudes
       double maxZ=Double.MIN_VALUE, minZ=Double.MAX_VALUE;
       double maxX=Double.MIN_VALUE, minX=Double.MAX_VALUE;
       double maxY=Double.MIN_VALUE, minY=Double.MAX_VALUE;
//       assign max and min altitude values
       for(i=0;i<2;i++)
           for(int j=0;j<5;j++){
               maxX= Math.max(maxX, patchCoordinates[i][j][0]);
               minX= Math.min(minX, patchCoordinates[i][j][0]);               
               maxY= Math.max(maxY, patchCoordinates[i][j][1]);
               minY= Math.min(minY, patchCoordinates[i][j][1]);               
               maxZ= Math.max(maxZ, patchCoordinates[i][j][2]);
               minZ= Math.min(minZ, patchCoordinates[i][j][2]);               
           }
       
        patchCoordinates[2] = new Double[][]{{minX,minY,minZ},
                                               {minX,maxY,minZ},
                                               {minX,maxY,maxZ},
                                               {minX,minY,maxZ},
                                               {minX,minY,minZ}};
        patchCoordinates[3]=  new Double[][]{{maxX,minY,minZ},
                                               {maxX,maxY,minZ},
                                               {maxX,maxY,maxZ},
                                               {maxX,minY,maxZ},
                                               {maxX,minY,minZ}};
        patchCoordinates[4] = new Double[][]{{minX,minY,minZ},
                                               {maxX,minY,minZ},
                                               {maxX,minY,maxZ},
                                               {minX,minY,maxZ},
                                               {minX,minY,minZ}};
        patchCoordinates[5]=  new Double[][]{{minX,maxY,minZ},
                                               {maxX,maxY,minZ},
                                               {maxX,maxY,maxZ},
                                               {minX,maxY,maxZ},
                                               {minX,maxY,minZ}};
        
//        change the content of the polyhedralSurface
        JAXBElement clonePatch = patchArray.getAbstractSurfacePatch().get(0);
        
        for(i=0;i<4;i++)
            patchArray.getAbstractSurfacePatch().add(clonePatch);
        
        i=0;
        for(JAXBElement patchJXB: patchArray.getAbstractSurfacePatch()){
                DirectPositionListType positionList = new DirectPositionListType();
                positionList.setSrsDimension(3);
 
                if(srsName!=null)
                    positionList.setSrsName(srsName);
                
                List<Double> positionValues=  new LinkedList<Double>();
                for(int j=0;j<5;j++)
                    positionValues.addAll(Arrays.asList(patchCoordinates[i][j]));
                positionList.setValue(positionValues);
                ((LinearRingType) ((PolygonPatchType)patchJXB.getValue()).getExterior().getAbstractRing()).setCoordinates(null);
                ((LinearRingType) ((PolygonPatchType)patchJXB.getValue()).getExterior().getAbstractRing()).getPosOrPointPropertyOrPointRep().clear();
                ((LinearRingType) ((PolygonPatchType)patchJXB.getValue()).getExterior().getAbstractRing()).setPosList(positionList);
                i++;
                
                }

                
            }
    
    return polySurface;
    }

   
   private void processAbstractRing(Date phenomenonTime, AbstractRingProperty extRingProp, CRSMapping mapping, String toCRS) throws UnmodifiableGeometryException, IndexOutOfBoundsException{
      
      CRSMapping destMapping = mapping;
      PointCoordinateEditor pointEditor = new PointCoordinateEditor(mapping, SUPPORTED_GEOMETRY._3D);

      if (toCRS != null) {
         destMapping = GeometryMetadataHandler.getSupportedCRSMapping(toCRS);
         pointEditor.setDefaultDestinationMapping(destMapping);
      }

      if (extRingProp != null && extRingProp.getAbstractRing().getClass() == LinearRingType.class) {
//                    go through all pos or point references in the exterion LinearRing
         List<JAXBElement<?>> posOrPointList = ((LinearRingType) extRingProp.getAbstractRing()).getPosOrPointPropertyOrPointRep();
         
         if(destMapping!=null)
             extRingProp.getAbstractRing().setSrsName(destMapping.getSridCode());
         
         for (JAXBElement posOrPoint : posOrPointList)
//                        if the element if the list is a direct position then just edit the coordinates and place the updated value in the element
            if (posOrPoint.getDeclaredType() == DirectPositionType.class){
               posOrPoint.setValue(pointEditor.editDirectPosition(phenomenonTime,(DirectPositionType) posOrPoint.getValue()));
            }
//                      if the element is a Point then update the internal pos coordinates and set the srs name to the one used throughout the polygon
            else if (posOrPoint.getDeclaredType() == PointType.class) {
               PointType pointT = (PointType) posOrPoint.getValue();
               pointT.setSrsName(destMapping.getSridCode());
               pointT.setDirectPosition(pointEditor.editDirectPosition(phenomenonTime,(DirectPosition) pointT.getPos()));
               posOrPoint.setValue(pointT);
            }
         
//             @todo: if the polygon is defined through coordinates then process the list of coordinates
         Coordinates cordinatesList = ((LinearRing) extRingProp.getAbstractRing()).getCoordinates();

      }      
      
//         @todo       process the exterion if this is a Ring and not a LinearRing
      else if(extRingProp!=null && extRingProp.getAbstractRing().getClass()== RingType.class){
         ((Ring)extRingProp.getAbstractRing()).getCurveMember();
      }
//                @todo need to process the internal rings as well 
   }

   private void transformAbstractRing(Date phenomenonTime, AbstractRingProperty extRingProp, CRSMapping mapping, String toCRS) throws UnmodifiableGeometryException, IndexOutOfBoundsException{
      
      CRSMapping destMapping = mapping;
      PointCoordinateEditor pointEditor = new PointCoordinateEditor(mapping, SUPPORTED_GEOMETRY._3D);

      if (toCRS != null && extRingProp != null && extRingProp.getAbstractRing().getClass() == LinearRingType.class) {
//                    go through all pos or point references in the exterion LinearRing
         destMapping = GeometryMetadataHandler.getSupportedCRSMapping(toCRS);
         pointEditor.setDefaultDestinationMapping(destMapping);
         
         List<JAXBElement<?>> posOrPointList = ((LinearRingType) extRingProp.getAbstractRing()).getPosOrPointPropertyOrPointRep();     
         
         if(posOrPointList.isEmpty() && ((LinearRingType) extRingProp.getAbstractRing()).getPosList()!=null){
             int count = ((LinearRingType) extRingProp.getAbstractRing()).getPosList().getValue().size();
             int dimensions =((LinearRingType) extRingProp.getAbstractRing()).getPosList().getSrsDimension();
             
             if(((LinearRingType) extRingProp.getAbstractRing()).getPosList().getSrsName()!=null && !((LinearRingType) extRingProp.getAbstractRing()).getPosList().getSrsName().isEmpty())
                    mapping = GeometryMetadataHandler.getSupportedCRSMapping(((LinearRingType) extRingProp.getAbstractRing()).getPosList().getSrsName());
             
             List<Double> positionValues = new LinkedList();
             for(int i=0;i<count/dimensions;i++){
                 double[] originalPositionValues=new double[0];
                 if(dimensions==2)
                    originalPositionValues = new double[]{((LinearRingType) extRingProp.getAbstractRing()).getPosList().getValue().get(i*dimensions),
                                           ((LinearRingType) extRingProp.getAbstractRing()).getPosList().getValue().get((i*dimensions)+1)};
                 else if(dimensions==3)
                    originalPositionValues =  new double[]{((LinearRingType) extRingProp.getAbstractRing()).getPosList().getValue().get(i*dimensions),
                                         ((LinearRingType) extRingProp.getAbstractRing()).getPosList().getValue().get((i*dimensions)+1),
                                         ((LinearRingType) extRingProp.getAbstractRing()).getPosList().getValue().get((i*dimensions)+2)};
                                         
                 double[] transformedPosition = pointEditor.transformCRS(phenomenonTime, originalPositionValues, mapping, destMapping);
                 for(double value:transformedPosition)
                    positionValues.add(value);
             }
             ((LinearRingType) extRingProp.getAbstractRing()).getPosList().setValue(positionValues);
             if(destMapping!=null)
               extRingProp.getAbstractRing().setSrsName(destMapping.getSridCode());
         }
         
         else if(!posOrPointList.isEmpty())
          for (JAXBElement posOrPoint : posOrPointList)
//                        if the element if the list is a direct position then just edit the coordinates and place the updated value in the element
            if (posOrPoint.getDeclaredType() == DirectPositionType.class){
               posOrPoint.setValue(pointEditor.transformDirectPosition(phenomenonTime,(DirectPositionType) posOrPoint.getValue()));
                if(destMapping!=null)
                  extRingProp.getAbstractRing().setSrsName(destMapping.getSridCode());
            }
//                      if the element is a Point then update the internal pos coordinates and set the srs name to the one used throughout the polygon
            else if (posOrPoint.getDeclaredType() == PointType.class) {
               PointType pointT = (PointType) posOrPoint.getValue();
               
               if(destMapping!=null)
                    pointT.setSrsName(destMapping.getSridCode());
               
               pointT.setDirectPosition(pointEditor.transformDirectPosition(phenomenonTime,(DirectPosition) pointT.getPos()));
               posOrPoint.setValue(pointT);
            }
         
//             @todo: if the polygon is defined through coordinates then process the list of coordinates
         Coordinates cordinatesList = ((LinearRing) extRingProp.getAbstractRing()).getCoordinates();

      }      
      
//         @todo       process the exterion if this is a Ring and not a LinearRing
      else if(extRingProp!=null && extRingProp.getAbstractRing().getClass()== RingType.class){
         ((Ring)extRingProp.getAbstractRing()).getCurveMember();
      }
//                @todo need to process the internal rings as well 
   }

   private String transformPointGeoRepresentation(Date transformationTime, PointType point, String toCRS) throws JAXBException {
      String result="";
//            makes sure that in case of two dimensional points the z vertice is set to null so as to support proper serialization to WKT format
        CRSMapping originalMapping = GeometryMetadataHandler.getSupportedCRSMapping(point.getSrsName());
        
        if(originalMapping==null){
           _logger.error("Unable to identify the original CRS.");
            return result;
        }
        
        CRSMapping destMapping = GeometryMetadataHandler.getSupportedCRSMapping(toCRS);
        PointCoordinateEditor pointEditor = new PointCoordinateEditor(originalMapping,SUPPORTED_GEOMETRY._3D);
        pointEditor.setDefaultDestinationMapping(destMapping);
        
        if(toCRS!=null){
            destMapping = GeometryMetadataHandler.getSupportedCRSMapping(toCRS);
            pointEditor.setDefaultDestinationMapping(destMapping);
        }
        
        _logger.debug("About to edit points !!");

        if(point.getPos()!=null){
            List<DirectPositionType> positionList = pointEditor.tranformDirectPositionList(transformationTime, Arrays.asList(new DirectPosition[]{point.getPos()}));
            try{
            point.setDirectPosition(positionList.get(0));
            }
            catch(Exception ex){
               if(ex instanceof org.opengis.util.FactoryException)
                  _logger.debug(null,ex);
            }
        }
        else{
            List<CoordinatesType> cordList = pointEditor.transformCordinates(transformationTime, Arrays.asList(new Coordinates[]{point.getCoordinates()}));
            point.setCoordinates(cordList.get(0));
        }
        
        if(destMapping!=null)
            point.setSrsName(destMapping.getSridCode());
        
      StringWriter strWriter = new StringWriter();
      JAXBElement rootElement = new JAXBElement(new QName(MetadataHandler.GML32_NAMESPACE,"Point"),PointType.class,point);
      Marshaller _marshaller = this.acquireMarshaller();
      _marshaller.marshal(rootElement, strWriter);
      releaseMarshaller(_marshaller);
      strWriter.flush();            
      result = strWriter.getBuffer().toString();
      _logger.debug("getPointGeoRepresentation: result is :: "+result);
        
      return result;
   }

   private String transformMultiPointGeoRepresentation(Date transformationTime, org.geotoolkit.gml.xml.v321.MultiPointType mpoint, String toCRS) throws JAXBException{
       String result = "";
        CRSMapping originalMapping = GeometryMetadataHandler.getSupportedCRSMapping(mpoint.getSrsName());
        
        if(originalMapping==null){
           _logger.error("Unable to identify the original CRS.");
        }
        
        CRSMapping destMapping = GeometryMetadataHandler.getSupportedCRSMapping(toCRS);
        PointCoordinateEditor pointEditor = new PointCoordinateEditor(originalMapping,SUPPORTED_GEOMETRY._3D);
        pointEditor.setDefaultDestinationMapping(destMapping);
        
        if(toCRS!=null){
            destMapping = GeometryMetadataHandler.getSupportedCRSMapping(toCRS);
            pointEditor.setDefaultDestinationMapping(destMapping);
        }
        
        _logger.debug("About to edit points !!");

        for(PointPropertyType point:mpoint.getPointMember())
            if(point.getPoint()!=null){
                List<DirectPositionType> positionList = pointEditor.tranformDirectPositionList(transformationTime, Arrays.asList(new DirectPosition[]{point.getPoint().getPos()}));
                try{
                point.getPoint().setDirectPosition(positionList.get(0));
                }
                catch(Exception ex){
                   if(ex instanceof org.opengis.util.FactoryException)
                      _logger.debug(null,ex);
                }
            }
            else{
                List<CoordinatesType> cordList = pointEditor.transformCordinates(transformationTime, Arrays.asList(new Coordinates[]{point.getPoint().getCoordinates()}));
                point.getPoint().setCoordinates(cordList.get(0));
            }
        
        if(destMapping!=null)
            mpoint.setSrsName(destMapping.getSridCode());
        
      StringWriter strWriter = new StringWriter();
      JAXBElement rootElement = new JAXBElement(new QName(MetadataHandler.GML32_NAMESPACE,"MultiPoint"),org.geotoolkit.gml.xml.v321.MultiPointType.class,mpoint);
      Marshaller _marshaller = this.acquireMarshaller();
      _marshaller.marshal(rootElement, strWriter);
      releaseMarshaller(_marshaller);
      strWriter.flush();            
      result = strWriter.getBuffer().toString();
      _logger.debug("getPointGeoRepresentation: result is :: "+result);

       return result;
   }
   private String transformPolygonGeoRepresentation(Date transformationTime, Polygon polygon, String toCRS) throws JAXBException {
      String result = "";

      CRSMapping mapping = GeometryMetadataHandler.getSupportedCRSMapping(polygon.getSrsName());
        if(mapping==null){
           _logger.error("Unable to identify the original CRS.");
//            return result;
        }
        
        CRSMapping destMapping = mapping;

        if(toCRS!=null){
            destMapping = GeometryMetadataHandler.getSupportedCRSMapping(toCRS);
        }
        
        
      AbstractRingProperty extRingProp= polygon.getExterior();
      transformAbstractRing(transformationTime, extRingProp,mapping,toCRS);
      
      if(destMapping!=null)
        polygon.setSrsName(destMapping.getSridCode());
      
      StringWriter strWriter = new StringWriter();
      JAXBElement rootElement = new JAXBElement(new QName(MetadataHandler.GML32_NAMESPACE, "Polygon"), PolygonType.class, (PolygonType) polygon);
      Marshaller _marshaller = this.acquireMarshaller();
      _marshaller.marshal(rootElement, strWriter);
      releaseMarshaller(_marshaller);
      strWriter.flush();
      result = strWriter.getBuffer().toString();
            
      _logger.debug("getPolygonGeoRepresentation: result is ::"+result);

      return result;
   }  

   private String transformPolyhedralSurfaceRepresentation(Date transformationTime, SurfaceType polySurface, String toCRS) throws JAXBException{
      String result = "";

      CRSMapping mapping = GeometryMetadataHandler.getSupportedCRSMapping(polySurface.getSrsName());
        if(mapping==null){
           _logger.error("Unable to identify the original CRS.");
//            return result;
        }
        
        CRSMapping destMapping = mapping;

        if(toCRS!=null){
            destMapping = GeometryMetadataHandler.getSupportedCRSMapping(toCRS);
        }
        

        //       PointCoordinateEditor pointEditor = new PointCoordinateEditor(mapping,SUPPORTED_GEOMETRY._3D);
       SurfacePatchArrayPropertyType patchArray = polySurface.getPatches().getValue();
//        go through the list of supported patches
       for(JAXBElement patchJXB: patchArray.getAbstractSurfacePatch()){
//           the list of supported patches in a surface includes:
          if(patchJXB.getValue() instanceof PolygonPatchType){
//              process exterior ring of surface patch
            transformAbstractRing(transformationTime, ((PolygonPatchType)patchJXB.getValue()).getExterior(),mapping,toCRS);
          }
//          in case of other type of srufaces; currently not supporting anything else
          else
             _logger.debug("Other types that polygon patch types are not currently supported for transformation");
        }

        
      if(destMapping!=null)
        polySurface.setSrsName(destMapping.getSridCode());
      
      StringWriter strWriter = new StringWriter();
      JAXBElement rootElement = new JAXBElement(new QName(MetadataHandler.GML32_NAMESPACE, "PolyhedralSurface"), SurfaceType.class, (SurfaceType) polySurface);
      Marshaller _marshaller = this.acquireMarshaller();
      _marshaller.marshal(rootElement, strWriter);
      releaseMarshaller(_marshaller);
      strWriter.flush();
      result = strWriter.getBuffer().toString();
            
      _logger.debug("getPolygonGeoRepresentation: result is ::"+result);

      return result;
   
   }
}   
/***
 *   
 * 
 **/
 