/*
 * Copyright 2006-2007 Queplix Corp.
 *
 * Licensed under the Queplix Public License, Version 1.1.1 (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.queplix.com/solutions/commercial-open-source/queplix-public-license/
 *
 * 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 com.queplix.core.utils.sql.vendor.mssql;

import com.queplix.core.utils.StringHelper;
import com.queplix.core.utils.sql.error.SQLDeadlockException;
import com.queplix.core.utils.sql.error.SQLDeleteConflictException;
import com.queplix.core.utils.sql.error.SQLDuplicateKeyException;
import com.queplix.core.utils.sql.error.SQLIndexConflictException;
import com.queplix.core.utils.sql.generic.BinaryParserImpl;
import com.queplix.core.utils.sql.generic.FloatParserImpl;
import com.queplix.core.utils.sql.generic.IntParserImpl;
import com.queplix.core.utils.sql.generic.LongParserImpl;
import com.queplix.core.utils.sql.generic.MemoParserImpl;
import com.queplix.core.utils.sql.generic.SqlWrapperImpl;
import com.queplix.core.utils.sql.generic.StringParserImpl;
import com.queplix.core.utils.sql.parser.BinaryParser;
import com.queplix.core.utils.sql.parser.DateParser;
import com.queplix.core.utils.sql.parser.FloatParser;
import com.queplix.core.utils.sql.parser.IntParser;
import com.queplix.core.utils.sql.parser.LongParser;
import com.queplix.core.utils.sql.parser.MemoParser;
import com.queplix.core.utils.sql.parser.StringParser;
import com.queplix.core.utils.sql.parser.TimeParser;
import com.queplix.core.utils.sql.parser.TimestampParser;

import java.sql.Connection;
import java.sql.SQLException;

/**
 * Microsoft SQL Server 2000 SQL operations wrapper implementation.
 *
 * @author [ONZ] Oleg N. Zhovtanyuk
 * @version $Revision: 1.1.1.1 $ $Date: 2005/09/12 15:31:22 $
 */
public class SqlWrapperMSSQLImpl
        extends SqlWrapperImpl {

    // =============================================================== Constants

    // Error codes.
    private static final int UNKNOWN_ERROR_CODE = 0;
    private static final int DUPLICATE_KEY_ERROR_CODE = 2627;
    private static final int INDEX_CONFLICT_ERROR_CODE = 2601;
    private static final int DELETE_CONFLICT_ERROR_CODE = 547;
    private static final int DEADLOCK_ERROR_CODE = 1205;

    // ================================================================== Fields

    // Parser implementations.
    // Change the generic implementations for database-specific ones, as needed.
    private final IntParserImpl iP = new IntParserImpl();
    private final LongParserImpl lP = new LongParserImpl();
    private final FloatParserImpl fP = new FloatParserImpl();
    private final StringParserImpl sP = new StringParserImpl();
    private final BinaryParserImpl bP = new BinaryParserImpl();
    private final MemoParserImpl mP = new MemoParserImpl();
    private final MemoParserImpl mlP = new MemoParserImpl();
    private final DateParserMSSQLImpl dP = new DateParserMSSQLImpl();
    private final TimestampParserMSSQLImpl tsP = new TimestampParserMSSQLImpl();
    private final TimeParserMSSQLImpl tP = new TimeParserMSSQLImpl();

    // ================================================ Interface implementation

    /* (non-Javadoc)
     * @see SqlWrapper#getIntParser()
     */

    public IntParser getIntParser() {
        return iP;
    }

    /* (non-Javadoc)
     * @see SqlWrapper#getLongParser()
     */
    public LongParser getLongParser() {
        return lP;
    }

    /* (non-Javadoc)
     * @see SqlWrapper#getFloatParser()
     */
    public FloatParser getFloatParser() {
        return fP;
    }

    /* (non-Javadoc)
     * @see SqlWrapper#getStringParser()
     */
    public StringParser getStringParser() {
        return sP;
    }

    /* (non-Javadoc)
     * @see SqlWrapper#getTimestampParser()
     */
    public TimestampParser getTimestampParser() {
        return tsP;
    }

    /* (non-Javadoc)
     * @see SqlWrapper#getDateParser()
     */
    public DateParser getDateParser() {
        return dP;
    }

    /* (non-Javadoc)
     * @see SqlWrapper#getTimeParser()
     */
    public TimeParser getTimeParser() {
        return tP;
    }

    /* (non-Javadoc)
     * @see SqlWrapper#getMemoParser()
     */
    public MemoParser getMemoParser() {
        return mP;
    }

    /* (non-Javadoc)
     * @see SqlWrapper#getMemoLongParser()
     */
    public MemoParser getMemoLongParser() {
        return mlP;
    }

    /* (non-Javadoc)
     * @see SqlWrapper#getBinaryParser()
     */
    public BinaryParser getBinaryParser() {
        return bP;
    }

    /* (non-Javadoc)
     * @see SqlWrapper#getNextSeq(Connection, String)
     */
    public long getNextSeq(Connection con, String name)
            throws SQLException {
        /** @todo implement it */
        throw new UnsupportedOperationException();
    }

    /* (non-Javadoc)
     * @see SqlWrapper#getSoundex(Connection, String)
     */
    public String getSoundex(Connection con, String str)
            throws SQLException {

        String soundexSQL = "SELECT SOUNDEX(?)";
        return getSoundexBySQL(con, soundexSQL, str);
    }

    /**
     * Intentionally returns <code>null</code> value to skip schema filtering.
     * <p/>
     * <p/>
     * MS SQL Server has no API to get the current schema, so the method always
     * returns <code>null</code> value to skip schema filtering.
     * </p>
     *
     * @param con database connection
     * @return current schema
     * @see SqlWrapper#getCurrentSchema(Connection)
     */
    public String getCurrentSchema(Connection con) {
        return null;
    }

    // ======================================================= Protected methods

    /* (non-Javadoc)
     * @see SqlWrapperImpl#throwSQLException(SQLException)
     */

    protected void throwSQLException(SQLException ex)
            throws SQLException {
        switch(ex.getErrorCode()) {
            case UNKNOWN_ERROR_CODE:
                throwUnknwonException(ex);
            case DUPLICATE_KEY_ERROR_CODE:
                throwSQLDuplicateKeyException2(ex);
            case INDEX_CONFLICT_ERROR_CODE:
                throwSQLIndexConflictException(ex);
            case DELETE_CONFLICT_ERROR_CODE:
                throwSQLDeleteConflictException(ex);
            case DEADLOCK_ERROR_CODE:
                throwSQLDeadlockException(ex);
            default:
                throw ex;
        }
    }

    // ========================================================= Private methods

    /* (non-Javadoc)
     * Parse error message and throw SQLException.
     */

    private void throwUnknwonException(SQLException ex)
            throws SQLException {

        // Get error message.
        String message = ex.getMessage();

        if(message != null && message.indexOf("Cannot insert duplicate") >= 0) {
            throwSQLDuplicateKeyException(ex);
        } else {
            throw ex;
        }
    }

    /* (non-Javadoc)
     * Throws the SQLDuplicateKeyException.
     *
     * Server error message is:
     * <pre>
     * Cannot insert duplicate key row in object '%.*ls' with unique index '%.*ls'.
     * </pre>
     */
    private void throwSQLDuplicateKeyException(SQLException ex)
            throws SQLException {

        // Get error message.
        String message = ex.getMessage();
        int beginIndex = 0;
        int endIndex = 0;

        // Get table name.
        String table = null;
        beginIndex = message.indexOf('\'') + 1;
        if(beginIndex >= 0) {
            endIndex = message.indexOf('\'', beginIndex);
            if(endIndex >= 0) {
                table = message.substring(beginIndex, endIndex);
            }
        }

        // Get contraint object name.
        String constraint = null;
        if(endIndex >= 0) {
            beginIndex = message.indexOf('\'', endIndex + 1) + 1;
            if(beginIndex >= 0) {
                endIndex = message.indexOf('\'', beginIndex);
                if(endIndex >= 0) {
                    constraint = message.substring(beginIndex, endIndex);
                }
            }
        }

        throw new SQLDuplicateKeyException(message, table, constraint);

    }

    /* (non-Javadoc)
     * Throws the SQLDuplicateKeyException.
     *
     * Server error message is:
     * <pre>
     * Violation of %ls constraint '%.*ls'.
     * Cannot insert duplicate key in object '%.*ls'.
     * </pre>
     */
    private void throwSQLDuplicateKeyException2(SQLException ex)
            throws SQLDuplicateKeyException {

        // Get error message.
        String message = ex.getMessage();
        int beginIndex = 0;
        int endIndex = 0;

        // Get constraint name.
        String constraint = "";
        beginIndex = message.indexOf('\'') + 1;
        if(beginIndex >= 0) {
            endIndex = message.indexOf('\'', beginIndex);
            if(endIndex >= 0) {
                constraint = message.substring(beginIndex, endIndex);
            }
        }

        // Get table name.
        String table = "";
        if(endIndex >= 0) {
            beginIndex = message.indexOf('\'', endIndex + 1) + 1;
            if(beginIndex >= 0) {
                endIndex = message.indexOf('\'', beginIndex);
                if(endIndex >= 0) {
                    table = message.substring(beginIndex, endIndex);
                }
            }
        }

        throw new SQLDuplicateKeyException(message, table, constraint);
    }

    /* (non-Javadoc)
     * Throws the SQLIndexConflictException.
     *
     * Server error message is:
     * <pre>
     * Cannot insert duplicate key row in object '%.*ls'
     * with unique index '%.*ls'.
     * </pre>
     */
    private void throwSQLIndexConflictException(SQLException ex)
            throws SQLIndexConflictException {

        // Get error message.
        String message = ex.getMessage();
        int beginIndex = 0;
        int endIndex = 0;

        // Get table name.
        String table = null;
        beginIndex = message.indexOf('\'') + 1;
        if(beginIndex >= 0) {
            endIndex = message.indexOf('\'', beginIndex);
            if(endIndex >= 0) {
                table = message.substring(beginIndex, endIndex);
            }
        }

        // Get contraint object name.
        String constraint = null;
        if(endIndex >= 0) {
            beginIndex = message.indexOf('\'', endIndex + 1) + 1;
            if(beginIndex >= 0) {
                endIndex = message.indexOf('\'', beginIndex);
                if(endIndex >= 0) {
                    constraint = message.substring(beginIndex, endIndex);
                }
            }
        }

        throw new SQLIndexConflictException(message, table, constraint);

    }

    /* (non-Javadoc)
     * Throws the SQLDeleteConflictException.
     *
     * Server error message is:
     * <pre>
     * %ls statement conflicted with %ls %ls constraint '%.*ls'.
     * The conflict occurred in database '%.*ls', table '%.*ls'%ls%.*ls%ls.
     * </pre>
     */
    private void throwSQLDeleteConflictException(SQLException ex)
            throws SQLDeleteConflictException {

        // Get error message.
        String message = ex.getMessage();
        int beginIndex = 0;
        int endIndex = 0;

        // Get constraint name.
        String constraint = "";
        beginIndex = message.indexOf('\'') + 1;
        if(beginIndex >= 0) {
            endIndex = message.indexOf('\'', beginIndex);
            if(endIndex >= 0) {
                constraint = message.substring(beginIndex, endIndex);
            }
        }

        // Skip database name.
        if(endIndex >= 0) {
            endIndex = message.indexOf('\'', endIndex + 1);
        }
        if(endIndex >= 0) {
            endIndex = message.indexOf('\'', endIndex + 1);
        }

        // Get table name.
        String table = "";
        if(endIndex >= 0) {
            beginIndex = message.indexOf('\'', endIndex + 1) + 1;
            if(beginIndex >= 0) {
                endIndex = message.indexOf('\'', beginIndex);
                if(endIndex >= 0) {
                    table = message.substring(beginIndex, endIndex);
                }
            }
        }

        // Get column name.
        String column = "";
        if(endIndex >= 0) {
            beginIndex = message.indexOf('\'', endIndex + 1) + 1;
            if(beginIndex >= 0) {
                endIndex = message.indexOf('\'', beginIndex);
                if(endIndex >= 0) {
                    column = message.substring(beginIndex, endIndex);
                }
            }
        }

        throw new SQLDeleteConflictException(message, table, column,
                constraint);

    }

    /* (non-Javadoc)
     * Throws the SQLDeadlockException.
     *
     * Server error message is:
     * <pre>
     * Transaction (Process ID %d) was deadlocked on lock resources
     * with another process and has been chosen as the deadlock victim.
     * Rerun the transaction.
     * </pre>
     */
    private void throwSQLDeadlockException(SQLException ex)
            throws SQLDeadlockException {

        // Get error message.
        String message = ex.getMessage();

        // Get process ID.
        int processID = StringHelper.EMPTY_NUMBER;
        int beginIndex = message.indexOf("ID") + 3;
        int endIndex = message.indexOf(')', beginIndex);
        try {
            processID = Integer.parseInt(message.substring(beginIndex,
                    endIndex));
        } catch (NumberFormatException e) {
        }

        throw new SQLDeadlockException(message, processID);
    }

}
