/*
 * Copyright 2013 gathanas.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package eu.dnetlib.espas.util;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.Priority;
import org.apache.xml.serialize.DOMSerializer;
import org.apache.xml.serialize.XMLSerializer;
import org.geotoolkit.factory.AuthorityFactoryFinder;
import org.geotoolkit.factory.Hints;
import org.geotoolkit.referencing.CRS;
import org.geotoolkit.referencing.factory.web.WebCRSFactory;
import org.geotoolkit.referencing.factory.wkt.DirectPostgisFactory;
import org.geotoolkit.xml.MarshallerPool;
import org.opengis.referencing.crs.CRSAuthorityFactory;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.w3c.dom.Element;
import org.w3c.dom.Node;



/**
 *
 * @author gathanas
 */
public class MetadataHandler {
   
    private static final Logger _logger = Logger.getLogger(MetadataHandler.class);
    
    protected static final String GMD_NAMESPACE = "http://www.isotc211.org/2005/gmd";
    protected static final String GCO_NAMESPACE = "http://www.isotc211.org/2005/gco";
    protected static final String GML32_NAMESPACE = "http://www.opengis.net/gml/3.2";
    protected static final String XLINK1999_NAMESPACE = "http://www.w3.org/1999/xlink";

    protected static final boolean USE_EWKT = false;
    /** This connection to an associated postgis db instance is used for retrieving the Coordinate System reference codes
     * defined in the spatial_ref_sys table. If codes defined in this table are expected to be used for the instantiation of
     * coordinate systems and related transformations then this connection should be initialized.
     */
    //          initializing the jdbc connection to the postgis db instnace
    protected static String connectionURL = "jdbc:postgresql://dl121.madgik.di.uoa.gr/dnet_espas_v2";
    protected static String userName="dnet";
    protected static String passwrd="dnetPwd";
    protected static MarshallerPool metadataMarshPool;
    protected static Connection postgisDBConnection;
    private static Map<String, CRSMapping> supportedCRSMappings;

    protected static CRSAuthorityFactory authorityFactory;

    private static boolean inited=false;
    
    /** Statically initialize the MetadataHandler with a set of default properties.
     */
    static {
        try {
            Class.forName("org.postgresql.Driver");
            initializeDBConnection(null,null,null);
        } catch (ClassNotFoundException ex) {
            _logger.log(Priority.ERROR, "Postgress driver is not available", ex);
        } catch (SQLException ex) {
            _logger.log(Priority.ERROR, "Could not establish a connection to postgis db instnace. Connection url is:" + connectionURL, ex);
        }

    }

    private static void initializeDBConnection(String dbURL, String user, String pass) throws SQLException{
        if (dbURL!=null)
            connectionURL=dbURL;
        if(user!=null)
            userName= user;
        if(pass!=null)
            passwrd = pass;
            
        postgisDBConnection = DriverManager.getConnection(connectionURL, userName, passwrd);
        loadCRSToSRIDMappings();
    }
    
    /** Used for initializing the MetadataHandler with a set of specified properties.
     */
   public static void initHandler(String connectionURL,String dbName, String userName, String passWord) {
      try {
         if (!inited || postgisDBConnection == null || postgisDBConnection.isClosed()) {
             initializeDBConnection(connectionURL+"/"+dbName, userName, passWord);
             inited=true;
         }
      }
      catch (SQLException ex) {
         _logger.log(Priority.ERROR, "Could not establish a connection to postgis db instnace. Connection url is:" + connectionURL, ex);
      }

   }
    
    //////////////////////////////////////////////////////////////////////////
    /** Loads CRS mappings from the postgis database. Information is extracted from the values of the crs_spatial_ref_sys table
     */
    protected static void loadCRSToSRIDMappings() throws SQLException {
        initCRSAuthorityFactory();
        String retrieveCRSMappingsQuery = "select crs, srid,  sridName, inverse_x_y_order, z_scale_factor from crs_spatial_ref_sys";

        ResultSet queryResults = postgisDBConnection.createStatement().executeQuery(retrieveCRSMappingsQuery);
        if (queryResults != null) {
            supportedCRSMappings = new HashMap<String, CRSMapping>();
            while (queryResults.next()) {
                CRSMapping mapping = new CRSMapping(queryResults.getString("crs"), queryResults.getInt("srid"), queryResults.getString("sridname"), queryResults.getBoolean("inverse_x_y_order"), queryResults.getDouble("z_scale_factor"));
                supportedCRSMappings.put(mapping.getEspasCRS(), mapping);
            }
            queryResults.close();
        }
        _logger.debug("CRS to ESPAS SRS mappings have been initialized");
    }

    /** Serializes the provided node elements to string representations that are latter on used for logging purposes.
     */
    protected static String serializeNode(Node extentContent) throws IOException {
        XMLSerializer xmlSerializer = new XMLSerializer();
        ByteArrayOutputStream btArrStream = new ByteArrayOutputStream();
        xmlSerializer.setOutputByteStream(btArrStream);
        xmlSerializer.setNamespaces(true);
        DOMSerializer dmSerializer = xmlSerializer.asDOMSerializer();
        _logger.info("DM serializer is :"+(dmSerializer==null)+" extent is :"+(extentContent==null));
        String strCont="";
        if(dmSerializer!=null && extentContent!=null){
            dmSerializer.serialize((Element) extentContent);
            strCont = new String(btArrStream.toByteArray());
        }
        return strCont;
    }

    /** Internal method used for retrieving an appropriate CRS out of a WTK factory that is
     */
    static CoordinateReferenceSystem getCRSForSRSName(String srsCode) {
        try{
         initCRSAuthorityFactory();

         _logger.info("SRS Code is :"+srsCode+" is in the list :"+supportedCRSMappings.containsKey(srsCode));
        CoordinateReferenceSystem result = null;
        if (srsCode.equals("http://ontology.espas-fp7.eu/crs/GEI") || srsCode.equals("http://ontology.espas-fp7.eu/crs/J2000spherical"))
            result = authorityFactory.createCoordinateReferenceSystem("EPSG:4326");
        else if (supportedCRSMappings.containsKey(srsCode))
            result = authorityFactory.createCoordinateReferenceSystem(supportedCRSMappings.get(srsCode).getSridCode());
        return result;
    }
        catch(Exception ex){
                _logger.error("Retrieval of Coordinate reference system failed! \n", ex);
                return null;
        }
    }

   public static void initCRSAuthorityFactory() {
      //        Initialize either the Postgis factory if the connection is online or otherwise the WebFactory.
      Hints hintsforAuthorityFactory = new Hints();
      if (postgisDBConnection != null)
         try {
             if(postgisDBConnection.isClosed())
                 initializeDBConnection(null,null,null);
            _logger.info("Initilizing PostgisFactory for CRS codes ");
            authorityFactory = new DirectPostgisFactory(hintsforAuthorityFactory, postgisDBConnection);
            _logger.info("PostgisFactory for CRS codes has been initialized");
         } catch (Exception ex) {
            _logger.info("Postgis CRSFactory could not be initialized", ex);
            authorityFactory = new WebCRSFactory(hintsforAuthorityFactory);
         }
      else
         authorityFactory = new WebCRSFactory(hintsforAuthorityFactory);
      
      AuthorityFactoryFinder.addAuthorityFactory(authorityFactory);
   }
    
   
   public static String getComputationKind(String computationType){
      String result = "Software";
      try{
         if(computationType.equalsIgnoreCase("http://ontology.espas-fp7.eu/computationType/Model"))
            result = "Model";
         else
            if(computationType.equalsIgnoreCase("http://ontology.espas-fp7.eu/computationType/Software"))
               result="Software";
         else
            if (postgisDBConnection != null){
               Statement queryStatement = postgisDBConnection.createStatement();
               String query="with recursive computationtype_tree(child, parent) as (" +
                            " select ch.id, pr.parent from computationtype as ch, computationtype as pr" +
                            " where ch.parent = pr.id and pr.parent!='NULL')" +
                            " select parent from computationtype_tree where child='"+computationType+"'";
               ResultSet rs = queryStatement.executeQuery(query);
               if(rs.next()){
                  result = rs.getString(1).replaceAll("http://ontology.espas-fp7.eu/computationType/", "");
                  result = (result!="-1")?result:"Software";
                  }
               }
      }
      catch(Exception exc){
         _logger.info(null, exc);
      }
      finally{
         return result;
              }
   }
   
   
   public static CRSMapping getSupportedCRSMapping(String espasCRSName){
       try {
          loadCRSToSRIDMappings();
       }
       catch (SQLException ex) {
          _logger.debug(null,ex);
       }
       
       return supportedCRSMappings.get(espasCRSName);
      
   }
}






/** Class used for holding mappings of ESPAS CRS to standard ones used in postgis.
 */
class CRSMapping {
    private String espasCRS;
    private String sridCode;
    private int srid;
    private boolean invertedXY;
    private double zScaleFactor;

    public CRSMapping(String espasCRS, int srid, String sridCode, boolean invertedXY, double zScaleFactor) {
        this.espasCRS = espasCRS;
        this.srid = srid;
        this.sridCode = sridCode;
        this.invertedXY = invertedXY;
        this.zScaleFactor = zScaleFactor;
    }

    public String getEspasCRS() {
        return espasCRS;
    }

    public int getSrid() {
        return srid;
    }
    
    public String getSridCode() {
        return sridCode;
    }

    public boolean isInvertedXY() {
        return invertedXY;
    }

    public double getzScaleFactor() {
        return zScaleFactor;
    }
}