/*
 * Copyright 2015 JBoss, by Red Hat, Inc
 *
 * 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.uberfire.ext.metadata.io;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.uberfire.commons.async.DescriptiveRunnable;
import org.uberfire.ext.metadata.engine.Indexer;
import org.uberfire.ext.metadata.engine.MetaIndexEngine;
import org.uberfire.ext.metadata.engine.Observer;
import org.uberfire.ext.metadata.model.KCluster;
import org.uberfire.ext.metadata.model.KObject;
import org.uberfire.io.IOService;
import org.uberfire.java.nio.IOException;
import org.uberfire.java.nio.base.FileSystemId;
import org.uberfire.java.nio.file.FileSystem;
import org.uberfire.java.nio.file.FileVisitResult;
import org.uberfire.java.nio.file.Path;
import org.uberfire.java.nio.file.SimpleFileVisitor;
import org.uberfire.java.nio.file.attribute.BasicFileAttributes;
import org.uberfire.java.nio.file.attribute.FileAttribute;
import org.uberfire.java.nio.file.attribute.FileAttributeView;

import static org.kie.soup.commons.validation.PortablePreconditions.checkNotNull;
import static org.uberfire.java.nio.file.Files.walkFileTree;

public final class BatchIndex {

    private static final Logger LOG = LoggerFactory.getLogger(BatchIndex.class);

    private final MetaIndexEngine indexEngine;
    private final IOService ioService;
    private final Class<? extends FileAttributeView>[] views;
    private final AtomicBoolean indexDisposed = new AtomicBoolean(false);
    private final Observer observer;
    private final ExecutorService executorService;

    @SafeVarargs
    public BatchIndex(final MetaIndexEngine indexEngine,
                      final IOService ioService,
                      final Observer observer,
                      final ExecutorService executorService,
                      final Class<? extends FileAttributeView>... views) {
        this.indexEngine = checkNotNull("indexEngine",
                                        indexEngine);
        this.ioService = checkNotNull("ioService",
                                      ioService);
        this.observer = checkNotNull("observer",
                                     observer);
        this.views = views;

        this.executorService = executorService;
    }

    public void runAsync(final FileSystem fs) {
        if (fs != null && fs.getRootDirectories().iterator().hasNext()) {
            executorService.execute(new DescriptiveRunnable() {
                @Override
                public String getDescription() {
                    return "FS BatchIndex [" + ((FileSystemId) fs).id() + "]";
                }

                @Override
                public void run() {
                    final AtomicBoolean indexFinished = new AtomicBoolean(false);
                    indexEngine.beforeDispose(new Runnable() {
                        @Override
                        public void run() {
                            indexDisposed.set(true);

                            if (!indexFinished.get()) {
                                indexEngine.delete(KObjectUtil.toKCluster(fs));
                            }
                        }
                    });

                    KCluster cluster = null;
                    try {
                        cluster = KObjectUtil.toKCluster(fs);
                        indexEngine.startBatch(cluster);
                        for (final Path root : fs.getRootDirectories()) {
                            BatchIndex.this.run(root);
                        }
                        indexFinished.set(true);
                    } catch (Exception ex) {
                        if (!indexDisposed.get()) {
                            logError("FileSystem Index fails. [@" + fs.toString() + "]",
                                     ex);
                        }
                    }
                    finally {
                        if (cluster != null) {
                            if (indexFinished.get()) {
                                indexEngine.commit(cluster);
                            } else {
                                indexEngine.abort(cluster);
                            }
                        }
                    }
                }
            });
        }
    }

    public void runAsync(final Path root) {
        this.executorService.execute(new DescriptiveRunnable() {
            @Override
            public String getDescription() {
                return "Path BatchIndex [" + root.toString() + "]";
            }

            @Override
            public void run() {
                BatchIndex.this.run(root);
            }
        });
    }

    public void run(final Path root) {
        run(root,
            null);
    }

    public void run(final Path root,
                    final Runnable callback) {
        try {
            if (root == null) {
                return;
            }

            logInformation("Starting indexing of " + root.toUri() + " ...");

            walkFileTree(checkNotNull("root",
                                      root),
                         new SimpleFileVisitor<Path>() {
                             @Override
                             public FileVisitResult visitFile(final Path file,
                                                              final BasicFileAttributes attrs) throws IOException {
                                 if (indexDisposed.get()) {
                                     return FileVisitResult.TERMINATE;
                                 }
                                 try {
                                     checkNotNull("file",
                                                  file);
                                     checkNotNull("attrs",
                                                  attrs);

                                     if (!file.getFileName().toString().startsWith(".")) {

                                         LOG.debug("Indexing " + file.toUri());

                                         //Default indexing
                                         for (final Class<? extends FileAttributeView> view : views) {
                                             ioService.getFileAttributeView(file,
                                                                            view);
                                         }
                                         final FileAttribute<?>[] allAttrs = ioService.convert(ioService.readAttributes(file));
                                         if (!indexDisposed.get()) {
                                             indexEngine.index(KObjectUtil.toKObject(file,
                                                                                     allAttrs));
                                         } else {
                                             return FileVisitResult.TERMINATE;
                                         }

                                         //Additional indexing
                                         for (Indexer indexer : IndexersFactory.getIndexers()) {
                                             if (file.getFileSystem().isOpen()) {
                                                 if (indexer.supportsPath(file)) {
                                                     final KObject kObject = indexer.toKObject(file);
                                                     if (kObject != null) {
                                                         if (!indexDisposed.get()) {
                                                             indexEngine.index(kObject);
                                                         } else {
                                                             return FileVisitResult.TERMINATE;
                                                         }
                                                     }
                                                 }
                                             }
                                         }
                                     }
                                 } catch (final Exception ex) {
                                     if (indexDisposed.get()) {
                                         logWarning("Batch index couldn't finish. [@" + root.toUri().toString() + "]");
                                         return FileVisitResult.TERMINATE;
                                     } else {
                                         logError("Index fails. [@" + file.toString() + "]",
                                                  ex);
                                     }
                                 }
                                 if (indexDisposed.get()) {
                                     return FileVisitResult.TERMINATE;
                                 }
                                 return FileVisitResult.CONTINUE;
                             }
                         });

            if (!indexDisposed.get()) {
                logInformation("Completed indexing of " + root.toUri());
                if (callback != null) {
                    callback.run();
                }
            } else {
                logWarning("Batch index couldn't finish. [@" + root.toUri().toString() + "]");
            }
        } catch (final IllegalStateException ex) {
            if (indexDisposed.get()) {
                logWarning("Batch index couldn't finish. [@" + root.toUri().toString() + "]");
            } else {
                logError("Index fails - Index has an invalid state. [@" + root.toUri().toString() + "]",
                         ex);
            }
        } catch (final Exception ex) {
            if (indexDisposed.get()) {
                logWarning("Batch index couldn't finish. [@" + root.toUri().toString() + "]");
            } else {
                logError("Index fails. [@" + root.toUri().toString() + "]",
                         ex);
            }
        }
    }

    private void logInformation(final String message) {
        observer.information(message);
        LOG.info(message);
    }

    private void logWarning(final String message) {
        observer.warning(message);
        LOG.warn(message);
    }

    private void logError(final String message,
                          final Throwable throwable) {
        observer.error(message);
        LOG.error(message,
                  throwable);
    }

    public void dispose() {
        indexEngine.dispose();
    }
}
