package org.aksw.sparqlify.web;

import java.io.PrintStream;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.util.Iterator;
import java.util.Map;

import javax.sql.DataSource;

import org.aksw.commons.io.util.StdIo;
import org.aksw.commons.sql.codec.api.SqlCodec;
import org.aksw.commons.sql.codec.util.SqlCodecUtils;
import org.aksw.commons.util.MapReader;
import org.aksw.jena_sparql_api.core.GraphQueryExecutionFactory;
import org.aksw.jena_sparql_api.core.utils.QueryExecutionUtils;
import org.aksw.jena_sparql_api.limit.QueryExecutionFactoryLimit;
import org.aksw.jena_sparql_api.model.QueryExecutionFactoryModel;
import org.aksw.jena_sparql_api.views.CandidateViewSelector;
import org.aksw.jenax.arq.connection.core.QueryExecutionFactory;
import org.aksw.jenax.web.server.boot.FactoryBeanSparqlServer;
import org.aksw.sparqlify.backend.postgres.DatatypeToStringPostgres;
import org.aksw.sparqlify.config.syntax.Config;
import org.aksw.sparqlify.config.v0_2.bridge.BasicTableInfoProvider;
import org.aksw.sparqlify.config.v0_2.bridge.BasicTableProviderJdbc;
import org.aksw.sparqlify.config.v0_2.bridge.ConfiguratorCandidateSelector;
import org.aksw.sparqlify.config.v0_2.bridge.SchemaProvider;
import org.aksw.sparqlify.config.v0_2.bridge.SchemaProviderImpl;
import org.aksw.sparqlify.config.v0_2.bridge.SyntaxBridge;
import org.aksw.sparqlify.core.algorithms.CandidateViewSelectorSparqlify;
import org.aksw.sparqlify.core.algorithms.DatatypeToString;
import org.aksw.sparqlify.core.algorithms.ViewDefinitionNormalizerImpl;
import org.aksw.sparqlify.core.builder.FluentSparqlifyFactory;
import org.aksw.sparqlify.core.cast.TypeSystem;
import org.aksw.sparqlify.core.domain.input.ViewDefinition;
import org.aksw.sparqlify.core.interfaces.MappingOps;
import org.aksw.sparqlify.core.sparql.QueryEx;
import org.aksw.sparqlify.core.sparql.QueryExecutionFactoryEx;
import org.aksw.sparqlify.core.sparql.QueryExecutionFactoryExWrapper;
import org.aksw.sparqlify.core.sparql.QueryFactoryEx;
import org.aksw.sparqlify.util.ExprRewriteSystem;
import org.aksw.sparqlify.util.SparqlifyCoreInit;
import org.aksw.sparqlify.util.SparqlifyUtils;
import org.aksw.sparqlify.util.SqlBackendConfig;
import org.aksw.sparqlify.util.SqlBackendRegistry;
import org.aksw.sparqlify.validation.LoggerCount;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.jena.graph.Graph;
import org.apache.jena.graph.Triple;
import org.apache.jena.query.QueryExecution;
import org.apache.jena.query.ResultSet;
import org.apache.jena.query.ResultSetFormatter;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
import org.apache.jena.riot.writer.NQuadsWriter;
import org.apache.jena.riot.writer.NTriplesWriter;
import org.apache.jena.sparql.core.Quad;
import org.apache.jena.sparql.exec.QueryExec;
import org.apache.jena.sparql.resultset.ResultsFormat;
import org.apache.jena.sparql.util.QueryExecUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class Main {

    private static final Logger logger = LoggerFactory
            .getLogger(Main.class);
    private static final Options cliOptions = new Options();

    /**
     * @param args
     *            the command line arguments
     */
    public static void main(String[] args) throws Exception {
//        JenaSystem.init();
//        InitJenaCore.init();
//        ARQ.init();
//        Class.forName("org.postgresql.Driver");


        LoggerCount loggerCount = new LoggerCount(logger);


        CommandLineParser cliParser = new GnuParser();

        cliOptions.addOption("P", "port", true, "Server port");
        cliOptions.addOption("C", "context", true, "Context e.g. /sparqlify");
        cliOptions.addOption("B", "backlog", true,
                "Maximum number of connections");

        SparqlifyCliHelper.addDatabaseOptions(cliOptions);
//		cliOptions.addOption("t", "type", true,
//				"Database type (posgres, mysql,...)");
//		cliOptions.addOption("d", "database", true, "Database name");
//		cliOptions.addOption("u", "username", true, "");
//		cliOptions.addOption("W", "password", true, "");
//		cliOptions.addOption("h", "hostname", true, "");
//		cliOptions.addOption("c", "class", true, "JDBC driver class");
//		cliOptions.addOption("j", "jdbcurl", true, "JDBC URL");

        // Note: Q and D are exclusive. If either is given, no server is started
        cliOptions.addOption("Q", "query", true, "");
        cliOptions.addOption("D", "dump", false, "");

        Option mappingSourceOption = new Option("m", "mapping", true, "Sparqlify mapping file (can be specified multiple times)");
        mappingSourceOption.setArgs(Option.UNLIMITED_VALUES);
        cliOptions.addOption(mappingSourceOption);

        cliOptions.addOption("t", "timeout", true, "Maximum query execution timeout in seconds");
        cliOptions.addOption("n", "resultsetsize", true, "Maximum result set size");

        cliOptions.addOption("o", "format", true, "Output format; currently only applies to dump (-D). Values: ntriples, nquads");

        cliOptions.addOption("1", "sparql11", false, "use jena for sparql 11 (supports property paths but may be slow)");


        CommandLine commandLine = cliParser.parse(cliOptions, args);


        // Parsing of command line args
        String portStr = commandLine.getOptionValue("P", "7531");
        //String backLogStr = commandLine.getOptionValue("B", "100");
        //String contextStr = commandLine.getOptionValue("C", "/sparqlify");
        int port = Integer.parseInt(portStr);
        //int backLog = Integer.parseInt(backLogStr);

//		String hostName = commandLine.getOptionValue("h", "");
//		String dbName = commandLine.getOptionValue("d", "");
//		String userName = commandLine.getOptionValue("u", "");
//		String passWord = commandLine.getOptionValue("p", "");
//
//		String jdbcUrl = commandLine.getOptionValue("j", "");



        String outputFormat = commandLine.getOptionValue("o", "default");

        //String driverClassName = commandLine.getOptionValue("c", "");

        boolean isDump = commandLine.hasOption("D");
        String queryString = commandLine.getOptionValue("Q", "");

        boolean isQuery = !queryString.isEmpty();
        if(!isQuery) {
            queryString = null;
        }

        if(isDump) {

            if(isQuery) {
                loggerCount.error("Options D and Q are mutually exclusive");
            }

//			if(outputFormat.equals("nquads")) {
//				//queryString = "Construct { Graph ?g { ?s ?p ?o } } { Graph ?g { ?s ?p ?o } }";
//				queryString = "Select ?s ?p ?o ?g { Graph ?g { ?s ?p ?o } }";
//			} else {
//				//queryString = "Construct { ?s ?p ?o } { ?s ?p ?o }";
//				queryString = "Select ?s ?p ?o { ?s ?p ?o }";
//			}
        }


        String maxQueryExecutionTimeStr = commandLine.getOptionValue("t", null);
        Integer maxQueryExecutionTime = maxQueryExecutionTimeStr == null
                ? null
                : Integer.parseInt(maxQueryExecutionTimeStr);

        String maxResultSetSizeStr = commandLine.getOptionValue("n", null);
        Long maxResultSetSize = maxResultSetSizeStr == null
                ? null
                : Long.parseLong(maxResultSetSizeStr);


        Config config = SparqlifyCliHelper.parseSmlConfigs(commandLine, loggerCount);
        SparqlifyCliHelper.onErrorPrintHelpAndExit(cliOptions, loggerCount, -1);

        /*
         * Connection Pool
         */
        DataSource dataSource = SparqlifyCliHelper.parseDataSource(commandLine, loggerCount);
        SparqlifyCliHelper.onErrorPrintHelpAndExit(cliOptions, loggerCount, -1);


        SparqlifyCoreInit.initSparqlifyFunctions();



        //TypeSystem typeSystem = SparqlifyCoreInit.createDefaultDatatypeSystem();
        //TypeSystem datatypeSystem = SparqlifyUtils.createDefaultDatatypeSystem();

        ExprRewriteSystem ers = SparqlifyUtils.createDefaultExprRewriteSystem();
        TypeSystem typeSystem = ers.getTypeSystem();

        // typeAliases for the H2 datatype
        Map<String, String> typeAlias = MapReader.readFromResource("/type-map.h2.tsv");


        //SqlEscaper sqlEscaper = new SqlEscaperBacktick();

        Connection conn = dataSource.getConnection();

        DatabaseMetaData dbMeta = conn.getMetaData();
        String dbProductName = dbMeta.getDatabaseProductName();
        logger.info("Database product: " + dbProductName);

        SqlBackendRegistry backendRegistry = SqlBackendRegistry.get();
        //Map<String, SqlBackendConfig> map = backendRegistry.getMap();

        SqlBackendConfig backendConfig = backendRegistry.apply(dbProductName);
        if(backendConfig == null) {
            throw new RuntimeException("Could not find backend for " + dbProductName);
        }


        SqlCodec sqlEscaper = backendConfig.getSqlEscaper();
        DatatypeToString typeSerializer = backendConfig.getTypeSerializer();

        try {
            BasicTableInfoProvider basicTableInfoProvider = new BasicTableProviderJdbc(conn);
            SchemaProvider schemaProvider = new SchemaProviderImpl(
                    basicTableInfoProvider,
                    typeSystem,
                    typeAlias,
                    sqlEscaper);
            SyntaxBridge syntaxBridge = new SyntaxBridge(schemaProvider);

            //OpMappingRewriter opMappingRewriter = SparqlifyUtils.createDefaultOpMappingRewriter(typeSystem);
            //MappingOps mappingOps = SparqlifyUtils.createDefaultMappingOps(typeSystem);
            MappingOps mappingOps = SparqlifyUtils.createDefaultMappingOps(ers);
            //OpMappingRewriter opMappingRewriter = new OpMappingRewriterImpl(mappingOps);


            CandidateViewSelector<ViewDefinition> candidateViewSelector = new CandidateViewSelectorSparqlify(mappingOps, new ViewDefinitionNormalizerImpl());


            //RdfViewSystem system = new RdfViewSystem2();
            ConfiguratorCandidateSelector.configure(config, syntaxBridge, candidateViewSelector, loggerCount);
        }
        finally {
            conn.close();
        }

        logger.info("Errors: " + loggerCount.getErrorCount() + ", Warnings: " + loggerCount.getWarningCount());

        if(loggerCount.getErrorCount() > 0) {
            throw new RuntimeException("Encountered " + loggerCount.getErrorCount() + " errors that need to be fixed first.");
        }


//		SparqlSqlOpRewriter ssoRewriter = SparqlifyUtils.createSqlOpRewriter(candidateViewSelector, opMappingRewriter, typeSystem);
//
//		SqlExprSerializerSystem serializerSystem = SparqlifyUtils.createSerializerSystem();
//		SqlOpSerializer sqlOpSerializer = new SqlOpSerializerImpl(serializerSystem);
//
//
//		SparqlSqlStringRewriter rewriter = new SparqlSqlStringRewriterImpl(ssoRewriter, sqlOpSerializer);//SparqlifyUtils.createSparqlSqlStringRewriter(ssoRewriter);
//
//
//
//		//SparqlSqlStringRewriter rewriter = SparqlifyUtils.createTestRewriter(candidateViewSelector, opMappingRewriter, typeSystem);
//
//		//SparqlSqlRewriter rewriter = new SparqlSqlRewriterImpl(candidateViewSelector, opMappingRewriter, sqlOpSelectBlockCollector, sqlOpSerializer);
//
//
//		QueryExecutionFactory qefDefault = new QueryExecutionFactorySparqlifyDs(rewriter, dataSource);
//
//		if(maxQueryExecutionTime != null) {
//			qefDefault = QueryExecutionFactoryTimeout.decorate(qefDefault, maxQueryExecutionTime * 1000);
//		}
//
//		if(maxResultSetSize != null) {
//			qefDefault = QueryExecutionFactoryLimit.decorate(qefDefault, false, maxResultSetSize);
//		}
//
//
//		QueryExecutionFactory qefExplain = new QueryExecutionFactorySparqlifyExplain(dataSource, ssoRewriter, sqlOpSerializer);
//
//
//		QueryExecutionFactoryEx qef = new QueryExecutionFactoryExImpl(qefDefault, qefExplain);

        boolean useSparql11Wrapper = commandLine.hasOption("1");

        Long mrs = useSparql11Wrapper ? null : maxResultSetSize;



        //DatatypeToString typeSerializer = new DatatypeToStringPostgres();
        //QueryExecutionFactoryEx qef = SparqlifyUtils.createDefaultSparqlifyEngine(dataSource, config, typeSerializer, sqlEscaper, mrs, maxQueryExecutionTime);
        QueryExecutionFactoryEx qef = FluentSparqlifyFactory.newEngine()
                .setDataSource(dataSource)
                .setConfig(config)
                .setDatatypeToString(new DatatypeToStringPostgres())
                .setSqlEscaper(SqlCodecUtils.createSqlCodecDefault())
                .setMaxQueryExecutionTime(maxQueryExecutionTime)
                .setMaxResultSetSize(mrs)
                .create();

        if(useSparql11Wrapper) {
            Graph graph = new GraphQueryExecutionFactory(qef);
            Model model = ModelFactory.createModelForGraph(graph);
            QueryExecutionFactory tmp = new QueryExecutionFactoryModel(model);
            if(maxResultSetSize != null) {
                tmp = QueryExecutionFactoryLimit.decorate(tmp, true, maxResultSetSize);
            }

            qef = new QueryExecutionFactoryExWrapper(tmp);
        }

        if(isDump) {
            if(outputFormat.equals("nquads")) {
                Iterator<Quad> itQuad = QueryExecutionUtils.createIteratorDumpQuads(qef);
                NQuadsWriter.write(System.out, itQuad);

            }
            else {
                Iterator<Triple> itTriple = QueryExecutionUtils.createIteratorDumpTriples(qef);
                NTriplesWriter.write(System.out, itTriple);
            }

            return;
        }
        else if(queryString != null) {
            QueryEx queryEx = QueryFactoryEx.create(queryString);

            if(queryEx.isSelectType()) {
                QueryExecution qe = qef.createQueryExecution(queryEx);
                ResultSet rs = qe.execSelect();
                System.out.println(ResultSetFormatter.asText(rs));
            }
            else if(queryEx.isConstructType()) {
                QueryExecution qe = qef.createQueryExecution(queryString);
                QueryExecUtils.exec(null, QueryExec.adapt(qe), ResultsFormat.FMT_RDF_NT, new PrintStream(StdIo.openStdOutWithCloseShield()));

                // Iterator<Triple> it = qe.execConstructTriples();
                // SparqlFormatterUtils.writeText(System.out, it);
                //model.write(System.out, "N-TRIPLES");
            }
            else {
                throw new RuntimeException("Query type not supported: " + queryString);
            }


            return;
        }


        FactoryBeanSparqlServer.newInstance()
            .setSparqlServiceFactory(qef)
            .create();


        //sparqler = qef; //new QueryExecutionFactorySparqlify(system, conn);

        //QueryExecutionFactoryStreamingProvider provider = new QueryExecutionFactoryStreamingProvider(qef);
        //QueryExecutionFactoryStreamingProvider.qeFactory = qef;

        // server.stop();
    }


//    public static Server createSparqlEndpoint(QueryExecutionFactoryEx qef, int port) throws Exception {
//        HttpSparqlEndpoint.sparqler = qef;
//
//
//        ServletHolder sh = new ServletHolder(ServletContainer.class);
//
//
//        /*
//         * For 0.8 and later the "com.sun.ws.rest" namespace has been renamed to
//         * "com.sun.jersey". For 0.7 or early use the commented out code instead
//         */
//        // sh.setInitParameter("com.sun.ws.rest.config.property.resourceConfigClass",
//        // "com.sun.ws.rest.api.core.PackagesResourceConfig");
//        // sh.setInitParameter("com.sun.ws.rest.config.property.packages",
//        // "jetty");
//        //        jassaServlet.setInitParameter("jersey.config.server.provider.classnames", "org.aksw.jena_sparql_api.web.servlets.PathFindingApi org.aksw.facete2.web.api.ServletDataStore org.aksw.facete2.web.api.ServletExportSparql org.aksw.facete2.web.api.ServletSparqlSpringBatchStatus");
//        //
////        sh.setInitParameter(
////                "com.sun.jersey.config.property.resourceConfigClass",
////                "com.sun.jersey.api.core.PackagesResourceConfig");
//        sh.setInitParameter("jersey.config.server.provider.packages",
//                "org.aksw.sparqlify.web");
//
//        Server server = new Server(port);
//        ServletContextHandler context = new ServletContextHandler(server, "/", ServletContextHandler.SESSIONS);
//
//        context.getServletContext().setAttribute("queryExecutionFactory", qef);
//        context.addServlet(sh, "/*");
//
//        return server;
//    }
}
