/*
 * Copyright (c) 1998, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.internal.expressions;

import java.io.*;
import java.util.*;

import org.eclipse.persistence.expressions.*;
import org.eclipse.persistence.internal.helper.DatabaseTable;
import org.eclipse.persistence.internal.queries.ReportItem;

/**
 * Allow a table expression to be created on a sub-select to define a sub-select in the from clause.
 */
public class FromSubSelectExpression extends TableExpression {
    /**  Allows a sub-select to be defined from clause. */
    protected SubSelectExpression subSelect;

    public FromSubSelectExpression() {
        super();
    }

    public FromSubSelectExpression(SubSelectExpression subSelect) {
        super();
        this.subSelect = subSelect;
        this.table = new SubSelectDatabaseTable(subSelect);
    }

    /**
     * INTERNAL:
     * Return if the expression is equal to the other.
     * This is used to allow dynamic expression's SQL to be cached.
     * From sub-selects are too complex to cache, so use identity.
     */
    @Override
    public boolean equals(Object object) {
        return this == object;
    }

    /**
     * INTERNAL:
     * Compute a consistent hash-code for the expression.
     */
    @Override
    public int computeHashCode() {
        return System.identityHashCode(this);
    }

    /**
     * INTERNAL:
     * Used for debug printing.
     */
    @Override
    public String descriptionOfNodeType() {
        return "FromSubSelect";
    }

    /**
     * This is used by sub-selects in the from clause to define a virtual table,
     * 'get' allows one of the sub-selected attributes to be aliased without using the field name.
     */
    public Expression get(String alias) {
        FromAliasExpression aliasExpression = new FromAliasExpression(alias, this);
        return aliasExpression;
    }

    /**
     * INTERNAL:
     * Normalize the expression into a printable structure.
     * Any joins must be added to form a new root.
     */
    @Override
    public Expression normalize(ExpressionNormalizer normalizer) {
        if (this.subSelect != null) {
            // Each item that is a function must have an alias defined for it.
            for (ReportItem item : this.subSelect.getSubQuery().getItems()) {
                if (!item.getAttributeExpression().isQueryKeyExpression()) {
                    item.setAttributeExpression(item.getAttributeExpression().as(item.getName()));
                }
            }
            // Need to force the sub-slect to normalize instead of deferring.
            this.subSelect.normalizeSubSelect(normalizer, normalizer.getClonedExpressions());
        }
        return super.normalize(normalizer);
    }

    /**
     * INTERNAL:
     * Also iterate over the sub-select if present.
     */
    @Override
    public void iterateOn(ExpressionIterator iterator) {
        super.iterateOn(iterator);
        if (this.subSelect != null) {
            this.subSelect.iterateOn(iterator);
        }
    }

    /**
     * INTERNAL:
     * Also copy over the sub-select if present.
     */
    @Override
    protected void postCopyIn(Map alreadyDone) {
        super.postCopyIn(alreadyDone);
        if (this.subSelect != null) {
            this.subSelect = (SubSelectExpression)this.subSelect.copiedVersionFrom(alreadyDone);
            this.table = new SubSelectDatabaseTable(subSelect);
        }
    }

    /**
     * INTERNAL:
     * This expression is built on a different base than the one we want. Rebuild it and
     * return the root of the new tree
     */
    @Override
    public Expression rebuildOn(Expression newBase) {
        Expression newLocalBase = getBaseExpression().rebuildOn(newBase);
        return newLocalBase.getAlias(this.subSelect.rebuildOn(newBase));
    }

    /**
     * INTERNAL:
     * Rebuild myself against the base, with the values of parameters supplied by the context
     * expression. This is used for transforming a standalone expression (e.g. the join criteria of a mapping)
     * into part of some larger expression. You normally would not call this directly, instead calling twist
     * See the comment there for more details"
     */
    @Override
    public Expression twistedForBaseAndContext(Expression newBase, Expression context, Expression oldBase) {
        Expression twistedBase = getBaseExpression().twistedForBaseAndContext(newBase, context, oldBase);
        return twistedBase.getAlias(this.subSelect.twistedForBaseAndContext(newBase, context, oldBase));
    }

    /**
     * INTERNAL:
     * Used to print a debug form of the expression tree.
     */
    @Override
    public void writeDescriptionOn(BufferedWriter writer) throws IOException {
        this.subSelect.writeDescriptionOn(writer);
        writer.write(tableAliasesDescription());
    }

    /**
     * INTERNAL:
     * Any table should use the sub-selects alias.
     */
    @Override
    public DatabaseTable aliasForTable(DatabaseTable table) {
        return super.aliasForTable(getTable());
    }


    public SubSelectExpression getSubSelect() {
        return subSelect;
    }

    public void setSubSelect(SubSelectExpression subSelect) {
        this.subSelect = subSelect;
        this.table = new SubSelectDatabaseTable(subSelect);
    }
}
