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

import org.hsqldb.HsqlNameManager.HsqlName;
import org.hsqldb.HsqlNameManager.SimpleName;
import org.hsqldb.ParserDQL.CompileContext;
import org.hsqldb.RangeVariable.RangeIteratorMain;
import org.hsqldb.index.Index;
import org.hsqldb.lib.HashMap;
import org.hsqldb.lib.HashSet;
import org.hsqldb.lib.HsqlArrayList;
import org.hsqldb.lib.OrderedHashSet;
import org.hsqldb.map.ValuePool;

/**
 * Metadata for range joined variables
 *
 * @author Fred Toussi (fredt@users dot sourceforge.net)
 * @version 2.4.1
 * @since 1.9.0
 */
public class RangeVariableJoined extends RangeVariable {

    RangeVariable[] rangeArray;

    public RangeVariableJoined(
            Table table,
            SimpleName alias,
            OrderedHashSet<String> columnList,
            SimpleName[] columnNameList,
            CompileContext compileContext) {
        super(table, alias, columnList, columnNameList, compileContext);

        setParameters();
    }

    private void setParameters() {

        namedJoinColumnExpressions = new HashMap<>();

        QuerySpecification qs =
            (QuerySpecification) this.rangeTable.getQueryExpression();

        this.rangeArray = qs.rangeVariables;

        for (int i = 0; i < rangeArray.length; i++) {
            if (rangeArray[i].isLeftJoin) {
                hasLeftJoin = true;
            }

            if (rangeArray[i].isRightJoin) {
                hasRightJoin = true;
            }

            if (rangeArray[i].isLateral) {
                hasLateral = true;
            }

            if (rangeArray[i].namedJoinColumnExpressions != null) {
                namedJoinColumnExpressions.putAll(
                    rangeArray[i].namedJoinColumnExpressions);
            }
        }
    }

    public RangeVariable[] getBaseRangeVariables() {
        return rangeArray;
    }

    public void setRangeTableVariables() {
        super.setRangeTableVariables();
    }

    public void setJoinType(boolean isLeft, boolean isRight) {
        super.setJoinType(isLeft, isRight);
    }

    public void addNamedJoinColumns(OrderedHashSet<String> columns) {
        super.addNamedJoinColumns(columns);
    }

    public void addColumn(int columnIndex) {
        super.addColumn(columnIndex);
    }

    public void addAllColumns() {
        super.addAllColumns();
    }

    public void addNamedJoinColumnExpression(
            String name,
            Expression e,
            int position) {
        super.addNamedJoinColumnExpression(name, e, position);
    }

    public ExpressionColumn getColumnExpression(String name) {
        return null;
    }

    public Table getTable() {
        return super.getTable();
    }

    public boolean hasSingleIndexCondition() {
        return super.hasSingleIndexCondition();
    }

    public boolean setDistinctColumnsOnIndex(int[] colMap) {
        return super.setDistinctColumnsOnIndex(colMap);
    }

    /**
     * Used for sort
     */
    public Index getSortIndex() {
        return super.getSortIndex();
    }

    /**
     * Used for sort
     */
    public boolean setSortIndex(Index index, boolean reversed) {
        return super.setSortIndex(index, reversed);
    }

    public boolean reverseOrder() {
        return super.reverseOrder();
    }

    public OrderedHashSet<HsqlName> getColumnNames() {
        return super.getColumnNames();
    }

    public OrderedHashSet<String> getUniqueColumnNameSet() {
        return super.getUniqueColumnNameSet();
    }

    public int findColumn(
            String schemaName,
            String tableName,
            String columnName) {

        if (tableAlias != null) {
            return super.findColumn(schemaName, tableName, columnName);
        }

        int count    = 0;
        int colIndex = -1;

        // this covers named column reference without tableName
        if (tableName == null
                && namedJoinColumnExpressions.containsKey(columnName)) {
            return super.findColumn(null, null, columnName);
        }

        for (int i = 0; i < rangeArray.length; i++) {
            RangeVariable currentRange = rangeArray[i];

            colIndex = currentRange.findColumn(
                schemaName,
                tableName,
                columnName);

            if (currentRange.namedJoinColumnCheck == null) {
                if (colIndex > -1) {
                    count += colIndex;
                } else {
                    count += currentRange.rangeTable.getColumnCount();
                }
            } else {
                int limit = colIndex > -1
                            ? colIndex
                            : currentRange.namedJoinColumnCheck.length;

                for (int j = 0; j < limit; j++) {
                    if (!currentRange.namedJoinColumnCheck[j]) {
                        count++;
                    }
                }
            }

            if (colIndex > -1) {
                break;
            }
        }

        if (colIndex > -1) {
            if (namedJoinColumnExpressions != null) {
                count += namedJoinColumnExpressions.size();
            }

            return count;
        }

        return -1;
    }

    public SimpleName getColumnAlias(int i) {
        return super.getColumnAlias(i);
    }

    public boolean hasColumnAlias() {
        return super.hasColumnAlias();
    }

    public SimpleName getTableAlias() {
        return super.getTableAlias();
    }

    public RangeVariable getRangeForTableName(String name) {

        if (tableAlias != null) {
            return super.getRangeForTableName(name);
        }

        for (int i = 0; i < rangeArray.length; i++) {
            RangeVariable range = rangeArray[i].getRangeForTableName(name);

            if (range != null) {
                return range;
            }
        }

        return null;
    }

    /**
     * Add all columns to a list of expressions
     */
    public void addTableColumns(HsqlArrayList<Expression> exprList) {
        super.addTableColumns(exprList);
    }

    /**
     * Add all columns to a list of expressions
     */
    public int addTableColumns(
            HsqlArrayList<Expression> exprList,
            int position,
            HashSet<String> exclude) {
        return super.addTableColumns(exprList, position, exclude);
    }

    public void addTableColumns(
            RangeVariable subRange,
            Expression expression,
            HashSet<String> exclude) {

        int index = getFirstColumnIndex(subRange);

        addTableColumns(
            expression,
            index,
            subRange.rangeTable.getColumnCount(),
            exclude);
    }

    protected int getFirstColumnIndex(RangeVariable subRange) {

        if (subRange == this) {
            return 0;
        }

        int count = 0;

        for (int i = 0; i < rangeArray.length; i++) {
            int index = rangeArray[i].getFirstColumnIndex(subRange);

            if (index == -1) {
                count += rangeArray[i].rangeTable.getColumnCount();
            } else {
                return count + index;
            }
        }

        return -1;
    }

    /**
     * Removes reference to Index to avoid possible memory leaks after alter
     * table or drop index
     */
    public void setForCheckConstraint() {
        super.setForCheckConstraint();
    }

    /**
     * used before condition processing
     */
    public Expression getJoinCondition() {
        return super.getJoinCondition();
    }

    public void addJoinCondition(Expression e) {
        super.addJoinCondition(e);
    }

    public void resetConditions() {
        super.resetConditions();
    }

    public void replaceColumnReference(
            RangeVariable range,
            Expression[] list) {}

    public void replaceRangeVariables(
            RangeVariable[] ranges,
            RangeVariable[] newRanges) {
        super.replaceRangeVariables(ranges, newRanges);
    }

    public void resolveRangeTable(
            Session session,
            RangeGroup rangeGroup,
            RangeGroup[] rangeGroups) {
        super.resolveRangeTable(session, rangeGroup, rangeGroups);
    }

    /**
     * Retrieves a String representation of this object. <p>
     *
     * The returned String describes this object's table, alias
     * access mode, index, join mode, Start, End and And conditions.
     *
     * @return a String representation of this object
     */
    public String describe(Session session, int blanks) {

        RangeVariableConditions[] conditionsArray = joinConditions;
        StringBuilder             sb              = new StringBuilder();
        String                    b = ValuePool.spaceString.substring(
            0,
            blanks);
        String                    temp            = "INNER";

        if (isLeftJoin) {
            temp = "LEFT OUTER";

            if (isRightJoin) {
                temp = "FULL";
            }
        } else if (isRightJoin) {
            temp = "RIGHT OUTER";
        }

        sb.append(b)
          .append("join type=")
          .append(temp)
          .append("\n")
          .append(b)
          .append("table=")
          .append(rangeTable.getName().name)
          .append("\n");

        if (tableAlias != null) {
            sb.append(b).append("alias=").append(tableAlias.name).append("\n");
        }

        boolean fullScan = !conditionsArray[0].hasIndexCondition();

        sb.append(b).append("access=").append(fullScan
                ? "FULL SCAN"
                : "INDEX PRED").append("\n");

        for (int i = 0; i < conditionsArray.length; i++) {
            RangeVariableConditions conditions = this.joinConditions[i];

            if (i > 0) {
                sb.append(b).append("OR condition = [");
            } else {
                sb.append(b).append("condition = [");
            }

            sb.append(conditions.describe(session, blanks + 2))
              .append(b)
              .append("]\n");
        }

        return sb.toString();
    }

    public RangeIteratorMain getIterator(Session session) {
        return super.getIterator(session);
    }
}
