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

import static eu.dnetlib.espas.util.MetadataHandler.serializeNode;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.util.Collection;
import java.util.Date;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import org.apache.commons.lang.time.DateUtils;
import org.apache.log4j.Logger;
import org.apache.log4j.Priority;
import org.geotoolkit.gml.xml.v321.VerticalCRSType;
import org.geotoolkit.metadata.iso.extent.AbstractGeographicExtent;
import org.geotoolkit.metadata.iso.extent.DefaultExtent;
import org.geotoolkit.metadata.iso.extent.DefaultGeographicBoundingBox;
import org.geotoolkit.metadata.iso.extent.DefaultVerticalExtent;
import org.geotoolkit.xml.MarshallerPool;
import org.opengis.metadata.extent.BoundingPolygon;
import org.opengis.metadata.extent.Extent;
import org.opengis.metadata.extent.GeographicBoundingBox;
import org.opengis.metadata.extent.VerticalExtent;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 *  The <code>GeometryMetadataHandler</code> class provides static methods for managing the manipulation of OGC metadata elements that are used 
 * in the description of ESPAS information. The provided methods are manage the stripping of geometry related information out of the OGC surrounding 
 * elements. In most of the provided implementations important is the role of the associated CRS system specified in the given elements as this defines 
 * the transformation of the provided input to the one expected by the ESPAS db.
 * 
 * @author georgeathanasopoulos
 */

public class GeometryMetadataHandler extends MetadataHandler {

    private static Logger _logger = Logger.getLogger(GeometryMetadataHandler.class);
    private static String[] datePatterns = new String[]{"yyyy-MM-dd' 'HH:mm:ss.SSS","yyyy-MM-dd'T'HH:mm:ss.SSS","yyyy-MM-dd'T'HH:mm:ssZ","yyyy-MM-dd'T'HH:mm:ss'Z'","yyyy-MM-dd'T'HH:mm:ssZZ","yyyy-MM-dd'T'HH:mm:ss.SSSZ","yyyy-MM-dd' 'HH:mm:ss.SSSZ"};

    private static GMLGeometryTransformationHandler gmlHandler;

  //       static initializer used for setting the context of the required JAXB classes used through out the list of provided operations
    static {
        gmlHandler = new GMLGeometryTransformationHandler();
        
//      marshaller pool used for handling input metadata 
        try {

            metadataMarshPool = new MarshallerPool(MarshallerPool.defaultClassesToBeBound());
        } catch (JAXBException ex) {
            _logger.log(Priority.ERROR, null, ex);
        }
    }

    public static boolean hasGeoExtent(Node extentContent) throws IOException, JAXBException {
        _logger.log(Priority.INFO, "hasGeoExtent");


        String strCont = serializeNode(extentContent);
        _logger.log(Priority.INFO, "Content is: \n" + strCont);

        Unmarshaller unMarshaller = metadataMarshPool.acquireUnmarshaller();
        Object extent = unMarshaller.unmarshal(extentContent);
        metadataMarshPool.release(unMarshaller);

        if (extent != null && (extent instanceof AbstractGeographicExtent))
            return true;

        return false;
    }

    public static boolean isGeoExtent(Node extentContent) throws IOException, JAXBException {
        _logger.log(Priority.INFO, "hasGeoExtent");


            String strCont = serializeNode(extentContent);
            _logger.log(Priority.INFO, "Content is: \n" + strCont);

            Unmarshaller unMarshaller = metadataMarshPool.acquireUnmarshaller();
            Object extent = unMarshaller.unmarshal(extentContent);
            metadataMarshPool.release(unMarshaller);
            
            if(extent==null)
                return false;

            if(extent instanceof BoundingPolygon)
                return true;
            else if(extent instanceof GeographicBoundingBox)
                return true;


        return false;
    }

   public static String transformGeoLocation(Date transformTime, String geographicNode, String toCRS) throws IOException, JAXBException, ParseException, SAXException {
      initCRSAuthorityFactory();
      String result = "";
      Node geoNode = null;
      
      if(geographicNode==null || geographicNode.isEmpty())
         return result;
      
      InputStream geoExStream = new ByteArrayInputStream(geographicNode.getBytes());
      geoNode = xmlParser.parse(geoExStream).getFirstChild();
       result = gmlHandler.transformGeometryRepresentation(transformTime, geoNode, toCRS);
      return result;
   }

/** Calls the GMLTransformationHander for the extraction of the correct GML instance representation.
 */    
    public static String getGeoLocation(String time, Node geomLocationChild, String toCRS) throws JAXBException, ParseException{
      _logger.log(Priority.INFO, "Processing espas:GeometryLocation node with time mark:"+time+"\n");
       initCRSAuthorityFactory();
       String result = gmlHandler.getGeometryRepresentation(DateUtils.parseDate(time,datePatterns), geomLocationChild,toCRS);
       _logger.log(Priority.INFO, "GML geography postgis statement is: \n"+result);
       return result;            
    }
    
    @Deprecated
    public static String getGeoLocation(Node geomLocationChild) throws JAXBException{
      _logger.log(Priority.INFO, "Processing espas:GeometryLocation node \n");
       initCRSAuthorityFactory();

       String result = gmlHandler.getGeometryRepresentation(null,geomLocationChild,null);
       _logger.log(Priority.INFO, "GML geography postgis statement is: \n"+result);
       return result;            
    }
    

    public static String getGeoSrsName(Node geometryLocation) {
        _logger.log(Priority.INFO, "Processing espas:GeometryLocation node \n");
        try {
            String result = gmlHandler.getGeometrySrsName(geometryLocation);
            return result;
            
        } catch (JAXBException ex) {
            _logger.log(Priority.ERROR, null, ex);
            return "";
        }
    }
   
   public static String getGeoExtent(String geoExtentTime, Node geographicExtentNode, String toCRS) throws IOException, JAXBException, ParseException {
      _logger.log(Priority.INFO, "getGeoExtent");
      initCRSAuthorityFactory();

      String output = "";

      if (geographicExtentNode == null)
         return output;

      String strCont = serializeNode(geographicExtentNode);
      _logger.log(Priority.INFO, "Extent Time is: " + geoExtentTime + " Geo extent content is: \n" + strCont);

      Unmarshaller unMarshaller = metadataMarshPool.acquireUnmarshaller();
      Object defGeoExt = unMarshaller.unmarshal(geographicExtentNode);
      metadataMarshPool.release(unMarshaller);

//            handle internal gml representations
      if (defGeoExt != null && defGeoExt instanceof DefaultGeographicBoundingBox)
         output = ((DefaultGeographicBoundingBox) defGeoExt).toString();

      else if (defGeoExt != null && defGeoExt instanceof BoundingPolygon) {
         NodeList polygons = ((Element) geographicExtentNode).getElementsByTagNameNS(GMD_NAMESPACE, "polygon");

         Node[] gmlElements = new Node[polygons.getLength()];
         for (int i = 0; i < polygons.getLength(); i++) {
//               select only the first child element of each polygon. It is assumed that there will not be more than one representations inside a polygon
            gmlElements[i] = ((Element) polygons.item(i)).getElementsByTagNameNS(GML32_NAMESPACE, "*").item(0);
            strCont = serializeNode(gmlElements[i]);
            _logger.debug("Polygon Content is: \n" + strCont);
         }

         Date transformationDatetime = null;
         try {
            transformationDatetime = DateUtils.parseDate(geoExtentTime, datePatterns);
         }
         catch (ParseException pex) {
            _logger.debug("Failed to parse give date :" + geoExtentTime, pex);
            transformationDatetime = new Date();
         }

//           calculate bounding polyon representation
         output = gmlHandler.getBoundingPolygonRepresentation(transformationDatetime, gmlElements,toCRS);

      } else
         throw new IOException("GeoExtent extent cannot be initialized. The given gml node is neither a bounding box nor a bounding polygon representation!");

      return output;

   }
    
    @Deprecated
    public static String getGeoExtent(Node geographicExtentNode) throws IOException, JAXBException {
        _logger.log(Priority.INFO, "getGeoExtent");
        initCRSAuthorityFactory();

        String output = "";
    
        if(geographicExtentNode==null)
            return output;
        
        String strCont = serializeNode(geographicExtentNode);
        _logger.log(Priority.INFO, "Content is: \n" + strCont);

        Unmarshaller unMarshaller = metadataMarshPool.acquireUnmarshaller();
        Object defGeoExt = unMarshaller.unmarshal(geographicExtentNode);
        metadataMarshPool.release(unMarshaller);

//            handle internal gml representations
        if (defGeoExt != null && defGeoExt instanceof DefaultGeographicBoundingBox)
            output = ((DefaultGeographicBoundingBox) defGeoExt).toString();
        else if (defGeoExt != null && defGeoExt instanceof BoundingPolygon) {
            NodeList polygons = ((Element) geographicExtentNode).getElementsByTagNameNS(GMD_NAMESPACE, "polygon");

//                if(polygons.getLength()>1){
                Node[] gmlElements = new Node[polygons.getLength()];
                for (int i = 0; i < polygons.getLength(); i++) {
                    gmlElements[i]=((Element) polygons.item(i)).getElementsByTagNameNS(GML32_NAMESPACE, "*").item(0);
                    strCont = serializeNode(gmlElements[i]);
                    _logger.debug("Polygon Content is: \n" + strCont);
                    }
                output = gmlHandler.getBoundingPolygonRepresentation(null,gmlElements,null);

        } else
            throw new IOException("GeoExtent extent cannot be initialized!");

        return output;

    }

    /**
     * Asserts whether the provided extent node has vertical extensions.
     */
    public static boolean hasVerticalExtent(Node extentContent) {
        _logger.log(Priority.INFO, "hasVerticalExtent");
        if (extentContent == null)
            return false;
        try {

            String strCont = serializeNode(extentContent);
            _logger.log(Priority.INFO, "Content is: \n" + strCont);

            Unmarshaller unMarshaller = metadataMarshPool.acquireUnmarshaller();
            Object extent = unMarshaller.unmarshal(extentContent);
            metadataMarshPool.release(unMarshaller);

            Collection<VerticalExtent> vertElements = DefaultExtent.castOrCopy((Extent)extent).getVerticalElements();
            if (vertElements != null && !vertElements.isEmpty())
                return true;

        } catch (JAXBException ex) {
            _logger.log(Priority.ERROR, null, ex);
        } catch (IOException ex) {
            _logger.log(Priority.ERROR, null, ex);
        }

        return false;
    }
    
    
    public static String getVerticalExtentMin(Node verticalExtent) throws IOException, JAXBException {
        return getVerticalExtentMin(verticalExtent, null);
    }

    public static String getVerticalExtentMin(Node verticalExtent, String vSrsName) throws IOException, JAXBException {
        _logger.log(Priority.INFO, "getVerticalExtentMin");
        String output = "";

        if(verticalExtent==null)
            return output;

            String strCont = serializeNode(verticalExtent);
            _logger.log(Priority.INFO, "Content is: \n" + strCont);

            Unmarshaller unMarshaller = metadataMarshPool.acquireUnmarshaller();
            DefaultVerticalExtent defTempExt = (DefaultVerticalExtent) unMarshaller.unmarshal(verticalExtent);
            metadataMarshPool.release(unMarshaller);

            if (defTempExt != null){
                if(defTempExt.getMinimumValue()==null)
                    output= ((Element)((Element)verticalExtent).getElementsByTagNameNS(GMD_NAMESPACE,"minimumValue").item(0)).getElementsByTagNameNS(GCO_NAMESPACE,"Real").item(0).getFirstChild().getTextContent();
                else
                    output= defTempExt.getMinimumValue().toString();
            }
            else
                throw new IOException("GeoExtent extent cannot be initialized!");

            return output;
    }
    
    
    public static String getVerticalExtentMax(Node verticalExtent) throws IOException, JAXBException {
        return getVerticalExtentMax(verticalExtent,null);
    }
    
    
    public static String getVerticalExtentMax(Node verticalExtent, String vSrsName) throws IOException, JAXBException {
        _logger.log(Priority.INFO, "getVerticalExtentMax");
        String output = "";

        if(verticalExtent==null)
            return output;
            
            String strCont = serializeNode(verticalExtent);
            _logger.log(Priority.INFO, "Content is: \n" + strCont);

            Unmarshaller unMarshaller = metadataMarshPool.acquireUnmarshaller();
            DefaultVerticalExtent defTempExt = (DefaultVerticalExtent) unMarshaller.unmarshal(verticalExtent);
            metadataMarshPool.release(unMarshaller);

            if (defTempExt != null){
                if(defTempExt.getMaximumValue()==null){
                    NodeList list = ((Element)verticalExtent).getElementsByTagNameNS(GMD_NAMESPACE,"maximumValue");
                    list= ((Element)list.item(0)).getElementsByTagNameNS(GCO_NAMESPACE,"Real");
                    output= ((Element)list.item(0)).getFirstChild().getTextContent();
                }
                else
                    output= defTempExt.getMaximumValue().toString();
                }
            else
                throw new IOException("GeoExtent extent cannot be initialized!");

            return output;

    }
    
    /** Retrieves the vertical CRS assigned to a gmd:verticalExtent element. The vertical crs can be provided either as xlink:href reference to the verticalCRS element or 
     * as an embedded gml:VerticalCRS description.
     */
    public static String getVerticalSRSName(Node verticalExtent){
        _logger.log(Priority.INFO, "getVerticalSRSName");
        String output = "";
        try {
            
            String strCont = serializeNode(verticalExtent);
            _logger.log(Priority.INFO, "Content is: \n" + strCont);

            
            Unmarshaller unMarshaller = metadataMarshPool.acquireUnmarshaller();
            DefaultVerticalExtent defTempExt = (DefaultVerticalExtent) unMarshaller.unmarshal(verticalExtent);

            metadataMarshPool.release(unMarshaller);
            if (defTempExt != null && defTempExt.getVerticalCRS()!=null){
                    NodeList list = ((Element)verticalExtent).getElementsByTagNameNS(GMD_NAMESPACE,"verticalCRS");
                    output = ((Element)list.item(0)).getAttributeNS(XLINK1999_NAMESPACE,"href");
            }
//            in the case where there is no href attribute pointing to the VCRS system then this should be embeded in the description
            else{
                NodeList list = ((Element)verticalExtent).getElementsByTagNameNS(GML32_NAMESPACE,"VerticalCRS");
//                if no gml:VerticalCRS element is embeded then this is an invalid document
                if(list.getLength()==0)
                    throw new IOException("GeoExtent extent cannot be initialized!");

                unMarshaller = metadataMarshPool.acquireUnmarshaller();
                VerticalCRSType vCRSType = (VerticalCRSType) unMarshaller.unmarshal(list.item(0));
                output  = vCRSType.getIdentifier();
            }
             

        } catch (JAXBException ex) {
            _logger.log(Priority.ERROR, null, ex);
        } catch (IOException ex) {
            _logger.log(Priority.ERROR, null, ex);
        }
        finally {
            return output;
        }
    }

    /**
     * Returns the specified geometry location in terms of position. This transformation depends on the use of GeneralDirectPosition class of the
     * GeoToolkit.
     */
    
    @Deprecated
    public static String getGeometryLocation(Node geometryLocation) {
        _logger.log(Priority.INFO, "getGeometryLocation");
        String output = "";
        try {

            String strCont = serializeNode(geometryLocation);
            _logger.log(Priority.INFO, "Content is: \n" + strCont);

            output= gmlHandler.getGeometryRepresentation(geometryLocation);
            }
        
        finally {
            return output;
        }


    }
}
