package leigh.mecha.olap.xmla;


import leigh.mecha.cred.CredentialConnection;
import leigh.mecha.http.client.HttpClientRig;
import leigh.mecha.http.client.HttpUtil;
import leigh.mecha.log.MechaLogger;
import leigh.mecha.log.MechaLoggerFactory;
import leigh.mecha.util.IOUtil;
import leigh.mecha.util.Translator;
import org.apache.http.auth.AuthenticationException;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.CloseableHttpClient;

import javax.xml.stream.XMLStreamException;
import java.io.*;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;

/**
 * Simple implementation of a XMLA driver. This is a streaming implementation and is suitable for large result
 * sets where the caller does not want the entire set buffered into RAM.
 *
 * @author Alex Leigh
 * @since mk12 mod1 (RETROGRADE)
 */
public class XmlaDriver implements AutoCloseable {
    private static final MechaLogger logger = MechaLoggerFactory.getLogger(XmlaDriver.class);
    private final static String XMLA = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"><SOAP-ENV:Header/><SOAP-ENV:Body><Execute xmlns=\"urn:schemas-microsoft-com:xml-analysis\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"><Command><Statement>%s</Statement></Command><Properties><PropertyList><Format>Multidimensional</Format><Catalog>%s</Catalog><AxisFormat>TupleFormat</AxisFormat></PropertyList></Properties></Execute></SOAP-ENV:Body></SOAP-ENV:Envelope>";
    private final static String XMLA_TABULAR = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"><SOAP-ENV:Header/><SOAP-ENV:Body><Execute xmlns=\"urn:schemas-microsoft-com:xml-analysis\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"><Command><Statement>%s</Statement></Command><Properties><PropertyList><Format>Tabular</Format><Catalog>%s</Catalog><AxisFormat>TupleFormat</AxisFormat></PropertyList></Properties></Execute></SOAP-ENV:Body></SOAP-ENV:Envelope>";
    private static final String EXECUTE_ACTION = "\"urn:schemas-microsoft-com:xml-analysis:Execute\"";
    private final CredentialConnection connection;
    private final HttpClientRig httpClient;
    private final UsernamePasswordCredentials httpUserPass;
    // If true, the driver will store the query result XML in a text file on the local system for debugging
    private final boolean storeResponse = false;

    /**
     * Create a new driver with the given credentials. The connection string should be a http URL pointing to
     * a MS data pump.
     */
    public XmlaDriver(CredentialConnection connection) throws NoSuchAlgorithmException, KeyStoreException,
            KeyManagementException {
        this.connection = connection;
        httpClient = new HttpClientRig(false);
        httpUserPass = new UsernamePasswordCredentials(connection.getUsername(), connection.getPassword());
    }

    /**
     * Perform a XMLA query against the backing store. HTTP basic authentication will be used with the
     * credentials stored in the driver.
     */

    // FIXME: This method does not work twice.
    public XmlaResultSet query(String catalog, String query, Translator keyTranslator, boolean isTabular)
            throws AuthenticationException, IOException, XMLStreamException {
        String xmlaText = buildQueryXmla(catalog, query, isTabular);
        logger.debug("XMLA Request [xmla: {}]", xmlaText);

        logger.info("Running XMLA query. [text: {}]", query);

        CloseableHttpClient client = httpClient.getClient();
        CloseableHttpResponse response;
        try {
            HttpPost post = new HttpPost(connection.getConnectionString());
            post.addHeader(new BasicScheme().authenticate(httpUserPass, post, null));
            post.setEntity(new StringEntity(xmlaText));
            post.setHeader(HttpUtil.HEADER_CONTENTTYPE, "text/xml");
            post.setHeader(HttpUtil.HEADER_SOAPACTION, EXECUTE_ACTION);
            response = client.execute(post);
        } catch (Exception e) {
            client.close();
            throw e;
        }

        if (storeResponse) {
            File debugFile = File.createTempFile("xmla-", ".xml");
            logger.info("Storing XML response to file. [fn: {}]", debugFile.getAbsolutePath());
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(debugFile));
            IOUtil.copy(response.getEntity().getContent(), bos);
            response.close();
            bos.close();
            InputStream in = new BufferedInputStream(new FileInputStream(debugFile));
            return new XmlaTabularResultSet(client, in, keyTranslator);
        }

        if (isTabular) {
            return new XmlaTabularResultSet(client, response.getEntity().getContent(), keyTranslator);
        }

        return new XmlaMultidimensionalResultSet(client, response.getEntity().getContent(), keyTranslator);
    }

    /**
     * For the given query text, build an entire XMLA request.
     */
    private String buildQueryXmla(String catalog, String query, boolean isTabular) {
        // TODO: XML-safe these values
        if (isTabular) return String.format(XMLA_TABULAR, query, catalog);
        return String.format(XMLA, query, catalog);
    }

    @Override
    public void close() {
        // Intentionally Blank
    }
}
