package leigh.mecha.olap.xmla;

import leigh.eo.EO;
import leigh.mecha.log.MechaLogger;
import leigh.mecha.log.MechaLoggerFactory;
import leigh.mecha.util.Translator;
import org.apache.http.impl.client.CloseableHttpClient;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;

/**
 * A result set implementation returned by the XMLA driver.
 * <p>
 * This implementation currently only supports rowset respomses; multi-dimensional responses will result in
 * parse errors.
 *
 * @author Alex Leigh
 * @since mk12 mod1 (RETROGRADE)
 */
public class XmlaMultidimensionalResultSet implements XmlaResultSet {
    private static final MechaLogger logger = MechaLoggerFactory.getLogger(XmlaMultidimensionalResultSet.class);
    private final static XMLInputFactory factory = XMLInputFactory.newInstance();
    private final static String URN_SQL = "urn:schemas-microsoft-com:xml-sql";
    private final CloseableHttpClient client;
    private final XMLStreamReader parser;
    private final HashMap<String, String> colDictionary = new HashMap<>();
    private EO row;
    private final Translator<String> keyTranslator;
    private InputStream inputStream;

    public XmlaMultidimensionalResultSet(CloseableHttpClient client, InputStream response,
                                         Translator<String> keyTranslator) throws XMLStreamException {
        this.inputStream = response;
        this.client = client;
        factory.setProperty(XMLInputFactory.IS_COALESCING, true);
        this.parser = factory.createXMLStreamReader(inputStream);
        this.keyTranslator = keyTranslator;
        init();
    }

    /**
     * Read the request has far as building a list of the returned columns. This will set up next() to read
     * the first row from the result XML.
     */
    void init() throws XMLStreamException {
        logger.info("init() Called.");
        // Discover the column mapping dictionary
        for (int event = parser.next(); event != XMLStreamConstants.END_DOCUMENT; event = parser.next()) {
            logger.info("XML Event: [event: {}] [name: {}]", event,
                    parser.getAttributeValue(null, "name"));

            switch (event) {
                case XMLStreamConstants.START_ELEMENT:
                    String fieldName = parser.getAttributeValue(URN_SQL, "field");

                    if (fieldName != null) {
                        String fieldElementName = parser.getAttributeValue(null, "name");
                        colDictionary.put(fieldElementName, fieldName);
                        break;
                    }
                    break;

                case XMLStreamConstants.END_ELEMENT:
                    String name = parser.getLocalName();
                    if (name.equals("schema")) {
                        // OK - We are done
                        return;
                    }
                    break;
            }
        }

        logger.error("Failed to locate <schema> tag.");
        // If we made it this far something is wrong
        throw new XMLStreamException("Invalid document");
    }

    @Override
    public void close() throws IOException {
        if (inputStream != null)
            inputStream.close();

        if (client != null)
            client.close();
    }

    /**
     * Advance the result set to the next row. Returns <code>true</code> if a row was loaded or <code>false</code>
     * if it was not.
     */
    @Override
    public boolean next() throws XMLStreamException {
        boolean loading = false;
        row = new EO("row");

        String dataName = null;

        for (int event = parser.next(); event != XMLStreamConstants.END_DOCUMENT; event = parser.next()) {
            switch (event) {
                case XMLStreamConstants.CHARACTERS:
                    // Found row data
                    if (loading) {
                        row.setValue(keyTranslator.getTranslation(dataName), parser.getText());
                    }
                    break;
                case XMLStreamConstants.START_ELEMENT:
                    String startName = parser.getLocalName();

                    if (loading) {
                        // Inside a <row>. Get the column name for this data element.
                        dataName = colDictionary.get(startName);
                        if (dataName == null)
                            throw new XMLStreamException("Unknown column element in stream: " + startName);
                    } else if (startName.equals("row")) {
                        // Start of a <row>
                        loading = true;
                    }
                    break;
                case XMLStreamConstants.END_ELEMENT:
                    String endName = parser.getLocalName();
                    if (endName.equals("row")) {
                        // </row>
                        return true;
                    }
                    break;
            }
        }

        return false;
    }

    /**
     * Return the current object for this result set.
     */
    @Override
    public EO getRow() {
        return row;
    }
}