/*-
 * #%L
 * JSQLParser library
 * %%
 * Copyright (C) 2004 - 2020 JSQLParser
 * %%
 * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
 * #L%
 */
package net.sf.jsqlparser.util.validation.metadata;

import java.sql.Connection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiPredicate;
import java.util.function.UnaryOperator;

/**
 * Adapter class always throwing {@link UnsupportedOperationException} for all
 * exists - methods.
 *
 * @author gitmotte
 *
 */
public abstract class AbstractDatabaseMetaDataCapability implements DatabaseMetaDataValidation {

    protected Connection connection;
    protected boolean cacheResults;
    protected Map<Named, Boolean> results = new HashMap<>();
    protected UnaryOperator<String> namesLookup = NamesLookup.NO_TRANSFORMATION;

    /**
     * With caching enabled - see {@link #isCacheResults()}
     * 
     * @param connection
     * @param namesLookup - see {@link NamesLookup}
     * @see #AbstractDatabaseMetaDataCapability(Connection, UnaryOperator, boolean)
     */
    public AbstractDatabaseMetaDataCapability(Connection connection, UnaryOperator<String> namesLookup) {
        this(connection, namesLookup, true);
    }

    /**
     * @param connection
     * @param namesLookup  - see {@link NamesLookup}
     * @param cacheResults - whether the results should be cached for later lookups
     * @see #AbstractDatabaseMetaDataCapability(Connection, UnaryOperator)
     */
    public AbstractDatabaseMetaDataCapability(Connection connection, UnaryOperator<String> namesLookup,
            boolean cacheResults) {
        this.connection = connection;
        this.namesLookup = namesLookup;
        this.cacheResults = cacheResults;
    }

    public UnaryOperator<String> getNamesLookup() {
        return namesLookup;
    }

    public Connection getConnection() {
        return connection;
    }

    public boolean isCacheResults() {
        return cacheResults;
    }

    public AbstractDatabaseMetaDataCapability clearCache() {
        this.results.clear();
        return this;
    }

    @Override
    @SuppressWarnings({"PMD.CyclomaticComplexity"})
    public final boolean exists(Named named) {
        Objects.requireNonNull(named);

        named.setFqnLookup(getNamesLookup().apply(named.getFqn()));
        named.setAliasLookup(getNamesLookup().apply(named.getAlias()));

        switch (named.getNamedObject()) {
        case table:
            return cache(named, this::tableExists);
        case column:
            return cache(named, this::columnExists);
        case schema:
            return cache(named, this::schemaExists);
        case index:
            return cache(named, this::indexExists);
        case database:
            return cache(named, this::databaseExists);
        case constraint:
        case uniqueConstraint:
            return cache(named, this::constraintExists);
        case view:
            return cache(named, this::viewExists);
        case procedure:
            return cache(named, this::procedureExists);
        case user:
            return cache(named, this::userExists);
        case role:
            return cache(named, this::roleExists);
        default:
        }
        throw new UnsupportedOperationException(
                named.getFqn() + ": evaluation of " + named.getNamedObject() + "-name not implemented.");
    }

    protected boolean cache(Named named, BiPredicate<Map<Named, Boolean>, Named> fn) {
        Map<Named, Boolean> m = Collections.unmodifiableMap(results);
        if (cacheResults) {
            return results.computeIfAbsent(named, k -> fn.test(m, k));
        } else {
            return fn.test(m, named);
        }
    }

    protected boolean roleExists(Map<Named, Boolean> results, Named name) {
        throw unsupported(name);
    }

    protected boolean userExists(Map<Named, Boolean> results, Named name) {
        throw unsupported(name);
    }

    protected boolean procedureExists(Map<Named, Boolean> results, Named name) {
        throw unsupported(name);
    }

    protected boolean databaseExists(Map<Named, Boolean> results, Named name) {
        throw unsupported(name);
    }

    protected boolean constraintExists(Map<Named, Boolean> results, Named name) {
        throw unsupported(name);
    }

    protected boolean viewExists(Map<Named, Boolean> results, Named name) {
        throw unsupported(name);
    }

    protected boolean indexExists(Map<Named, Boolean> results, Named name) {
        throw unsupported(name);
    }

    protected boolean schemaExists(Map<Named, Boolean> results, Named name) {
        throw unsupported(name);
    }

    protected boolean columnExists(Map<Named, Boolean> results, Named name) {
        throw unsupported(name);
    }

    protected boolean tableExists(Map<Named, Boolean> results, Named name) {
        throw unsupported(name);
    }

    protected UnsupportedOperationException unsupported(Named name) {
        return new UnsupportedOperationException(
                name.getFqn() + ": evaluation of " + name.getNamedObject() + "-name not supported.");
    }

}
