package imcode.server.document.index.service.impl;

import com.imcode.imcms.util.ThreadUtility;
import imcode.server.document.index.IndexException;
import imcode.server.document.index.service.DocumentIndexService;
import imcode.server.document.index.service.IndexUpdateOp;
import imcode.server.document.index.service.IndexUpdateOperation;
import org.apache.log4j.Logger;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.response.QueryResponse;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;

/**
 * Implements all DocumentIndexService functionality.
 * Ensures that update and rebuild operations never run concurrently.
 * <p>
 * Indexing errors are handled asynchronously as ManagedSolrDocumentIndexService.IndexError events.
 * On index write (update or rebuild) failure the service stops processing write requests.
 * <p>
 * The business-logic (like rebuild scheduling and index recovery) is implemented on higher levels.
 */
// translated from scala
public class ManagedDocumentIndexService implements DocumentIndexService {

    private final static Logger logger = Logger.getLogger(ManagedDocumentIndexService.class);
    private final static Object lock = new Object();

    private final ExecutorService serviceFailureExecutor = Executors.newSingleThreadExecutor();
    private final ScheduledExecutorService indexUpdateExecutor = Executors.newSingleThreadScheduledExecutor();
    private final ExecutorService indexRebuildExecutor = Executors.newSingleThreadExecutor();

    private final AtomicBoolean shutdownRef = new AtomicBoolean(false);
    private final LinkedBlockingQueue<IndexUpdateOp> indexUpdateRequests = new LinkedBlockingQueue<>(1024);

    private volatile Future indexUpdateFuture = CompletableFuture.completedFuture(null);
    private volatile Future indexRebuildFuture = CompletableFuture.completedFuture(null);

    private SolrClient solrClientReader;
    private SolrClient solrClientWriter;
    private DocumentIndexServiceOps serviceOps;
    private Consumer<ServiceFailure> failureHandler;

    ManagedDocumentIndexService(SolrClient solrClientReader, SolrClient solrClientWriter,
                                DocumentIndexServiceOps serviceOps, Consumer<ServiceFailure> failureHandler) {
        this.solrClientReader = solrClientReader;
        this.solrClientWriter = solrClientWriter;
        this.serviceOps = serviceOps;
        this.failureHandler = failureHandler;
    }

    @Override
    public QueryResponse query(SolrQuery solrQuery) {
        try {
            return serviceOps.query(solrClientReader, solrQuery);

        } catch (Exception e) {
            logger.error("Search error. solrQuery: " + solrQuery, e);

            serviceFailureExecutor.submit(() -> failureHandler.accept(new ServiceFailure(
                    ManagedDocumentIndexService.this, e, ServiceFailure.Type.SEARCH
            )));

            throw new IndexException(e);
        }
    }

    @Override
    public void update(IndexUpdateOp request) {
        if (!shutdownRef.get()) {
            if (indexUpdateRequests.offer(request)) {
                invokeIndexUpdateThread();

            } else {
                logger.error("Can't submit index update request [" + request + "], requests query is full.");
                // todo: decide what to do here or how to prevent
            }
        }
    }

    /**
     * Creates and starts new index-rebuild-thread if there is no already running one.
     * Any exception or an interruption terminates index-rebuild-thread.
     * <p>
     * An existing index-update-thread is stopped before rebuilding happens and a new index-update-thread is started
     * immediately after running index-rebuild-thread is terminated without errors.
     */
    @Override
    public Future rebuild() {
        if (!shutdownRef.get() && indexRebuildFuture.isDone()) {
            synchronized (lock) {
                if (indexRebuildFuture.isDone()) {
                    indexRebuildFuture = indexRebuildExecutor.submit(this::rebuildIndexes);
                }
            }
        }

        return indexRebuildFuture;
    }

    @Override
    public void shutdown() {
        synchronized (lock) {
            if (shutdownRef.compareAndSet(false, true)) {
                logger.info("Attempting to shut down the service.");

                try {
                    indexUpdateRequests.clear();
                    serviceFailureExecutor.shutdown();
                    indexUpdateExecutor.shutdown();
                    indexRebuildExecutor.shutdown();

                    try {
                        solrClientReader.commit(true, true, true);
                        solrClientReader.close();
                    } catch (Exception e) {
                        logger.warn("An error occurred while shutting down SolrServer reader.", e);
                    }

                    if (!solrClientReader.equals(solrClientWriter)) {
                        try {
                            solrClientWriter.commit(true, true, true);
                            solrClientWriter.close();
                        } catch (Exception e) {
                            logger.warn("An error occurred while shutting down SolrServer writer.", e);
                        }
                    }

                    logger.info("Service has been shut down.");

                } catch (Exception e) {
                    logger.warn("An error occurred while shutting down the service.", e);
                    throw e;
                }
            }
        }
    }

    @Override
    public boolean isUpdateDone() {
        return indexUpdateFuture.isDone() && indexUpdateRequests.isEmpty();
    }

    private void rebuildIndexes() {
        indexUpdateRequests.clear();
        serviceOps.rebuildIndex(solrClientWriter);
        indexUpdateFuture = indexUpdateExecutor.submit(this::updateIndexes);
    }

    private void invokeIndexUpdateThread() {
        if (indexUpdateFuture.isDone() && indexRebuildFuture.isDone()) {
            indexUpdateFuture = indexUpdateExecutor.schedule(this::updateIndexes, 1, TimeUnit.SECONDS);
            logger.info("Submitted new index update thread.");
        }
    }

    private void updateIndexes() {
        logger.info("Index update thread invoked for " + indexUpdateRequests.size() + " update requests.");

        while (!indexUpdateRequests.isEmpty()) {
            try {
                final IndexUpdateOp updateOp = indexUpdateRequests.take();
                final IndexUpdateOperation indexUpdateOperation = updateOp.operation();

                if (IndexUpdateOperation.ADD.equals(indexUpdateOperation)) {
                    serviceOps.addDocsToIndex(solrClientWriter, updateOp.docId());

                } else if (IndexUpdateOperation.DELETE.equals(indexUpdateOperation)) {
                    serviceOps.deleteDocsFromIndex(solrClientWriter, updateOp.docId());
                }
            } catch (InterruptedException e) {
                logger.debug("document-index-update thread [" + toString() + "] was interrupted");

            } catch (Exception e) {
                final ServiceFailure writeFailure = new ServiceFailure(
                        ManagedDocumentIndexService.this, e, ServiceFailure.Type.UPDATE
                );
                logger.error("error in document-index-update thread [" + toString() + "].", e);
                ThreadUtility.spawnDaemon(() -> failureHandler.accept(writeFailure));
            }
        }

        logger.info("Index update thread finished.");
    }
}

@@ -548,17 +548,202 @@
 current.
-*
+CompletableFuture;%0Aimport java.util.concurrent.ExecutorService;%0Aimport java.util.concurrent.Executors;%0Aimport java.util.concurrent.Future;%0Aimport java.util.concurrent.LinkedBlockingQueue
 ;%0Aimport
@@ -1623,25 +1623,16 @@
 e final 
-Scheduled
 Executor
@@ -1690,17 +1690,8 @@
 read
-Scheduled
 Exec
@@ -6371,15 +6371,13 @@
 or.s
-chedule
+ubmit
 (thi
@@ -6396,29 +6396,8 @@
 exes
-, 1, TimeUnit.SECONDS
 );%0A 

Apply new UI to the admin panel and editors: - Not scheduling index updates.