/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


package org.hsqldb.util;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import java.util.Vector;

/**
 * @author Nicolas BAZIN, INGENICO
 * @version 1.7.0
 */
class TransferSQLText extends DataAccessPoint {

    String              sFileName              = null;
    BufferedWriter      WTextWrite             = null;
    BufferedReader      WTextRead              = null;
    protected boolean   StructureAlreadyParsed = false;
    Hashtable           DbStmts                = null;
    protected JDBCTypes JDBCT                  = null;

    TransferSQLText(String _FileName,
                    Traceable t) throws DataAccessPointException {

        super(t);

        sFileName = _FileName;
        JDBCT     = new JDBCTypes();

        if (sFileName == null) {
            throw new DataAccessPointException("File name not initialized");
        }
    }

    boolean execute(String statement) throws DataAccessPointException {

        if (WTextWrite == null) {
            try {
                WTextWrite = new BufferedWriter(new FileWriter(sFileName));
            } catch (IOException e) {
                throw new DataAccessPointException(e.getMessage());
            }
        }

        try {
            WTextWrite.write(statement + "\n");
            WTextWrite.flush();
        } catch (IOException e) {
            throw new DataAccessPointException(e.getMessage());
        }

        return true;
    }

    void putData(String statement, TransferResultSet r,
                 int iMaxRows) throws DataAccessPointException {

        int i = 0;

        if (r == null) {
            return;
        }

        if (WTextWrite == null) {
            try {
                WTextWrite = new BufferedWriter(new FileWriter(sFileName));
            } catch (IOException e) {
                throw new DataAccessPointException(e.getMessage());
            }
        }

        try {
            while (r.next()) {
                if (i == 0) {
                    WTextWrite.write(statement + "\n");
                    WTextWrite.flush();
                }

                transferRow(r);

                if (iMaxRows != 0 && i == iMaxRows) {
                    break;
                }

                i++;

                if (iMaxRows != 0 || i % 100 == 0) {
                    tracer.trace("Transfered " + i + " rows");
                }
            }
        } catch (Exception e) {
            throw new DataAccessPointException(e.getMessage());
        } finally {
            try {
                if (i > 0) {
                    WTextWrite.write("\tNumber of Rows=" + i + "\n\n");
                    WTextWrite.flush();
                }
            } catch (IOException e) {
                throw new DataAccessPointException(e.getMessage());
            }
        }
    }

    void close() throws DataAccessPointException {

        if (WTextWrite != null) {
            try {
                WTextWrite.flush();
                WTextWrite.close();
            } catch (IOException e) {}
        }
    }

    /**
     * Method declaration
     *
     *
     * @param type
     * @param r
     * @param p
     *
     * @throws SQLException
     */
    private void transferRow(TransferResultSet r) throws Exception {

        String sLast = "";
        int    len   = r.getColumnCount();

        if (WTextWrite == null) {
            try {
                WTextWrite = new BufferedWriter(new FileWriter(sFileName));
            } catch (IOException e) {
                throw new DataAccessPointException(e.getMessage());
            }
        }

        for (int i = 0; i < len; i++) {
            int t = r.getColumnType(i + 1);

            sLast = "column=" + r.getColumnName(i + 1) + " datatype="
                    + (String) helper.getSupportedTypes().get(new Integer(t));

            Object o = r.getObject(i + 1);

            if (o == null) {
                sLast += " value=<null>";
            } else {
                o     = helper.convertColumnValue(o, i + 1, t);
                sLast += " value=\'" + o.toString() + "\'";
            }

            WTextWrite.write("\t" + sLast + "\n");
            WTextWrite.flush();
        }

        WTextWrite.write("\n");
        WTextWrite.flush();

        sLast = "";
    }

    class ColumnDef {

        String columnName;
        String columnType;
        String options;
        int    start;
        int    len;

        public ColumnDef() {

            columnName = "";
            columnType = "";
            options    = "";
            start      = 0;
            len        = 0;
        }
    }

    ColumnDef getColumnDef(String ColumnsDesc, int curPos) {

        int       nextPos   = 0;
        ColumnDef columnDef = new TransferSQLText.ColumnDef();

        columnDef.start = curPos;

        if ((ColumnsDesc == null) || (ColumnsDesc.length() == 0)
                || (curPos >= ColumnsDesc.length())) {
            return new TransferSQLText.ColumnDef();
        }

        String stbuff = ColumnsDesc.substring(curPos);

        try {
            int i = 0;

            for (; i < stbuff.length(); i++) {
                int c = stbuff.charAt(i);

                if (c == ',' || c == ' ' || c == ')' || c == ';') {
                    continue;
                } else {
                    break;
                }
            }

            if (i == stbuff.length()) {
                return new TransferSQLText.ColumnDef();
            }

            columnDef.len += i;
            stbuff        = stbuff.substring(i);

            while (stbuff.charAt(nextPos) != ' ') {
                nextPos++;
            }

            columnDef.columnName = stbuff.substring(0, nextPos);
            stbuff               = stbuff.substring(nextPos);
            columnDef.len        += nextPos;
            nextPos              = 0;

            if (!columnDef.columnName.toUpperCase().equals("CONSTRAINT")) {
                i = 0;

                for (; i < stbuff.length() && stbuff.charAt(i) == ' '; i++) {}

                stbuff        = stbuff.substring(i);
                columnDef.len += i;

                while ((stbuff.charAt(nextPos) != '(')
                        && (stbuff.charAt(nextPos) != ',')
                        && (stbuff.charAt(nextPos) != ')')
                        && (stbuff.charAt(nextPos) != ';')
                        && (stbuff.charAt(nextPos) != ' ')) {
                    nextPos++;
                }

                columnDef.columnType = stbuff.substring(0,
                        nextPos).toUpperCase();
                stbuff        = stbuff.substring(nextPos);
                columnDef.len += nextPos;
                nextPos       = 0;
            }

            while ((stbuff.charAt(nextPos) != ',')
                    && (stbuff.charAt(nextPos) != ';')
                    && (nextPos < stbuff.length())
                    && (stbuff.charAt(nextPos) != ')')) {
                if (stbuff.charAt(nextPos) == '(') {
                    while (stbuff.charAt(nextPos) != ')') {
                        nextPos++;
                    }
                }

                nextPos++;
            }

            columnDef.options = stbuff.substring(0, nextPos);
            columnDef.len     += nextPos;
        } catch (Exception e) {
            columnDef = new TransferSQLText.ColumnDef();
        }

        return columnDef;
    }

    String translateTypes(String CreateLine, TransferTable TTable,
                          DataAccessPoint Dest)
                          throws DataAccessPointException {

        String    translatedLine = "";
        JDBCTypes JDBCT          = new JDBCTypes();
        int       currentPos     = 0;
        String    columnName     = "";
        String    columnType     = "";
        int       colnum         = 0;
        ColumnDef cDef;

        currentPos     = CreateLine.indexOf('(') + 1;
        translatedLine = CreateLine.substring(0, currentPos);

        do {
            cDef = getColumnDef(CreateLine, currentPos);

            if (cDef.len == 0) {
                break;
            }

            columnName = cDef.columnName;
            columnType = cDef.columnType;

            if (columnName.toUpperCase().indexOf("CONSTRAINT") >= 0) {
                translatedLine +=
                    CreateLine.substring(currentPos, currentPos + cDef.len)
                    + ",";
                currentPos += cDef.len + 1;

                colnum++;

                continue;
            }

            columnName = Dest.helper.formatIdentifier(columnName) + " ";

            try {
                Integer inttype = new Integer(
                    Dest.helper.convertToType(JDBCT.toInt(columnType)));

                columnType = (String) TTable.hTypes.get(inttype);
            } catch (Exception JDBCtypeEx) {}

            if (cDef.options != null) {
                columnType += cDef.options;
            }

            try {
                columnType = Dest.helper.fixupColumnDefWrite(TTable, null,
                        columnType, null, colnum);
            } catch (SQLException SQLe) {
                return CreateLine;
            }

            translatedLine += columnName + " " + columnType + ",";
            currentPos     += cDef.len + 1;

            colnum++;
        } while (true);

        return translatedLine.substring(0, translatedLine.length() - 1)
               + ");";
    }

    void parseFileForTables() throws DataAccessPointException {

        StringTokenizer Tokenizer;

        if (WTextRead == null) {
            try {
                WTextRead = new BufferedReader(new FileReader(sFileName));
            } catch (IOException e) {
                throw new DataAccessPointException(e.getMessage());
            }
        }

        String        currentLine  = "";
        String        Token        = "";
        String        name         = "";
        TransferTable relatedTable = null;

        try {
            while ((currentLine = WTextRead.readLine()) != null) {
                currentLine = currentLine.trim() + ";";
                Tokenizer   = new StringTokenizer(currentLine);

                try {
                    Token = Tokenizer.nextToken();
                } catch (NoSuchElementException NSE) {
                    continue;
                }

                if (Token == null) {
                    continue;
                }

                if (!Token.toUpperCase().equals("CREATE")) {
                    continue;
                }

                Token = Tokenizer.nextToken().toUpperCase();

                if (Token.equals("TABLE") || Token.equals("VIEW")) {
                    try {
                        name = Tokenizer.nextToken(" (;");
                        relatedTable = new TransferTable(this, name, "",
                                                         Token, tracer);
                        relatedTable.Stmts.bCreate      = false;
                        relatedTable.Stmts.bDelete      = false;
                        relatedTable.Stmts.bDrop        = false;
                        relatedTable.Stmts.bCreateIndex = false;
                        relatedTable.Stmts.bDropIndex   = false;
                        relatedTable.Stmts.bInsert      = false;
                        relatedTable.Stmts.bAlter       = false;

                        DbStmts.put(relatedTable.Stmts.sSourceTable,
                                    relatedTable);
                    } catch (NoSuchElementException NSE) {
                        continue;
                    }
                }
            }
        } catch (Exception IOe) {
            throw new DataAccessPointException(IOe.getMessage());
        }
    }

    void parseFileForTheRest(TransferTable TTable,
                             DataAccessPoint Dest)
                             throws DataAccessPointException {

        StringTokenizer Tokenizer;

        StructureAlreadyParsed = true;

        if (WTextRead == null) {
            try {
                WTextRead = new BufferedReader(new FileReader(sFileName));
            } catch (IOException e) {
                throw new DataAccessPointException(e.getMessage());
            }
        }

        String        currentLine  = "";
        String        Token        = "";
        String        name         = "";
        TransferTable relatedTable = null;

        try {
            while ((currentLine = WTextRead.readLine()) != null) {
                currentLine = currentLine.trim() + ";";
                Tokenizer   = new StringTokenizer(currentLine);

                try {
                    Token = Tokenizer.nextToken();
                } catch (NoSuchElementException NSE) {
                    continue;
                }

                if (Token == null) {
                    continue;
                }

                if (Token.toUpperCase().equals("INSERT")) {
                    try {
                        if (!Tokenizer.nextToken().toUpperCase().equals(
                                "INTO")) {
                            throw new DataAccessPointException(
                                "Error in INSERT statement: no INTO found");
                        }

                        Token = Tokenizer.nextToken();

                        if ((relatedTable =
                                (TransferTable) DbStmts.get(Token)) != null) {
                            relatedTable.Stmts.bDelete     = true;
                            relatedTable.Stmts.bInsert     = true;
                            relatedTable.Stmts.sDestInsert = currentLine;
                            relatedTable.Stmts.sDestDelete =
                                "DELETE FROM "
                                + relatedTable.Stmts.sSourceTable + ";";
                        }

                        continue;
                    } catch (NoSuchElementException NSE) {
                        continue;
                    }
                } else if (Token.toUpperCase().equals("ALTER")) {
                    try {
                        if (!Tokenizer.nextToken().toUpperCase().equals(
                                "TABLE")) {
                            continue;
                        }

                        name  = Tokenizer.nextToken();
                        Token = Tokenizer.nextToken().toUpperCase();

                        if (!Token.equals("ADD")) {
                            continue;
                        }

                        do {
                            Token = Tokenizer.nextToken().toUpperCase();
                        } while (!Token.equals("CONSTRAINT"));

                        if ((relatedTable = (TransferTable) DbStmts.get(name))
                                != null) {
                            if (relatedTable.Stmts.sDestAlter == null) {
                                relatedTable.Stmts.sDestAlter = "";
                            }

                            relatedTable.Stmts.bAlter     = true;
                            relatedTable.Stmts.sDestAlter += currentLine;
                        } else {
                            throw new DataAccessPointException(
                                "table not found");
                        }

                        Token = Tokenizer.nextToken();

                        if (relatedTable.Stmts.sDestDrop == null) {
                            relatedTable.Stmts.sDestDrop = "";
                        }

                        relatedTable.Stmts.bDrop = true;
                        relatedTable.Stmts.sDestDrop =
                            "ALTER TABLE " + name + " DROP CONSTRAINT "
                            + Token + ";" + relatedTable.Stmts.sDestDrop;

                        continue;
                    } catch (NoSuchElementException NSE) {
                        continue;
                    }
                } else if (!Token.toUpperCase().equals("CREATE")) {
                    continue;
                }

                Token = Tokenizer.nextToken().toUpperCase();

                if (Token.equals("TABLE") || Token.equals("VIEW")) {
                    try {
                        name = Tokenizer.nextToken(" (;");

                        if (!DbStmts.containsKey(name)) {
                            throw new DataAccessPointException(
                                "error: index is created before the table");
                        }

                        relatedTable = (TransferTable) DbStmts.get(name);
                        relatedTable.Stmts.bCreate = true;
                        relatedTable.Stmts.bDrop   = true;

//                        relatedTable.Stmts.sDestCreate = currentLine;
                        relatedTable.Stmts.sDestCreate =
                            translateTypes(currentLine, TTable, Dest);
                        relatedTable.Stmts.sDestDrop =
                            "DROP " + relatedTable.Stmts.sType + " " + name
                            + ";";

                        DbStmts.put(relatedTable.Stmts.sSourceTable,
                                    relatedTable);
                    } catch (NoSuchElementException NSE) {
                        continue;
                    }
                }

                if (Token.equals("INDEX") || Token.equals("UNIQUE")) {
                    try {
                        while ((Token =
                                Tokenizer.nextToken()).toUpperCase().equals(
                                    "INDEX")) {
                            ;
                        }

                        String IndexdropCommand = "DROP INDEX " + Token
                                                  + " ;";

                        while ((Token = Tokenizer.nextToken(
                                " (")).toUpperCase().equals("ON")) {
                            ;
                        }

                        name = Token;

                        if (!DbStmts.containsKey(Token)) {
                            throw new DataAccessPointException(
                                "error: index is created before the table");
                        }

                        relatedTable = (TransferTable) DbStmts.get(Token);

                        if (relatedTable.Stmts.sDestCreateIndex == null) {
                            relatedTable.Stmts.sDestCreateIndex = "";
                        }

                        if (relatedTable.Stmts.sDestDropIndex == null) {
                            relatedTable.Stmts.sDestDropIndex = "";
                        }

                        relatedTable.Stmts.bCreateIndex     = true;
                        relatedTable.Stmts.bDropIndex       = true;
                        relatedTable.Stmts.sDestCreateIndex += currentLine;
                        relatedTable.Stmts.sDestDropIndex += IndexdropCommand;
                    } catch (NoSuchElementException NSE) {
                        continue;
                    }
                }
            }
        } catch (IOException IOe) {
            throw new DataAccessPointException(IOe.getMessage());
        }
    }

    Vector getTables(String sCatalog,
                     String[] sSchemas) throws DataAccessPointException {

        Vector AllTables = new Vector();

        if (DbStmts == null) {
            DbStmts = new Hashtable();
        }

        if (WTextRead != null) {
            try {
                WTextRead.close();

                WTextRead = null;
            } catch (IOException e) {}
        }

        this.parseFileForTables();

        StructureAlreadyParsed = false;

        Enumeration e = DbStmts.elements();

        while (e.hasMoreElements()) {
            AllTables.addElement(e.nextElement());
        }

        return AllTables;
    }

    void getTableStructure(TransferTable TTable,
                           DataAccessPoint Dest)
                           throws DataAccessPointException {

        if (!StructureAlreadyParsed) {
            if (WTextRead != null) {
                try {
                    WTextRead.close();

                    WTextRead = null;
                } catch (IOException e) {}
            }

            this.parseFileForTheRest(TTable, Dest);
        }
    }

    TransferResultSet getData(String statement)
    throws DataAccessPointException {

        StringTokenizer Tokenizer;
        String          tableName = "";

        try {
            Tokenizer = new StringTokenizer(statement);

            while (!Tokenizer.nextToken().toUpperCase().equals("FROM")) {
                ;
            }

            tableName = Tokenizer.nextToken(" ;");
        } catch (NoSuchElementException NSE) {
            throw new DataAccessPointException(
                "Table name not found in statement: " + statement);
        }

        if (WTextRead != null) {
            try {
                WTextRead.close();

                WTextRead = null;
            } catch (IOException e) {}
        }

        return (this.parseFileForData(tableName));
    }

    TransferResultSet parseFileForData(String tableName)
    throws DataAccessPointException {

        TransferResultSet trsData = new TransferResultSet();
        StringTokenizer   Tokenizer;

        if (WTextRead == null) {
            try {
                WTextRead = new BufferedReader(new FileReader(sFileName));
            } catch (IOException e) {
                throw new DataAccessPointException(e.getMessage());
            }
        }

        String currentLine = "";
        String Token;

        try {
            while ((currentLine = WTextRead.readLine()) != null) {
                currentLine = currentLine.trim() + ";";
                Tokenizer   = new StringTokenizer(currentLine);

                try {
                    Token = Tokenizer.nextToken();
                } catch (NoSuchElementException NSE) {
                    continue;
                }

                if (Token == null) {
                    continue;
                }

                if (!Token.toUpperCase().equals("INSERT")) {
                    continue;
                }

                try {
                    if (!Tokenizer.nextToken().toUpperCase().equals("INTO")) {
                        throw new DataAccessPointException(
                            "Error in INSERT statement: no INTO found");
                    }

                    Token = Tokenizer.nextToken();

                    if (!Token.equals(tableName)) {
                        continue;
                    }

                    int    iParsedRows   = 0;
                    Vector vColumnNames  = new Vector();
                    Vector vColumnValues = new Vector();
                    Vector vColumnTypes  = new Vector();

                    while ((currentLine = WTextRead.readLine()) != null) {
                        currentLine = currentLine.trim();

                        boolean newLine = (currentLine.length() == 0);

                        if (newLine) {
                            int iColumnNb = 0;

                            iParsedRows++;

                            iColumnNb = vColumnNames.size();

                            String[] Names  = new String[iColumnNb + 1];
                            int[]    Types  = new int[iColumnNb + 1];
                            Object[] Values = new Object[iColumnNb + 1];

                            for (int Idx = 0; Idx < iColumnNb; Idx++) {
                                Names[Idx + 1] =
                                    (String) vColumnNames.elementAt(Idx);
                                Types[Idx + 1] =
                                    ((Integer) vColumnTypes.elementAt(
                                        Idx)).intValue();
                                Values[Idx + 1] =
                                    vColumnValues.elementAt(Idx);
                            }

                            try {
                                trsData.addRow(Names, Types, Values,
                                               iColumnNb);
                            } catch (Exception e) {
                                throw new DataAccessPointException(
                                    e.getMessage());
                            }

                            iColumnNb = 0;

                            vColumnNames.removeAllElements();
                            vColumnValues.removeAllElements();
                            vColumnTypes.removeAllElements();

                            continue;
                        }

                        Tokenizer = new StringTokenizer(currentLine);
                        Token     = Tokenizer.nextToken("=");

                        if (Token.equals("Number of Rows")) {
                            int iNbRows =
                                Integer.parseInt(Tokenizer.nextToken());

                            if (iNbRows != iParsedRows) {
                                throw new DataAccessPointException(
                                    "Number of parsed rows (" + iParsedRows
                                    + ") is different from the expected ("
                                    + iNbRows + ")");
                            }

                            return trsData;
                        }

                        if (Token.equals("column")) {
                            Token = Tokenizer.nextToken(" =");

                            vColumnNames.addElement(Token);
                        }

                        Token = Tokenizer.nextToken(" =");

                        if (Token.equals("datatype")) {
                            int iType;

                            Token = Tokenizer.nextToken(" =");

                            try {
                                iType = JDBCT.toInt(Token.toUpperCase());
                            } catch (Exception e) {
                                throw new DataAccessPointException(
                                    "Unknown type: " + Token);
                            }

                            vColumnTypes.addElement(new Integer(iType));
                        }

                        Token = Tokenizer.nextToken(" =");

                        if (Token.equals("value")) {
                            int iStart = currentLine.indexOf("value=") + 6;
                            String sValue =
                                currentLine.substring(iStart).trim();

                            if (sValue.indexOf("<null>") >= 0) {
                                vColumnValues.addElement(null);
                            } else {
                                int    i       = sValue.indexOf('\'') + 1;
                                String sbToken = sValue.substring(i);

                                i       = sbToken.lastIndexOf('\'');
                                sbToken = sbToken.substring(0, i);
                                Token   = sbToken;

                                vColumnValues.addElement(Token);
                            }
                        }
                    }
                } catch (IndexOutOfBoundsException IOBe) {
                    continue;
                }
            }
        } catch (IOException IOe) {
            throw new DataAccessPointException(IOe.getMessage());
        }

        return trsData;
    }
}
