/*
 * Copyright Red Hat, Inc. and/or its affiliates
 * and other contributors as indicated by the @author tags and
 * the COPYRIGHT.txt file distributed with this work.
 *
 * Licensed 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.teiid.client;

import java.io.EOFException;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.OptionalDataException;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.teiid.core.TeiidProcessingException;
import org.teiid.core.util.ExternalizeUtil;
import org.teiid.jdbc.JDBCPlugin;
import org.teiid.jdbc.RequestOptions;


/**
 * Request Message, used by MMXStatement for submitting queries.
 */
public class RequestMessage implements Externalizable {

    private static final String SPAN_CONTEXT = "spanContext"; //$NON-NLS-1$

    private static final RequestOptions DEFAULT_REQUEST_OPTIONS = new RequestOptions();

    public static final int DEFAULT_FETCH_SIZE = 2048;

    /** Transaction auto wrap constant - never wrap a command execution in a transaction */
    public static final String TXN_WRAP_OFF = "OFF"; //$NON-NLS-1$

    /** Transaction auto wrap constant - always wrap commands in a transaction. */
    public static final String TXN_WRAP_ON = "ON"; //$NON-NLS-1$

    /**
     * Transaction auto wrap constant - checks if a command
     * requires a transaction and will be automatically wrap it.
     */
    public static final String TXN_WRAP_DETECT = "DETECT"; //$NON-NLS-1$

    public enum StatementType {
        PREPARED, CALLABLE, STATEMENT
    }

    public enum ResultsMode {
        RESULTSET, UPDATECOUNT, EITHER
    }

    public enum ShowPlan {
        ON, DEBUG, OFF
    }

    private String[] commands;
    private boolean isBatchedUpdate;
    private int fetchSize = DEFAULT_FETCH_SIZE;
    private int cursorType;
    private boolean partialResultsFlag;
    private StatementType statementType = StatementType.STATEMENT;
    private List<?> parameterValues;
    private String txnAutoWrapMode;
    private String styleSheet;
    private ResultsMode resultsMode = ResultsMode.EITHER;
    //whether to use ResultSet cache if there is one
    private boolean useResultSetCache;
    // Treat the double quoted strings as variables in the command
    private boolean ansiQuotedIdentifiers = true;
    private ShowPlan showPlan = ShowPlan.OFF;
    private int rowLimit;
    private Serializable executionPayload;
    private long executionId;
    private int transactionIsolation;
    private boolean noExec;
    private boolean autoGeneratedKeys;
    private boolean delaySerialization;

    /*
     * Used by embedded connections, could change if we add support
     * for an asynch socket transport
     */
    private boolean sync;
    private RequestOptions requestOptions;
    private Object command;

    private Map<String, String> properties;

    public RequestMessage() {
    }

    public RequestMessage(String command) {
        this();
        setCommands(command);
    }

    public boolean isSync() {
        return sync;
    }

    public void setSync(boolean sync) {
        this.sync = sync;
    }

    public int getFetchSize() {
        return fetchSize;
    }

    public boolean supportsPartialResults() {
        return partialResultsFlag;
    }

    public void setFetchSize(int fetchSize) {
        this.fetchSize = fetchSize;
    }

    /**
     * @param partial
     */
    public void setPartialResults(boolean partial) {
        partialResultsFlag = partial;
    }

    /**
     * @return True if this request includes a prepared statement.
     */
    public boolean isPreparedStatement() {
        return this.statementType == StatementType.PREPARED;
    }

    /**
     * @return True if this request includes a callable statement.
     */
    public boolean isCallableStatement() {
        return this.statementType == StatementType.CALLABLE;
    }

    public void setStatementType(StatementType statementType) {
        this.statementType = statementType;
    }

    /**
     * @return A list of parameter values. May be null.
     */
    public List<?> getParameterValues() {
        if (parameterValues == null) {
            return Collections.EMPTY_LIST;
        }
        return parameterValues;
    }

    /**
     * @param values
     */
    public void setParameterValues(List<?> values) {
        parameterValues = values;
    }

    /**
     * @return String
     */
    public int getCursorType() {
        return cursorType;
    }

    /**
     * Sets the cursorType.
     * @param cursorType The cursorType to set
     */
    public void setCursorType(int cursorType) {
        this.cursorType = cursorType;
    }

    /**
     * @return String
     */
    public String getTxnAutoWrapMode() {
        if (txnAutoWrapMode == null) {
            return TXN_WRAP_DETECT;
        }
        return txnAutoWrapMode;
    }

    /**
     * Sets the txnAutoWrapMode.
     * @param txnAutoWrapMode The txnAutoWrapMode to set
     * @throws TeiidProcessingException
     */
    public void setTxnAutoWrapMode(String txnAutoWrapMode) throws TeiidProcessingException {
        if (txnAutoWrapMode != null) {
            txnAutoWrapMode = txnAutoWrapMode.toUpperCase();
            if (!(txnAutoWrapMode.equals(TXN_WRAP_OFF)
                || txnAutoWrapMode.equals(TXN_WRAP_ON)
                || txnAutoWrapMode.equals(TXN_WRAP_DETECT))) {
                 throw new TeiidProcessingException(JDBCPlugin.Event.TEIID20000, JDBCPlugin.Util.gs(JDBCPlugin.Event.TEIID20000, txnAutoWrapMode));
            }
        }
        this.txnAutoWrapMode = txnAutoWrapMode;
    }

    /**
     * @return String
     */
    public String getStyleSheet() {
        return styleSheet;
    }

    /**
     * Sets the styleSheet.
     * @param styleSheet The styleSheet to set
     */
    public void setStyleSheet(String styleSheet) {
        this.styleSheet = styleSheet;
    }

    public boolean useResultSetCache() {
        return useResultSetCache;
    }

    public void setUseResultSetCache(boolean useResultSetCacse) {
        this.useResultSetCache = useResultSetCacse;
    }

    public String getCommandString() {
        if (commands.length == 1) {
            return commands[0];
        }
        return Arrays.deepToString(commands);
    }

    public boolean isAnsiQuotedIdentifiers() {
        return ansiQuotedIdentifiers;
    }

    public void setAnsiQuotedIdentifiers(boolean ansiQuotedIdentifiers) {
        this.ansiQuotedIdentifiers = ansiQuotedIdentifiers;
    }

    public ShowPlan getShowPlan() {
        return showPlan;
    }

    public void setShowPlan(ShowPlan showPlan) {
        this.showPlan = showPlan;
    }

    /**
     * @return Returns the rowLimit.
     * @since 4.3
     */
    public int getRowLimit() {
        return this.rowLimit;
    }


    /**
     * @param rowLimit The rowLimit to set.
     * @since 4.3
     */
    public void setRowLimit(int rowLimit) {
        this.rowLimit = rowLimit;
    }

    public String[] getCommands() {
        return commands;
    }

    public void setCommands(String... batchedCommands) {
        this.commands = batchedCommands;
    }

    public Object getCommand() {
        return command;
    }

    public void setCommand(Object command) {
        this.command = command;
    }

    public void setExecutionPayload(Serializable executionPayload) {
        this.executionPayload = executionPayload;
    }

    public Serializable getExecutionPayload() {
        return executionPayload;
    }

    public long getExecutionId() {
        return executionId;
    }

    public void setExecutionId(long executionId) {
        this.executionId = executionId;
    }

    public void setBatchedUpdate(boolean isBatchedUpdate) {
        this.isBatchedUpdate = isBatchedUpdate;
    }

    public boolean isBatchedUpdate() {
        return isBatchedUpdate;
    }

    public ResultsMode getResultsMode() {
        return resultsMode;
    }

    public void setResultsMode(ResultsMode resultsMode) {
        this.resultsMode = resultsMode;
    }

    public int getTransactionIsolation() {
        return transactionIsolation;
    }

    public void setTransactionIsolation(int transactionIsolation) {
        this.transactionIsolation = transactionIsolation;
    }

    public boolean isNoExec() {
        return noExec;
    }

    public void setNoExec(boolean noExec) {
        this.noExec = noExec;
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        this.commands = ExternalizeUtil.readStringArray(in);
        this.isBatchedUpdate = in.readBoolean();
        this.fetchSize = in.readInt();
        this.cursorType = in.readInt();
        this.partialResultsFlag = in.readBoolean();
        this.statementType = StatementType.values()[in.readByte()];
        this.parameterValues = ExternalizeUtil.readList(in);
        in.readBoolean(); //legacy validation mode
        this.txnAutoWrapMode = (String)in.readObject();
        in.readObject(); // legacy xml format
        this.styleSheet = (String)in.readObject();
        this.resultsMode = ResultsMode.values()[in.readByte()];
        this.useResultSetCache = in.readBoolean();
        this.ansiQuotedIdentifiers = in.readBoolean();
        this.showPlan = ShowPlan.values()[in.readByte()];
        this.rowLimit = in.readInt();
        this.executionPayload = (Serializable)in.readObject();
        this.executionId = in.readLong();
        this.transactionIsolation = in.readInt();
        this.noExec = in.readBoolean();
        try {
            byte options = in.readByte();
            //8.3 property
            this.autoGeneratedKeys = (options & 1) == 1;
            //8.4 property
            this.delaySerialization = (options & 2) == 2;
            this.properties = ExternalizeUtil.readMap(in);
        } catch (OptionalDataException e) {
        } catch (EOFException e) {
        }
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        ExternalizeUtil.writeArray(out, commands);
        out.writeBoolean(isBatchedUpdate);
        out.writeInt(fetchSize);
        out.writeInt(cursorType);
        out.writeBoolean(partialResultsFlag);
        out.writeByte(statementType.ordinal());
        ExternalizeUtil.writeList(out, parameterValues);
        out.writeBoolean(false); //legacy xml validation
        out.writeObject(txnAutoWrapMode);
        out.writeObject(null); //legacy xml format
        out.writeObject(styleSheet);
        out.writeByte(resultsMode.ordinal());
        out.writeBoolean(useResultSetCache);
        out.writeBoolean(ansiQuotedIdentifiers);
        out.writeByte(showPlan.ordinal());
        out.writeInt(rowLimit);
        out.writeObject(executionPayload);
        out.writeLong(executionId);
        out.writeInt(transactionIsolation);
        out.writeBoolean(noExec);
        byte options = 0;
        if (autoGeneratedKeys) {
            options |= 1;
        }
        if (delaySerialization) {
            options |= 2;
        }
        out.writeByte(options);
        ExternalizeUtil.writeMap(out, properties);
    }

    public RequestOptions getRequestOptions() {
        if (this.requestOptions == null) {
            this.requestOptions = DEFAULT_REQUEST_OPTIONS;
        }
        return this.requestOptions;
    }

    public void setRequestOptions(RequestOptions requestOptions) {
        this.requestOptions = requestOptions;
    }

    public void setReturnAutoGeneratedKeys(boolean autoGenerateKeys) {
        this.autoGeneratedKeys = autoGenerateKeys;
    }

    public boolean isReturnAutoGeneratedKeys() {
        return this.autoGeneratedKeys;
    }

    public boolean isDelaySerialization() {
        return delaySerialization;
    }

    public void setDelaySerialization(boolean delaySerialization) {
        this.delaySerialization = delaySerialization;
    }

    public void setSpanContext(String string) {
        if (this.properties == null) {
            this.properties = new HashMap<String, String>();
        }
        if (string == null) {
            this.properties.remove(SPAN_CONTEXT);
        } else {
            properties.put(SPAN_CONTEXT, string);
        }
    }

    public String getSpanContext() {
        if (this.properties == null) {
            return null;
        }
        return this.properties.get(SPAN_CONTEXT);
    }

}
