/*
 * ModeShape (http://www.modeshape.org)
 *
 * 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
 *
 *       http://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.
 */
package org.modeshape.jcr.index.elasticsearch;

import org.modeshape.common.collection.Problems;
import org.modeshape.jcr.ExecutionContext;
import org.modeshape.jcr.NodeTypes;
import org.modeshape.jcr.api.index.IndexDefinition;
import org.modeshape.jcr.api.query.qom.ChildCount;
import org.modeshape.jcr.cache.change.ChangeSetAdapter;
import org.modeshape.jcr.index.elasticsearch.client.EsClient;
import org.modeshape.jcr.query.QueryContext;
import org.modeshape.jcr.query.model.FullTextSearch;
import org.modeshape.jcr.query.model.Or;
import org.modeshape.jcr.spi.index.IndexCostCalculator;
import org.modeshape.jcr.spi.index.provider.IndexProvider;
import org.modeshape.jcr.spi.index.provider.IndexUsage;
import org.modeshape.jcr.spi.index.provider.ManagedIndexBuilder;

import javax.jcr.RepositoryException;
import javax.jcr.query.qom.ChildNodeJoinCondition;
import javax.jcr.query.qom.DescendantNodeJoinCondition;
import javax.jcr.query.qom.DynamicOperand;
import javax.jcr.query.qom.JoinCondition;
import java.util.Collection;

/**
 * @author kulikov
 */
public class EsIndexProvider extends IndexProvider {

    private String host = "localhost";
    private int port = 9200;
    private EsClient client;

    @Override
    protected void doInitialize() throws RepositoryException {
        logger().debug("Elasticsearch index provider for repository '{0}' "
                + "is trying to connect to cluster", getRepositoryName());
        client = new EsClient(host, port);
    }

    /**
     * Gets ES instance address.
     *
     * @return ip address or domain name.
     */
    public String getHost() {
        return host;
    }

    /**
     * Assigns ES instance address.
     *
     * @param host ip address or domain name.
     */
    public void setHost(String host) {
        this.host = host;
    }

    /**
     * Gets ES instance port number.
     *
     * @return port number
     */
    public int getPort() {
        return port;
    }

    /**
     * Gets ES instance port number.
     *
     * @return port number
     */
    public void setPort(int port) {
        this.port = port;
    }

    @Override
    protected void postShutdown() {
        logger().debug("Shutting down the elasticsearch index provider '{0}' in repository '{1}'", getName(), getRepositoryName());
    }

    @Override
    protected int getCostEstimate() {
        return IndexCostCalculator.Costs.REMOTE;
    }

    @Override
    public void validateProposedIndex(ExecutionContext context,
                                      IndexDefinition defn,
                                      NodeTypes.Supplier nodeTypeSupplier,
                                      Problems problems) {
        // first perform some custom validations
    }

    @Override
    protected ManagedIndexBuilder getIndexBuilder(IndexDefinition defn,
                                                  String workspaceName,
                                                  NodeTypes.Supplier nodeTypesSupplier,
                                                  ChangeSetAdapter.NodeTypePredicate matcher) {
        return EsManagedIndexBuilder.create(client, context(), defn, nodeTypesSupplier, workspaceName, matcher);
    }

    @Override
    protected IndexUsage evaluateUsage(QueryContext context, final IndexCostCalculator calculator, final IndexDefinition defn) {
        return new IndexUsage(context, calculator, defn) {
            @Override
            protected boolean applies(ChildCount operand) {
                // nothing to do about this...
                return false;
            }

            @Override
            protected boolean applies(DynamicOperand operand) {
                if (IndexDefinition.IndexKind.TEXT == defn.getKind() && !(operand instanceof FullTextSearch)) {
                    // text indexes only support FTS operands...
                    return false;
                }
                return super.applies(operand);
            }

            @Override
            protected boolean indexAppliesTo(Or or) {
                boolean appliesToConstraints = super.indexAppliesTo(or);
                if (!appliesToConstraints) {
                    return false;
                }
                Collection<JoinCondition> joinConditions = calculator.joinConditions();
                if (joinConditions.isEmpty()) {
                    return true;
                }
                for (JoinCondition joinCondition : joinConditions) {
                    if (joinCondition instanceof ChildNodeJoinCondition || joinCondition instanceof DescendantNodeJoinCondition) {
                        // the index can't handle OUTER JOINS with OR criteria (see https://issues.jboss.org/browse/MODE-2054)
                        // so reject it, making the query engine fallback to the default behavior which works
                        return false;
                    }
                }
                return true;
            }
        };
    }
}
