/*
// Licensed to Julian Hyde under one or more contributor license
// agreements. See the NOTICE file distributed with this work for
// additional information regarding copyright ownership.
//
// Julian Hyde licenses this file to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
*/
package org.olap4j.driver.xmla;

import org.olap4j.*;
import org.olap4j.driver.xmla.XmlaOlap4jConnection.BackendFlavor;
import org.olap4j.mdx.*;

import java.io.StringWriter;
import java.sql.*;
import java.util.concurrent.*;

/**
 * Implementation of {@link org.olap4j.OlapStatement}
 * for XML/A providers.
 *
 * @author jhyde
 * @since May 24, 2007
 */
abstract class XmlaOlap4jStatement implements OlapStatement {
    final XmlaOlap4jConnection olap4jConnection;
    private boolean closed;

    /**
     * Current cell set, or null if the statement is not executing anything.
     * Any method which modifies this member must synchronize
     * on the {@link XmlaOlap4jStatement}.
     */
    XmlaOlap4jCellSet openCellSet;
    private boolean canceled;
    int timeoutSeconds;
    Future<byte []> future;

    // Tells this statement to cancel as soon as it starts.
    private boolean cancelEarly = false;

    /**
     * Creates an XmlaOlap4jStatement.
     *
     * @param olap4jConnection Connection
     */
    XmlaOlap4jStatement(
        XmlaOlap4jConnection olap4jConnection)
    {
        assert olap4jConnection != null;
        this.olap4jConnection = olap4jConnection;
        this.closed = false;
    }

    /**
     * Returns the error-handler.
     *
     * @return Error handler
     */
    private XmlaHelper getHelper() {
        return olap4jConnection.helper;
    }

    // implement Statement

    public ResultSet executeQuery(String sql) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public int executeUpdate(String sql) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void close() throws SQLException {
        if (!closed) {
            closed = true;
            if (openCellSet != null) {
                CellSet c = openCellSet;
                openCellSet = null;
                c.close();
            }
        }
    }

    public int getMaxFieldSize() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void setMaxFieldSize(int max) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public int getMaxRows() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void setMaxRows(int max) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void setEscapeProcessing(boolean enable) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public int getQueryTimeout() throws SQLException {
        return timeoutSeconds;
    }

    public void setQueryTimeout(int seconds) throws SQLException {
        if (seconds < 0) {
            throw getHelper().createException(
                "illegal timeout value " + seconds);
        }
        this.timeoutSeconds = seconds;
    }

    public synchronized void cancel() {
        synchronized (this) {
            if (!canceled) {
                if (future != null) {
                    canceled = true;
                    future.cancel(true);
                } else {
                    this.cancelEarly = true;
                }
            }
        }
    }

    public SQLWarning getWarnings() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void clearWarnings() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void setCursorName(String name) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public boolean execute(String sql) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public ResultSet getResultSet() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public int getUpdateCount() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public boolean getMoreResults() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void setFetchDirection(int direction) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public int getFetchDirection() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void setFetchSize(int rows) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public int getFetchSize() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public int getResultSetConcurrency() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public int getResultSetType() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void addBatch(String sql) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void clearBatch() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public int[] executeBatch() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public OlapConnection getConnection() {
        return olap4jConnection;
    }

    public boolean getMoreResults(int current) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public ResultSet getGeneratedKeys() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public int executeUpdate(
        String sql, int autoGeneratedKeys) throws SQLException
    {
        throw new UnsupportedOperationException();
    }

    public int executeUpdate(
        String sql, int columnIndexes[]) throws SQLException
    {
        throw new UnsupportedOperationException();
    }

    public int executeUpdate(
        String sql, String columnNames[]) throws SQLException
    {
        throw new UnsupportedOperationException();
    }

    public boolean execute(
        String sql, int autoGeneratedKeys) throws SQLException
    {
        throw new UnsupportedOperationException();
    }

    public boolean execute(
        String sql, int columnIndexes[]) throws SQLException
    {
        throw new UnsupportedOperationException();
    }

    public boolean execute(
        String sql, String columnNames[]) throws SQLException
    {
        throw new UnsupportedOperationException();
    }

    public int getResultSetHoldability() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public boolean isClosed() throws SQLException {
        return closed;
    }

    public void setPoolable(boolean poolable) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public boolean isPoolable() throws SQLException {
        throw new UnsupportedOperationException();
    }

    // implement Wrapper

    public <T> T unwrap(Class<T> iface) throws SQLException {
        if (iface.isInstance(this)) {
            return iface.cast(this);
        }
        throw getHelper().createException(
            "does not implement '" + iface + "'");
    }

    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return iface.isInstance(this);
    }

    // implement OlapStatement

    public CellSet executeOlapQuery(String mdx) throws OlapException {
        final String catalog = olap4jConnection.getCatalog();
        final String roleName = olap4jConnection.getRoleName();
        final String propList = olap4jConnection.makeConnectionPropertyList();

        final String dataSourceInfo;
        switch (BackendFlavor.getFlavor(olap4jConnection, true)) {
        case ESSBASE:
            dataSourceInfo =
                olap4jConnection.getOlapDatabase().getDataSourceInfo();
            break;
        default:
            dataSourceInfo =
                olap4jConnection.getDatabase();
        }

        StringBuilder buf = new StringBuilder(
            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
            + "<soapenv:Envelope\n"
            + "    xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"\n"
            + "    xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n"
            + "    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
            + "    <soapenv:Body>\n"
            + "        <Execute xmlns=\"urn:schemas-microsoft-com:xml-analysis\">\n"
            + "        <Command>\n"
            + "        <Statement>\n"
            + "           <![CDATA[\n" + mdx + "]]>\n"
            + "         </Statement>\n"
            + "        </Command>\n"
            + "        <Properties>\n"
            + "          <PropertyList>\n");
        if (catalog != null) {
            buf.append("            <Catalog>");
            buf.append(catalog);
            buf.append("</Catalog>\n");
        }
        if (propList != null) {
            buf.append(propList);
        }
        if (roleName != null && !("".equals(roleName))) {
            buf.append("        <Roles>");
            buf.append(roleName);
            buf.append("</Roles>\n");
        }
        if (dataSourceInfo != null) {
            buf.append("            <DataSourceInfo>");
            buf.append(dataSourceInfo);
            buf.append("</DataSourceInfo>\n");
        }
        buf.append(
            "            <Format>Multidimensional</Format>\n"
            + "            <AxisFormat>TupleFormat</AxisFormat>\n"
            + "          </PropertyList>\n"
            + "        </Properties>\n"
            + "</Execute>\n"
            + "</soapenv:Body>\n"
            + "</soapenv:Envelope>");
        final String request = buf.toString();

        // Close the previous open CellSet, if there is one.
        synchronized (this) {
            if (openCellSet != null) {
                final XmlaOlap4jCellSet cs = openCellSet;
                openCellSet = null;
                try {
                    cs.close();
                } catch (SQLException e) {
                    throw getHelper().createException(
                        "Error while closing previous CellSet", e);
                }
            }

            this.future =
                olap4jConnection.proxy.submit(
                    olap4jConnection.serverInfos, request);
            openCellSet = olap4jConnection.factory.newCellSet(this);
        }
        if (cancelEarly) {
            cancel();
        }
        // Release the monitor before calling populate, so that cancel can
        // grab the monitor if it needs to.
        openCellSet.populate();
        return openCellSet;
    }

    public CellSet executeOlapQuery(
        SelectNode selectNode)
        throws OlapException
    {
        final String mdx = toString(selectNode);
        return executeOlapQuery(mdx);
    }

    public void addListener(
        CellSetListener.Granularity granularity,
        CellSetListener listener)
        throws OlapException
    {
        throw getHelper().createException(
            "This driver does not support the cell listener API.");
    }

    /**
     * Waits for an XMLA request to complete.
     *
     * <p>You must not hold the monitor on this Statement when calling this
     * method; otherwise {@link #cancel()} will not be able to operate.
     *
     * @return Byte array resulting from successful request
     *
     * @throws OlapException if error occurred, or request timed out or
     * was canceled
     */
    byte[] getBytes() throws OlapException {
        synchronized (this) {
            if (future == null) {
                throw new IllegalArgumentException();
            }
        }
        try {
            // Wait for the request to complete, with timeout if necessary.
            // Whether or not timeout is used, the request can still be
            // canceled.
            if (timeoutSeconds > 0) {
                return future.get(timeoutSeconds, TimeUnit.SECONDS);
            } else {
                return future.get();
            }
        } catch (InterruptedException e) {
            throw getHelper().createException(null, e);
        } catch (ExecutionException e) {
            throw getHelper().createException(null, e.getCause());
        } catch (TimeoutException e) {
            throw getHelper().createException(
                "Query timeout of " + timeoutSeconds + " seconds exceeded");
        } catch (CancellationException e) {
            throw getHelper().createException("Query canceled");
        } finally {
            synchronized (this) {
                if (future == null) {
                    throw new IllegalArgumentException();
                }
                future = null;
            }
        }
    }

    /**
     * Converts a {@link org.olap4j.mdx.ParseTreeNode} to MDX string.
     *
     * @param node Parse tree node
     * @return MDX text
     */
    private static String toString(ParseTreeNode node) {
        StringWriter sw = new StringWriter();
        ParseTreeWriter parseTreeWriter = new ParseTreeWriter(sw);
        node.unparse(parseTreeWriter);
        return sw.toString();
    }
}

// End XmlaOlap4jStatement.java
