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;

/**
 * This class provides access to a result set resulting from a XMLA query. The result set could contain
 * tabular or multidimensional data.
 *
 * @author Alex Leigh
 */
public class XmlaTabularResultSet implements XmlaResultSet {
    private static final MechaLogger logger = MechaLoggerFactory.getLogger(XmlaTabularResultSet.class);
    private final static XMLInputFactory factory = XMLInputFactory.newInstance();
    private final CloseableHttpClient client;
    private final XMLStreamReader parser;
    private EO row;
    private Translator<String> keyTranslator;
    private InputStream inputStream;

    public XmlaTabularResultSet(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();
    }

    /**
     * Advance to the ROOT tag.
     *
     * @throws XMLStreamException
     */
    public void init() throws XMLStreamException {
        // Discover the column mapping dictionary
        for (int event = parser.next(); event != XMLStreamConstants.END_DOCUMENT; event = parser.next()) {
            switch (event) {
                case XMLStreamConstants.START_ELEMENT:
                    String name = parser.getLocalName();
                    if (name.equals("root")) {
                        // OK - We are done
                        return;
                    }
                    break;
            }
        }

        logger.error("Failed to locate <root> 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();
    }

    @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) {
                        if (keyTranslator == null) {
                            row.setValue(dataName, parser.getText());
                        } else {
                            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 = 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;
    }

    @Override
    public EO getRow() {
        return row;
    }
}
