package org.aksw.sparqlify.admin.web.main;

import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.annotation.Resource;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

import org.aksw.commons.sql.codec.api.SqlCodec;
import org.aksw.commons.util.slf4j.LoggerCount;
import org.aksw.jenax.arq.connection.core.QueryExecutionFactory;
import org.aksw.service_framework.core.ServiceLauncherRdb2Rdf;
import org.aksw.service_framework.core.SparqlService;
import org.aksw.service_framework.jpa.core.ServiceRepositoryJpaImpl;
import org.aksw.sparqlify.admin.model.Rdb2RdfConfig;
import org.aksw.sparqlify.admin.model.Rdb2RdfExecution;
import org.aksw.sparqlify.admin.web.api.ServiceEventListenerRegister;
import org.aksw.sparqlify.admin.web.api.ServiceManager;
import org.aksw.sparqlify.admin.web.api.ServiceManagerImpl;
import org.aksw.sparqlify.backend.postgres.DatatypeToStringPostgres;
import org.aksw.sparqlify.config.syntax.Config;
import org.aksw.sparqlify.core.algorithms.CandidateViewSelectorSparqlify;
import org.aksw.sparqlify.core.algorithms.DatatypeToString;
import org.aksw.sparqlify.core.interfaces.SparqlSqlOpRewriterImpl;
import org.aksw.sparqlify.core.interfaces.SqlTranslator;
import org.aksw.sparqlify.core.sql.common.serialization.SqlEscaper;
import org.aksw.sparqlify.core.sql.common.serialization.SqlEscaperDoubleQuote;
import org.aksw.sparqlify.inverse.SparqlSqlInverseMapper;
import org.aksw.sparqlify.inverse.SparqlSqlInverseMapperImpl;
import org.aksw.sparqlify.jpa.EntityInverseMapper;
import org.aksw.sparqlify.jpa.EntityInverseMapperImplHibernate;
import org.aksw.sparqlify.util.SparqlifyCoreInit;
import org.aksw.sparqlify.util.SparqlifyUtils;
import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.hibernate5.HibernateExceptionTranslator;
import org.springframework.orm.jpa.JpaDialect;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@ComponentScan("org.aksw.sparqlify.admin.web")
@EnableTransactionManagement
@PropertySource("classpath:config/jdbc/jdbc.properties")
public class AppConfig
{
    private static final Logger logger = LoggerFactory.getLogger(AppConfig.class);

    private static final String JDBC_DRIVER = "jdbc.driver";
    private static final String JDBC_PASSWORD = "jdbc.password";
    private static final String JDBC_URL = "jdbc.url";
    private static final String JDBC_USERNAME = "jdbc.username";

    private static final String HIBERNATE_DIALECT = "hibernate.dialect";
    private static final String HIBERNATE_SHOW_SQL = "hibernate.showSql";
    private static final String HIBERNATE_HBM2DDL_AUTO = "hibernate.hbm2ddl.auto";
    //private static final String PROPERTY_NAME_ENTITYMANAGER_PACKAGES_TO_SCAN = "entitymanager.packages.to.scan";

    @Resource
    private Environment env;

    /**
     * When starting the server from the command line,
     * this attribute can be set to override any other means of creating a data source
     */
    public static DataSource cliDataSource = null;



    //@Bean
//	public DataSource dataSource2() {
//		JndiObjectFactoryBean jndiObjectFactory = new JndiObjectFactoryBean();
//
//	}

    @Bean
    public DataSource dataSource() throws IllegalArgumentException, ClassNotFoundException {

        // TODO Somehow allow loading drivers dynamically
        Class.forName("org.postgresql.Driver");

        DataSource result = null;

        try {
            String jndiName = "java:comp/env/jdbc/sparqlifyDs";
            Context ctx = new InitialContext();
            result = (DataSource)ctx.lookup(jndiName);
        } catch (NamingException e) {
            logger.info("Exception on retrieving initial JNDI context - trying a different method");
        }

        if(result != null) {
            return result;
        }


//		JndiObjectFactoryBean jndiFactory = new JndiObjectFactoryBean();
//		jndiFactory.setResourceRef(true);
//		jndiFactory.setJndiName(jndiName);
//		//jndiFactory.setJndiName("jdbc/sparqlifyDs");
//		jndiFactory.setLookupOnStartup(true);
//		jndiFactory.afterPropertiesSet();
//
//
//		result = (DataSource)jndiFactory.getObject();



//		URL location = AppConfig.class.getProtectionDomain().getCodeSource().getLocation();
//        System.out.println(location.getFile());

        DriverManagerDataSource dataSource = new DriverManagerDataSource();

        dataSource.setDriverClassName(env.getRequiredProperty(JDBC_DRIVER));
        dataSource.setUrl(env.getRequiredProperty(JDBC_URL));
        dataSource.setUsername(env.getRequiredProperty(JDBC_USERNAME));
        dataSource.setPassword(env.getRequiredProperty(JDBC_PASSWORD));

        return dataSource;
    }

//	@Bean
//
//	public LocalSessionFactoryBean sessionFactory(DataSource dataSource) {
//		LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean();
//		sessionFactoryBean.setDataSource(dataSource);
//		sessionFactoryBean
//				.setPackagesToScan(env
//						.getRequiredProperty(PROPERTY_NAME_ENTITYMANAGER_PACKAGES_TO_SCAN));
//		sessionFactoryBean.setHibernateProperties(hibProperties());
//		return sessionFactoryBean;
//	}
//
    private Properties getHibernateProperties() {
        Properties properties = new Properties();
        properties.put(HIBERNATE_DIALECT, env.getRequiredProperty(HIBERNATE_DIALECT));
        properties.put(HIBERNATE_SHOW_SQL, env.getRequiredProperty(HIBERNATE_SHOW_SQL));
        properties.put(HIBERNATE_HBM2DDL_AUTO, env.getRequiredProperty(HIBERNATE_HBM2DDL_AUTO));

        properties.put("hibernate.current_session_context_class", "thread");

        return properties;
    }

//	@Bean
//
//	public HibernateTransactionManager transactionManager(LocalSessionFactoryBean sessionFactory) {
//
//		HibernateTransactionManager transactionManager = new HibernateTransactionManager();
//		transactionManager.setSessionFactory(sessionFactory.getObject());
//
//		// Force creation of the schema
//		Session session = transactionManager.getSessionFactory().openSession();
//		Transaction tx = session.beginTransaction();
//		tx.commit();
//
//		session.close();
//
//
//		return transactionManager;
//	}

//	@Bean
//	public UrlBasedViewResolver setupViewResolver() {
//		UrlBasedViewResolver resolver = new UrlBasedViewResolver();
//		resolver.setPrefix("/WEB-INF/pages/");
//		resolver.setSuffix(".jsp");
//		resolver.setViewClass(JstlView.class);
//		return resolver;
//	}

    @Bean
    public HibernateExceptionTranslator hibernateExceptionTranslator() {
        return new HibernateExceptionTranslator();
    }

//    @Bean
//    public LocalSessionFactoryBean sessionFactory(DataSource dataSource) {
//    	LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
//    	//sessionFactory.setJpaVendorAdapter(vendorAdapter);
//    	sessionFactory.setHibernateProperties(getHibernateProperties());
//    	sessionFactory.setPackagesToScan("org.aksw.service_framework.jpa.model", "org.aksw.sparqlify.admin.model");
//    	sessionFactory.setDataSource(dataSource);
//
//    	return sessionFactory;
//    }
//
//
//	@Bean
//	public HibernateTransactionManager transactionManager(SessionFactory sessionFactory) {
//		HibernateTransactionManager txManager = new HibernateTransactionManager();
//		txManager.setSessionFactory(sessionFactory);
//		return txManager;
//	}

    @Bean
    public EntityManagerFactory entityManagerFactory(DataSource dataSource) {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(true);
        vendorAdapter.setShowSql(false);
        //vendorAdapter.setDatabasePlatform("org.hibernate.dialect.MySQL5InnoDBDialect");
        vendorAdapter.setDatabasePlatform("org.hibernate.dialect.PostgreSQLDialect");
        vendorAdapter.setDatabase(Database.POSTGRESQL);

        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan("org.aksw.service_framework.jpa.model", "org.aksw.sparqlify.admin.model");
        factory.setDataSource(dataSource);

        Properties properties = getHibernateProperties();
//        Properties properties = new Properties();
//        properties.setProperty("hibernate.cache.use_second_level_cache", "true");
//        properties.setProperty("hibernate.cache.region.factory_class", "org.hibernate.cache.ehcache.EhCacheRegionFactory");
//        properties.setProperty("hibernate.cache.use_query_cache", "true");
//        properties.setProperty("hibernate.generate_statistics", "true");

        factory.setJpaProperties(properties);

        factory.afterPropertiesSet();

        return factory.getObject();
    }

    @Bean
    public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager txManager = new JpaTransactionManager();
        JpaDialect jpaDialect = new HibernateJpaDialect();
        txManager.setEntityManagerFactory(entityManagerFactory);
        txManager.setJpaDialect(jpaDialect);
        return txManager;
    }



    /**
     *
     * Note: We must ensure that the schema exists prior to mapping it
     *
     * @return
     * @throws Exception
     */
    @Bean
    @DependsOn("entityManagerFactory")
    public QueryExecutionFactory managerApiQef(DataSource dataSource, SqlCodec sqlEscaper)
        throws Exception
    {
        LoggerCount loggerCount = new LoggerCount(logger);
        InputStream in = this.getClass().getResourceAsStream("/sparqlify-web-manager.sml");
        Config config = SparqlifyUtils.readConfig(in, loggerCount);

        if(loggerCount.getErrorCount() != 0 || loggerCount.getWarningCount() != 0) {
            throw new RuntimeException("Errors reading mapping encountered");
        }

        DatatypeToString typeSerializer = new DatatypeToStringPostgres();

        QueryExecutionFactory result = SparqlifyUtils.createDefaultSparqlifyEngine(
                dataSource, config, typeSerializer, sqlEscaper, 1000l, 60, SparqlifyCoreInit.loadSqlFunctionDefinitions("functions.xml"));
        return result;
    }

    @Bean
    public SqlEscaper sqlEscaper() {
        return new SqlEscaperDoubleQuote();
    }

    @Bean
    public SparqlSqlInverseMapper sparqlSqlInverseMapper(CandidateViewSelectorSparqlify candidateViewSelector, SqlTranslator sqlTranslator) {
        SparqlSqlInverseMapper result = new SparqlSqlInverseMapperImpl(candidateViewSelector, sqlTranslator);

        return result;
    }

    @Bean
    public EntityInverseMapper entityInverseMapper(SessionFactory sessionFactory, SparqlSqlInverseMapper inverseMapper) {
        EntityInverseMapperImplHibernate result = EntityInverseMapperImplHibernate.create(inverseMapper, sessionFactory);
        return result;
    }


    @Bean
    public ServiceRepositoryJpaImpl<Rdb2RdfConfig, Rdb2RdfExecution, SparqlService> sparqlServiceRepo(JpaTransactionManager txManager) {

        EntityManagerFactory emf = txManager.getEntityManagerFactory();

        ServiceRepositoryJpaImpl<Rdb2RdfConfig, Rdb2RdfExecution, SparqlService> serviceRepo =
            ServiceRepositoryJpaImpl.create(
                    emf,
                    Rdb2RdfConfig.class,
                    Rdb2RdfExecution.class,
                    new ServiceLauncherRdb2Rdf()
                    );

        return serviceRepo;
    }


    @Bean
    public Map<String, SparqlService> sparqlServiceMap(ServiceRepositoryJpaImpl<Rdb2RdfConfig, Rdb2RdfExecution, SparqlService> serviceRepo) {
        Map<String, SparqlService> result = Collections.synchronizedMap(new HashMap<String, SparqlService>());

        ServiceEventListenerRegister listener = new ServiceEventListenerRegister(result);
        serviceRepo.getServiceEventListeners().add(listener);

        serviceRepo.startAll();

        return result;
    }


//	@Bean
//	public Map<String, QueryExecutionFactory> sparqlServiceMap(@Resource(name="tmpSparqlServiceMap") Map<String, SparqlService> tmpSparqlServiceMap) {
//	    Map<String, QueryExecutionFactory> result = Maps.transformValues(tmpSparqlServiceMap, new Function<SparqlService, QueryExecutionFactory>() {
//            @Override
//            public QueryExecutionFactory apply(SparqlService sparqlService) {
//                return sparqlService.getSparqlService();
//            }
//	    });
//
//	    return result;
//	}
//
//    @Bean
//    public Map<String, PrefixMapping> sparqlNamespaceMap(Map<String, SparqlService> sparqlServiceMap) {
//        Map<String, PrefixMapping> result = Maps.transformValues(sparqlServiceMap, new Function<SparqlService, PrefixMapping>() {
//            @Override
//            public PrefixMapping apply(SparqlService sparqlService) {
//                PrefixMapping tmp = null;
//
//                Object c = sparqlService.getConfig();
//                if(c instanceof Config) {
//                    Config config = (Config)c;
//                    tmp = config.getPrefixMapping();
//                }
//
//                return tmp;
//            }
//        });
//
//        return result;
//    }
//

    @Bean
    public ServiceManager sparqlServiceManager(ServiceRepositoryJpaImpl<Rdb2RdfConfig, Rdb2RdfExecution, SparqlService> serviceRepo, EntityInverseMapper entityInverseMapper) {
        ServiceManager serviceManager = ServiceManagerImpl.create(serviceRepo, entityInverseMapper);

        return serviceManager;
    }


    @Bean
    public SessionFactory sessionFactory(JpaTransactionManager txManager) {
        EntityManagerFactory emf = txManager.getEntityManagerFactory();
        //SessionFactory result = ((HibernateEntityManagerFactory)emf).getSessionFactory();
        SessionFactory result = emf.unwrap(SessionFactory.class);
        return result;
    }


    // TODO Possibly replace this ugly unwrapping by creating the Sparqlify Query Execution
    // in spring bean style

    @Bean
    public SparqlSqlOpRewriterImpl sparqlSqlOpRewriter(QueryExecutionFactory qef) {
        SparqlSqlOpRewriterImpl result = SparqlifyUtils.unwrapOpRewriter(qef);
        return result;
    }

    @Bean
    public SqlTranslator sqlTranslator(SparqlSqlOpRewriterImpl opRewriter) {
        SqlTranslator result = SparqlifyUtils.unwrapSqlTransformer(opRewriter);
        return result;
    }

    @Bean
    public CandidateViewSelectorSparqlify candidateViewSelector(SparqlSqlOpRewriterImpl opRewriter) {
        CandidateViewSelectorSparqlify result = SparqlifyUtils.unwrapCandidateViewSelector(opRewriter);
        return result;
    }


}


