/*
 * 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 javax.sql.DataSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
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);
    
    public static final String GMD_NAMESPACE = "http://www.isotc211.org/2005/gmd";
    public static final String GCO_NAMESPACE = "http://www.isotc211.org/2005/gco";
    public static final String GML32_NAMESPACE = "http://www.opengis.net/gml/3.2";
    public 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://localhost/dnet_espas";
    protected static String userName="dnet";
    protected static String passwrd="dnetPwd";
    
    private static DataSource postgisDataSource=null;
    
    protected static MarshallerPool metadataMarshPool;
    protected static Connection postgisDBConnection;
    private static Map<String, CRSMapping> supportedCRSMappings;

    protected static CRSAuthorityFactory authorityFactory;
    protected static DocumentBuilder xmlParser=null;

    private static boolean inited=false;
    
    /** Statically initialize the MetadataHandler with a set of default properties.
     */
    static {
        try {
            Class.forName("org.postgresql.Driver");
        } catch (ClassNotFoundException ex) {
            _logger.log(Priority.ERROR, "Postgress driver is not available", ex);
        }
    }


   public static void initHandler(DataSource source) {
         if (!inited)
            try {
               DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
               docFactory.setNamespaceAware(true);
               xmlParser = docFactory.newDocumentBuilder();
            } catch (ParserConfigurationException ex) {
               _logger.error("Failed to initialize XML parser !", ex);
            }
        postgisDataSource = source;
      try {
        if (!inited || postgisDBConnection == null || postgisDBConnection.isClosed()) {
             initializeDBConnection();
         }
        inited=true;
      }
      catch (SQLException ex) {
         _logger.log(Priority.ERROR, "Could not establish a connection to postgis db instnace. ", ex);
      }

   }
    /** Used for initializing the MetadataHandler with a set of specified properties.
     */
   public static void initHandler(String hostConnectionURL,String dbName, String user, String pass) {
         if (!inited)
            try {
               DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
               docFactory.setNamespaceAware(true);
               xmlParser = docFactory.newDocumentBuilder();
            } catch (ParserConfigurationException ex) {
               _logger.error("Failed to initialize XML parser !", ex);
            }
         
      try {
         
         connectionURL = hostConnectionURL+"/"+dbName;
         userName = user;
         passwrd= pass;
         
        if (!inited || postgisDBConnection == null || postgisDBConnection.isClosed()) {
             initializeDBConnection();
         }
        inited=true;
      }
      catch (SQLException ex) {
         _logger.log(Priority.ERROR, "Could not establish a connection to postgis db instnace. Connection url is:" + hostConnectionURL, ex);
      }

   }
    
    /** Internal method used for retrieving an appropriate CRS out of a WTK factory that is
     */
   public 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 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 id, parent from computationtype where id ='"+computationType+"' "+
                              "UNION " +
                              "select ch.child, pr.parent from computationtype_tree as ch, computationtype as pr where ch.parent = pr.id and pr.parent is not NULL) " +
                              "select parent " +
                              "from computationtype_tree " +
                              "where parent like '%Software' or parent like '%Model'";
               _logger.debug("About to execute computation type query :"+query );
               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);
      
   }

       //////////////////////////////////////////////////////////////////////////

   protected static void initCRSAuthorityFactory() {
      //        Initialize either the Postgis factory if the connection is online or otherwise the WebFactory.
      Hints hintsforAuthorityFactory = new Hints();
      if(authorityFactory==null ){
        if (postgisDBConnection != null){
         try {
             if(postgisDBConnection.isClosed())
                 initializeDBConnection();
             
            _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);
   }
   }
    
   
    /** 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 null :"+(dmSerializer==null)+", extent is null :"+(extentContent==null));
        String strCont="";
        if(dmSerializer!=null && extentContent!=null){
            dmSerializer.serialize((Element) extentContent);
            strCont = new String(btArrStream.toByteArray());
        }
        return strCont;
    }

       /** Loads CRS mappings from the postgis database. Information is extracted from the values of the crs_spatial_ref_sys table
     */
   private static void loadCRSToSRIDMappings() throws SQLException {
      String retrieveCRSMappingsQuery = "select crs, srid,  sridName, inverse_x_y_order, z_scale_factor from crs_spatial_ref_sys";
      Statement stmnt = null;
      ResultSet queryResults = null;
      try {
         stmnt = postgisDBConnection.createStatement();
         queryResults = stmnt.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);
            }
            _logger.debug("CRS to ESPAS SRS mappings have been initialized");
         }
      }

      finally {
         if (queryResults != null)
            queryResults.close();
         if (stmnt != null)
            stmnt.close();
      }

   }

   private static void initializeDBConnection() throws SQLException{
        if(postgisDataSource!=null)
           postgisDBConnection= postgisDataSource.getConnection();
        else
            postgisDBConnection = DriverManager.getConnection(connectionURL, userName, passwrd);

        initCRSAuthorityFactory();
        loadCRSToSRIDMappings();
    }

}






