/* 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;

import org.hsqldb.lib.FileUtil;
import org.hsqldb.lib.StringConverter;
import org.hsqldb.persist.TextCache;

// tony_lai@users 20020820 - patch 595099 - user define PK name

/**
 * Subclass of Table to handle TEXT data source. <p>
 *
 * Extends Table to provide the notion of an SQL base table object whose
 * data is read from and written to a text format data file.
 *
 * @author sqlbob@users (RMP)
 * @version    1.8.0
 */
class TextTable extends org.hsqldb.Table {

    private String  dataSource  = "";
    private boolean isReversed  = false;
    private boolean isConnected = false;

    /**
     *  Constructs a new TextTable from the given arguments.
     *
     * @param  db the owning database
     * @param  name the table's HsqlName
     * @param  type (normal or temp text table)
     * @param  sessionid the id of the owning session (for temp table)
     * @exception  HsqlException  Description of the Exception
     */
    TextTable(Database db, HsqlNameManager.HsqlName name,
              int type) throws HsqlException {
        super(db, name, type);
    }

    /**
     * common handling for all errors during <code>connect</code>
     */
    private void onConnectError(Session session) {

        if (cache != null) {
            try {
                cache.close(false);
            } catch (HsqlException ex) {}
        }

        cache = null;

        clearAllRows(session);
    }

    public boolean isConnected() {
        return isConnected;
    }

    /**
     * connects to the data source
     */
    public void connect(Session session) throws HsqlException {
        connect(session, isReadOnly);
    }

    /**
     * connects to the data source
     */
    private void connect(Session session,
                         boolean withReadOnlyData) throws HsqlException {

        // Open new cache:
        if ((dataSource.length() == 0) || isConnected) {

            // nothing to do
            return;
        }

        try {
            cache = database.logger.openTextCache(this, dataSource,
                                                  withReadOnlyData,
                                                  isReversed);

            // read and insert all the rows from the source file
            CachedRow row     = null;
            int       nextpos = 0;

            if (((TextCache) cache).ignoreFirst) {
                nextpos += ((TextCache) cache).readHeaderLine();
            }

            while (true) {
                row = (CachedRow) rowStore.get(nextpos);

                if (row == null) {
                    break;
                }

                nextpos = row.getPos() + row.getStorageSize();

                row.setNewNodes();
                insertFromTextSource(row);
            }
        } catch (HsqlException e) {
            int linenumber = cache == null ? 0
                                           : ((TextCache) cache)
                                               .getLineNumber();

            onConnectError(session);

            // everything is in order here.
            // At this point table should either have a valid (old) data
            // source and cache or have an empty source and null cache.
            throw Trace.error(Trace.TEXT_FILE, new Object[] {
                new Integer(linenumber), e.getMessage()
            });
        } catch (java.lang.RuntimeException t) {
            int linenumber = cache == null ? 0
                                           : ((TextCache) cache)
                                               .getLineNumber();

            onConnectError(session);

            throw Trace.error(Trace.TEXT_FILE, new Object[] {
                new Integer(linenumber),
                t.getClass().getName() + ": " + t.getMessage()
            });
        }

        isConnected = true;

        setIsReadOnly(withReadOnlyData);
    }

    /**
     * disconnects from the data source
     */
    public void disconnect(Session session) throws HsqlException {

        // Close old cache:
        database.logger.closeTextCache(this);

        cache = null;

        clearAllRows(session);

        isConnected = false;
    }

    /**
     * This method does some of the work involved with managing the creation
     * and openning of the cache, the rest is done in Log.java and
     * TextCache.java.
     *
     * Better clarification of the role of the methods is needed.
     */
    private void openCache(Session session, String dataSourceNew,
                           boolean isReversedNew,
                           boolean isReadOnlyNew) throws HsqlException {

        if (dataSourceNew == null) {
            dataSourceNew = "";
        }

        disconnect(session);

        dataSource = dataSourceNew;
        isReversed = (isReversedNew && dataSource.length() > 0);

        connect(session, isReadOnlyNew);
    }

    /**
     * High level command to assign a data source to the table definition.
     * Reassigns only if the data source or direction has changed.
     */
    protected void setDataSource(Session session, String dataSourceNew,
                                 boolean isReversedNew,
                                 boolean newFile) throws HsqlException {

        if (getTableType() == Table.TEMP_TEXT_TABLE) {
            ;
        } else {
            session.checkAdmin();
        }

        dataSourceNew = dataSourceNew.trim();

        if (newFile && FileUtil.getDefaultInstance().exists(dataSourceNew)) {
            throw Trace.error(Trace.TEXT_SOURCE_EXISTS, dataSourceNew);
        }

        //-- Open if descending, direction changed, file changed, or not connected currently
        if (isReversedNew || (isReversedNew != isReversed)
                || !dataSource.equals(dataSourceNew) || !isConnected) {
            openCache(session, dataSourceNew, isReversedNew, isReadOnly);
        }

        if (isReversed) {
            setIsReadOnly(true);
        }
    }

    protected String getDataSource() {
        return dataSource;
    }

    protected boolean isDescDataSource() {
        return isReversed;
    }

    public void setHeader(String header) throws HsqlException {

        if (cache != null && ((TextCache) cache).ignoreFirst) {
            ((TextCache) cache).setHeader(header);

            return;
        }

        throw Trace.error(Trace.TEXT_TABLE_HEADER);
    }

    public String getHeader() {

        String header = cache == null ? null
                                      : ((TextCache) cache).getHeader();

        return header == null ? null
                              : StringConverter.toQuotedString(header, '\"',
                              true);
    }

    /**
     * Used by INSERT, DELETE, UPDATE operations. This class will return
     * a more appropriate message when there is no data source.
     */
    void checkDataReadOnly() throws HsqlException {

        if (dataSource.length() == 0) {
            throw Trace.error(Trace.UNKNOWN_DATA_SOURCE);
        }

        if (isDataReadOnly()) {
            throw Trace.error(Trace.DATA_IS_READONLY);
        }
    }

    public boolean isDataReadOnly() {
        return !isConnected() || super.isDataReadOnly();
    }

    void setDataReadOnly(boolean value) throws HsqlException {

        if (isReversed && value == false) {
            throw Trace.error(Trace.DATA_IS_READONLY);
        }

        openCache(null, dataSource, isReversed, value);
        setIsReadOnly(value);
    }

    boolean isIndexCached() {
        return false;
    }

    protected Table duplicate() throws HsqlException {
        return new TextTable(database, tableName, getTableType());
    }

    void drop() throws HsqlException {
        openCache(null, "", false, false);
    }

    void setIndexRoots(String s) throws HsqlException {

        // do nothing
    }
}
