package datawave.query.tables;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.accumulo.core.client.AccumuloClient;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.Value;
import org.apache.accumulo.core.security.Authorizations;
import org.apache.log4j.Logger;

import datawave.query.QueryParameters;
import datawave.query.planner.QueryPlanner;
import datawave.query.tables.chunk.Chunker;
import datawave.webservice.query.Query;
import datawave.webservice.query.QueryImpl.Parameter;
import datawave.webservice.query.configuration.GenericQueryConfiguration;

public class PartitionedQueryLogic extends ShardQueryLogic {
    protected static final Logger log = Logger.getLogger(PartitionedQueryLogic.class);

    private AccumuloClient client;
    private Query settings;
    private Set<Authorizations> auths;

    private Chunker chunker;

    public PartitionedQueryLogic() {
        super();
    }

    public PartitionedQueryLogic(PartitionedQueryLogic other) {
        super(other);
        this.setChunker(other.chunker.clone());
    }

    @Override
    public GenericQueryConfiguration initialize(AccumuloClient client, Query settings, Set<Authorizations> auths) throws Exception {
        log.trace("initialize()");

        this.client = client;
        this.auths = auths;

        this.settings = settings.duplicate(settings.getQueryName() + "-chunk");
        this.settings.setQuery(getJexlQueryString(this.settings));
        chunker.setBaseQuery(this.settings);

        // this logic only accepts a list of selectors, possibly with ORs between them (simplified LUCENE)
        // but if the parent class thinks that the syntax is LUCENE, it will convert it to JEXL which screws up the chuncker
        Set<Parameter> params = new HashSet<>();
        Set<Parameter> origParams = this.settings.getParameters();
        for (Parameter p : origParams) {
            if (p.getParameterName().equals(QueryParameters.QUERY_SYNTAX) == false) {
                params.add(p);
            }
        }
        // ensure that the parent logic thinks that the syntax is JEXL so that it leaves it alone
        params.add(new Parameter(QueryParameters.QUERY_SYNTAX, "JEXL"));
        this.settings.setParameters(params);

        if (chunker.preInitializeQueryLogic()) {
            GenericQueryConfiguration config = super.initialize(this.client, this.settings, this.auths);
            if (!config.getQueries().hasNext()) {
                return config;
            }

            chunker.initialize(config);
        }

        return initializeNextChunk();
    }

    /**
     * This method will pull the next sub-query from the chunker and use that query to reinitialize the query logic.
     *
     * @return query configuration
     * @throws Exception
     *             if there are issues
     */
    public GenericQueryConfiguration initializeNextChunk() throws Exception {
        log.trace("initializeNextChunk()");
        if (chunker.hasNext()) {
            Query settings = chunker.next();
            return super.initialize(this.client, settings, this.auths);
        } else {
            log.trace("Something went wrong in chunking...calling initialize with the original query");
            return super.initialize(this.client, this.settings, this.auths);
        }
    }

    /**
     * Returns the iterator exposed by the ShardQueryLogic chain. This is used to construct the
     *
     * @return the iterator exposed by the ShardQueryLogic chain
     */
    private Iterator<Entry<Key,Value>> scanIterator() {
        return super.iterator();
    }

    /**
     * Returns an iterator to the results of every sub-query generated by the specified chunker.
     *
     * This iterator will re-initialize the query logic when moving between the generated query chunks.
     *
     */
    @Override
    public Iterator<Entry<Key,Value>> iterator() {
        return new Iterator<Entry<Key,Value>>() {
            Iterator<Entry<Key,Value>> currentChunk = scanIterator();

            @Override
            public boolean hasNext() {
                while (chunker.hasNext() && !currentChunk.hasNext()) {
                    try {
                        close();
                        GenericQueryConfiguration nextConfig = initializeNextChunk();
                        setupQuery(nextConfig);
                        this.currentChunk = scanIterator();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    } catch (Exception e) {
                        log.warn("Could not process a chunk.", e);
                    }
                }
                return currentChunk.hasNext();
            }

            @Override
            public Entry<Key,Value> next() {
                return this.currentChunk.next();
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException();
            }

        };
    }

    @Override
    public PartitionedQueryLogic clone() {
        return new PartitionedQueryLogic(this);
    }

    public Chunker getChunker() {
        return chunker;
    }

    public void setChunker(Chunker chunker) {
        this.chunker = chunker;
    }
}
