/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Other licenses:
 * -----------------------------------------------------------------------------
 * Commercial licenses for this work are available. These replace the above
 * Apache-2.0 license and offer limited warranties, support, maintenance, and
 * commercial database integrations.
 *
 * For more information, please visit: https://www.jooq.org/legal/licensing
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
package org.jooq.impl;

import static org.jooq.Clause.CONDITION;
import static org.jooq.Clause.CONDITION_COMPARISON;
import static org.jooq.Comparator.*;
import static org.jooq.SQLDialect.*;
import static org.jooq.impl.DSL.asterisk;
import static org.jooq.impl.DSL.exists;
import static org.jooq.impl.DSL.name;
import static org.jooq.impl.DSL.noCondition;
import static org.jooq.impl.DSL.notExists;
import static org.jooq.impl.DSL.row;
import static org.jooq.impl.DSL.select;
import static org.jooq.impl.QOM.Quantifier.ALL;
import static org.jooq.impl.QOM.Quantifier.ANY;
import static org.jooq.impl.SubqueryCharacteristics.PREDICAND;
import static org.jooq.impl.Tools.embeddedFieldsRow;
import static org.jooq.impl.Tools.fieldNames;
import static org.jooq.impl.Tools.fieldsByName;
import static org.jooq.impl.Tools.quantify;
import static org.jooq.impl.Tools.visitSubquery;
import static org.jooq.impl.Transformations.transformInConditionSubqueryWithLimitToDerivedTable;
import static org.jooq.impl.Transformations.subqueryWithLimit;

import java.util.Set;

import org.jooq.Clause;
import org.jooq.Comparator;
import org.jooq.Context;
import org.jooq.Name;
// ...
import org.jooq.QuantifiedSelect;
import org.jooq.QueryPartInternal;
import org.jooq.Record;
import org.jooq.RenderContext;
import org.jooq.Row;
import org.jooq.SQLDialect;
import org.jooq.Select;
import org.jooq.SelectOrderByStep;
import org.jooq.impl.QOM.Quantifier;
import org.jooq.impl.QOM.UNotYetImplemented;
import org.jooq.impl.QOM.UTransient;
import org.jooq.impl.Tools.BooleanDataKey;

/**
 * @author Lukas Eder
 */
final class RowSubqueryCondition extends AbstractCondition implements UNotYetImplemented {
    private static final Clause[]        CLAUSES                                    = { CONDITION, CONDITION_COMPARISON };
    private static final Set<SQLDialect> NO_SUPPORT_NATIVE                          = SQLDialect.supportedBy(CUBRID, DERBY, DUCKDB, FIREBIRD);
    private static final Set<SQLDialect> NO_SUPPORT_QUANTIFIED                      = SQLDialect.supportedBy(DERBY, DUCKDB, FIREBIRD, SQLITE);
    // See https://bugs.mysql.com/bug.php?id=103494
    private static final Set<SQLDialect> NO_SUPPORT_QUANTIFIED_OTHER_THAN_IN_NOT_IN = SQLDialect.supportedBy(MARIADB, MYSQL);






    private final Row                    left;
    private final Select<?>              right;
    private final QuantifiedSelect<?>    rightQuantified;
    private final Comparator             comparator;

    RowSubqueryCondition(Row left, Select<?> right, Comparator comparator) {
        this.left = left;
        this.right = right;
        this.rightQuantified = null;
        this.comparator = comparator;
    }

    RowSubqueryCondition(Row left, QuantifiedSelect<?> right, Comparator comparator) {
        this.left = left;
        this.right = null;
        this.rightQuantified = right;
        this.comparator = comparator;
    }

    @Override
    public final void accept(Context<?> ctx) {
        ctx.visit(delegate(ctx));
    }

    @Override // Avoid AbstractCondition implementation
    public final Clause[] clauses(Context<?> ctx) {
        return null;
    }

    private static final boolean inOrNotIn(Comparator comparator, Quantifier quantifier) {
        return comparator == EQUALS && quantifier == ANY
            || comparator == NOT_EQUALS && quantifier == ALL;
    }

    private final QueryPartInternal delegate(Context<?> ctx) {

        // [#3505] TODO: Emulate this where it is not supported
        if (rightQuantified != null) {

            // TODO: Handle all cases, not just the query one
            QuantifiedSelectImpl<?> q = (QuantifiedSelectImpl<?>) rightQuantified;
            boolean inOrNotIn = inOrNotIn(comparator, q.quantifier);

            if (NO_SUPPORT_QUANTIFIED.contains(ctx.dialect()) ||
                NO_SUPPORT_QUANTIFIED_OTHER_THAN_IN_NOT_IN.contains(ctx.dialect()) && !inOrNotIn) {

                switch (comparator) {
                    case EQUALS:
                    case NOT_EQUALS: {
                        if (inOrNotIn)
                            return new RowSubqueryCondition(left, q.query, comparator == EQUALS ? IN : NOT_IN);
                        else
                            return emulationUsingExists(ctx, left, q.query, comparator == EQUALS ? NOT_EQUALS : EQUALS, comparator == EQUALS);
                    }

                    case GREATER:
                    case GREATER_OR_EQUAL:
                    case LESS:
                    case LESS_OR_EQUAL:
                    default:
                        return emulationUsingExists(ctx, left, q.query, q.quantifier == ALL ? comparator.inverse() : comparator, q.quantifier == ALL);
                }
            }
            else
                return new Native();
        }









        else if (NO_SUPPORT_NATIVE.contains(ctx.dialect()))
            return emulationUsingExists(ctx, left, right,
                   comparator == GREATER
                || comparator == GREATER_OR_EQUAL
                || comparator == LESS
                || comparator == LESS_OR_EQUAL
                || comparator == IS_DISTINCT_FROM
                || comparator == IS_NOT_DISTINCT_FROM
                 ? comparator
                 : EQUALS,
                   comparator == NOT_IN
                || comparator == NOT_EQUALS
            );
        else
            return new Native();
    }

    private static final QueryPartInternal emulationUsingExists(Context<?> ctx, Row row, Select<?> select, Comparator comparator, boolean notExists) {
        Select<Record> subselect = emulatedSubselect(ctx, row, select, comparator);
        return (QueryPartInternal) (notExists ? notExists(subselect) : exists(subselect));
    }

    private static final SelectOrderByStep<Record> emulatedSubselect(Context<?> ctx, Row row, Select<?> s, Comparator c) {
        RenderContext render = ctx instanceof RenderContext r ? r : null;
        Row l = embeddedFieldsRow(row);
        Name table = name(render == null ? "t" : render.nextAlias());
        Name[] names = fieldNames(l.size());

        return select()
              .from(new AliasedSelect<>(s, true, true, false, names).as(table))
              .where(c == null
                  ? noCondition()
                  : new RowCondition(l, row(fieldsByName(table, names)), c));
    }

    private class Native extends AbstractCondition implements UTransient {

        @Override
        public final void accept(Context<?> ctx) {
            SelectQueryImpl<?> s;

            if ((comparator == IN || comparator == NOT_IN)
                    && right != null
                    && (s = subqueryWithLimit(right)) != null
                    && Transformations.NO_SUPPORT_IN_LIMIT.contains(ctx.dialect())) {
                ctx.visit(new RowSubqueryCondition(left, select(asterisk()).from(s.asTable("t")), comparator));
            }
            else if ((comparator == EQUALS || comparator == NOT_EQUALS)
                    && rightQuantified != null
                    && (s = subqueryWithLimit(rightQuantified)) != null
                    && Transformations.NO_SUPPORT_IN_LIMIT.contains(ctx.dialect())) {
                ctx.visit(new RowSubqueryCondition(
                    left,
                    quantify(
                        ((QuantifiedSelectImpl<?>) rightQuantified).quantifier,
                        select(asterisk()).from(s.asTable("t"))
                    ),
                    comparator
                ));
            }
            else
                accept0(ctx);
        }

        final void accept0(Context<?> ctx) {
            switch (ctx.family()) {















                default:
                    ctx.visit(left)
                       .sql(' ')
                       .visit(comparator.toKeyword())
                       .sql(' ');

                    if (rightQuantified == null) {

                        // Some databases need extra parentheses around the RHS
                        boolean extraParentheses = false ;

                        ctx.sql(extraParentheses ? "((" : "(")
                           .data(BooleanDataKey.DATA_ROW_VALUE_EXPRESSION_PREDICATE_SUBQUERY, true, c -> visitSubquery(c, right, PREDICAND, false))
                           .sql(extraParentheses ? "))" : ")");
                    }

                    // [#2054] Quantified row value expression comparison predicates shouldn't have parentheses before ANY or ALL
                    else
                        ctx.data(BooleanDataKey.DATA_ROW_VALUE_EXPRESSION_PREDICATE_SUBQUERY, true, c -> c.visit(rightQuantified));

                    break;
            }
        }

        @Override
        public final Clause[] clauses(Context<?> ctx) {
            return CLAUSES;
        }
    }
}
