/* Copyright (c) 2001-2024, 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() {

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

    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=" + helper.getSupportedTypes().get(
                    Integer.valueOf(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 = "";
    }

    static 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.isEmpty()
                || 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.equalsIgnoreCase("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) {

        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().contains("CONSTRAINT")) {
                translatedLine += CreateLine.substring(
                    currentPos,
                    currentPos + cDef.len) + ",";
                currentPos += cDef.len + 1;

                colnum++;
                continue;
            }

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

            try {
                Integer inttype = Integer.valueOf(
                    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.equalsIgnoreCase("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.equalsIgnoreCase("INSERT")) {
                    try {
                        if (!Tokenizer.nextToken().equalsIgnoreCase("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.equalsIgnoreCase("ALTER")) {
                    try {
                        if (!Tokenizer.nextToken().equalsIgnoreCase("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.equalsIgnoreCase("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()).equalsIgnoreCase(
                                "INDEX")) {}

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

                        while ((Token = Tokenizer.nextToken(
                                " (")).equalsIgnoreCase("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().equalsIgnoreCase("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.equalsIgnoreCase("INSERT")) {
                    continue;
                }

                try {
                    if (!Tokenizer.nextToken().equalsIgnoreCase("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.isEmpty();

                        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(Integer.valueOf(iType));
                        }

                        Token = Tokenizer.nextToken(" =");

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

                            if (sValue.contains("<null>")) {
                                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;
    }
}
