/*
 * 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.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import org.apache.log4j.Logger;
import org.apache.log4j.Priority;
import org.geotoolkit.gml.xml.v321.VerticalCRSType;
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.GeographicExtent;
import org.opengis.metadata.extent.VerticalExtent;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;


/**
 *  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 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);
            
            Collection<GeographicExtent> geoElements = DefaultExtent.castOrCopy((Extent)extent).getGeographicElements();
            if (geoElements != null && !geoElements.isEmpty())
                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;
    }
    
/** Calls the GMLTransformationHander for the extraction of the correct GML instance representation.
 */    
    public static String getGeoLocation(Node geomLocationChild) throws JAXBException{
        _logger.log(Priority.INFO, "Processing espas:GeometryLocation node \n");
            String result = gmlHandler.getGeometryRepresentation(geomLocationChild);
            _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(Node geographicExtentNode) throws IOException, JAXBException {
        _logger.log(Priority.INFO, "getGeoExtent");

        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.log(Priority.DEBUG, "Polygon Content is: \n" + strCont);
                    }
                output = gmlHandler.getBoundingPolygonRepresentation(gmlElements);
//                    }
//                @todo: this should be changed so as to return polygons even in cases where single points are provided. 
//                else
//                    for (int i = 0; i < polygons.getLength(); i++) {
//                    strCont = serializeNode(((Element) polygons.item(i)).getFirstChild());
//                    _logger.log(Priority.INFO, "Polygon Content is: \n" + strCont);
//                    output = gmlHandler.getGeometryRepresentation(((Element) polygons.item(i)).getElementsByTagNameNS(GML32_NAMESPACE, "*").item(0));
//                }

        } 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);

            InputStream strInStream = new ByteArrayInputStream(strCont.getBytes());

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


    }
}
