/*
 * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0,
 * and the EPL 1.0 (https://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 */
package org.h2.table;

import java.util.ArrayList;

import org.h2.engine.SessionLocal;
import org.h2.index.Index;
import org.h2.index.IndexType;
import org.h2.index.MetaIndex;
import org.h2.message.DbException;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.schema.Schema;
import org.h2.util.StringUtils;
import org.h2.value.TypeInfo;
import org.h2.value.Value;
import org.h2.value.ValueNull;
import org.h2.value.ValueVarchar;
import org.h2.value.ValueVarcharIgnoreCase;

/**
 * This class is responsible to build the database meta data pseudo tables.
 */
public abstract class MetaTable extends Table {

    /**
     * The approximate number of rows of a meta table.
     */
    public static final long ROW_COUNT_APPROXIMATION = 1000;

    /**
     * The table type.
     */
    protected final int type;

    /**
     * The indexed column.
     */
    protected int indexColumn;

    /**
     * The index for this table.
     */
    protected MetaIndex metaIndex;

    /**
     * Create a new metadata table.
     *
     * @param schema the schema
     * @param id the object id
     * @param type the meta table type
     */
    protected MetaTable(Schema schema, int id, int type) {
        // tableName will be set later
        super(schema, id, null, true, true);
        this.type = type;
    }

    protected final void setMetaTableName(String upperName) {
        setObjectName(database.sysIdentifier(upperName));
    }

    /**
     * Creates a column with the specified name and character string data type.
     *
     * @param name
     *            the uppercase column name
     * @return the column
     */
    final Column column(String name) {
        return new Column(database.sysIdentifier(name),
                database.getSettings().caseInsensitiveIdentifiers ? TypeInfo.TYPE_VARCHAR_IGNORECASE
                        : TypeInfo.TYPE_VARCHAR);
    }

    /**
     * Creates a column with the specified name and data type.
     *
     * @param name
     *            the uppercase column name
     * @param type
     *            the data type
     * @return the column
     */
    protected final Column column(String name, TypeInfo type) {
        return new Column(database.sysIdentifier(name), type);
    }

    @Override
    public final String getCreateSQL() {
        return null;
    }

    @Override
    public final Index addIndex(SessionLocal session, String indexName, int indexId, IndexColumn[] cols,
            int uniqueColumnCount, IndexType indexType, boolean create, String indexComment) {
        throw DbException.getUnsupportedException("META");
    }

    /**
     * If needed, convert the identifier to lower case.
     *
     * @param s the identifier to convert
     * @return the converted identifier
     */
    protected final String identifier(String s) {
        if (database.getSettings().databaseToLower) {
            s = s == null ? null : StringUtils.toLowerEnglish(s);
        }
        return s;
    }

    /**
     * Checks index conditions.
     *
     * @param session the session
     * @param value the value
     * @param indexFrom the lower bound of value, or {@code null}
     * @param indexTo the higher bound of value, or {@code null}
     * @return whether row should be included into result
     */
    protected final boolean checkIndex(SessionLocal session, String value, Value indexFrom, Value indexTo) {
        if (value == null || (indexFrom == null && indexTo == null)) {
            return true;
        }
        Value v;
        if (database.getSettings().caseInsensitiveIdentifiers) {
            v = ValueVarcharIgnoreCase.get(value);
        } else {
            v = ValueVarchar.get(value);
        }
        if (indexFrom != null && session.compare(v, indexFrom) < 0) {
            return false;
        }
        if (indexTo != null && session.compare(v, indexTo) > 0) {
            return false;
        }
        return true;
    }

    /**
     * Check whether to hide the table. Tables are never hidden in the system
     * session.
     *
     * @param table the table
     * @param session the session
     * @return whether the table is hidden
     */
    protected final boolean hideTable(Table table, SessionLocal session) {
        return table.isHidden() && session != database.getSystemSession();
    }

    /**
     * Generate the data for the given metadata table using the given first and
     * last row filters.
     *
     * @param session the session
     * @param first the first row to return
     * @param last the last row to return
     * @return the generated rows
     */
    public abstract ArrayList<Row> generateRows(SessionLocal session, SearchRow first, SearchRow last);

    @Override
    public boolean isInsertable() {
        return false;
    }

    @Override
    public final void removeRow(SessionLocal session, Row row) {
        throw DbException.getUnsupportedException("META");
    }

    @Override
    public final void addRow(SessionLocal session, Row row) {
        throw DbException.getUnsupportedException("META");
    }

    @Override
    public final void removeChildrenAndResources(SessionLocal session) {
        throw DbException.getUnsupportedException("META");
    }

    @Override
    public final void close(SessionLocal session) {
        // nothing to do
    }

    /**
     * Add a row to a list.
     *
     * @param session the session
     * @param rows the original row list
     * @param stringsOrValues the values, or strings
     */
    protected final void add(SessionLocal session, ArrayList<Row> rows, Object... stringsOrValues) {
        Value[] values = new Value[stringsOrValues.length];
        for (int i = 0; i < stringsOrValues.length; i++) {
            Object s = stringsOrValues[i];
            Value v = s == null ? ValueNull.INSTANCE : s instanceof String ? ValueVarchar.get((String) s) : (Value) s;
            values[i] = columns[i].convert(session, v);
        }
        rows.add(Row.get(values, 1, rows.size()));
    }

    @Override
    public final void checkRename() {
        throw DbException.getUnsupportedException("META");
    }

    @Override
    public final void checkSupportAlter() {
        throw DbException.getUnsupportedException("META");
    }

    @Override
    public final long truncate(SessionLocal session) {
        throw DbException.getUnsupportedException("META");
    }

    @Override
    public long getRowCount(SessionLocal session) {
        throw DbException.getInternalError(toString());
    }

    @Override
    public boolean canGetRowCount(SessionLocal session) {
        return false;
    }

    @Override
    public final boolean canDrop() {
        return false;
    }

    @Override
    public final TableType getTableType() {
        return TableType.SYSTEM_TABLE;
    }

    @Override
    public final Index getScanIndex(SessionLocal session) {
        return new MetaIndex(this, IndexColumn.wrap(columns), true);
    }

    @Override
    public final ArrayList<Index> getIndexes() {
        ArrayList<Index> list = new ArrayList<>(2);
        if (metaIndex == null) {
            return list;
        }
        list.add(new MetaIndex(this, IndexColumn.wrap(columns), true));
        // TODO re-use the index
        list.add(metaIndex);
        return list;
    }

    @Override
    public long getRowCountApproximation(SessionLocal session) {
        return ROW_COUNT_APPROXIMATION;
    }

    @Override
    public final boolean isDeterministic() {
        return true;
    }

    @Override
    public final boolean canReference() {
        return false;
    }

}
