/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.ignite.spi.deployment.uri.scanners;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URI;
import org.apache.ignite.IgniteLogger;
import org.apache.ignite.internal.util.tostring.GridToStringExclude;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.spi.IgniteSpiThread;

/**
 * URI deployment scanner manager.
 */
public class UriDeploymentScannerManager implements UriDeploymentScannerContext {
    /** Ignite instance name. */
    private final String igniteInstanceName;

    /** URI that scanner should looks after. */
    @GridToStringExclude
    private final URI uri;

    /** Temporary deployment directory. */
    private final File deployDir;

    /** Scan frequency. */
    private final long freq;

    /** Found files filter. */
    private final FilenameFilter filter;

    /** Scanner listener which should be notified about changes. */
    private final GridUriDeploymentScannerListener lsnr;

    /** Logger. */
    private final IgniteLogger log;

    /** Underlying scanner. */
    private final UriDeploymentScanner scanner;

    /** Scanner implementation. */
    private IgniteSpiThread scannerThread;

    /** Whether first scan completed or not. */
    private boolean firstScan = true;

    /**
     * Creates new scanner.
     *
     * @param igniteInstanceName Ignite instance name.
     * @param uri URI which scanner should looks after.
     * @param deployDir Temporary deployment directory.
     * @param freq Scan frequency.
     * @param filter Found files filter.
     * @param lsnr Scanner listener which should be notifier about changes.
     * @param log Logger.
     * @param scanner Scanner.
     */
    public UriDeploymentScannerManager(
        String igniteInstanceName,
        URI uri,
        File deployDir,
        long freq,
        FilenameFilter filter,
        GridUriDeploymentScannerListener lsnr,
        IgniteLogger log,
        UriDeploymentScanner scanner) {
        assert uri != null;
        assert freq > 0;
        assert deployDir != null;
        assert filter != null;
        assert log != null;
        assert lsnr != null;
        assert scanner != null;

        this.igniteInstanceName = igniteInstanceName;
        this.uri = uri;
        this.deployDir = deployDir;
        this.freq = freq;
        this.filter = filter;
        this.log = log.getLogger(getClass());
        this.lsnr = lsnr;
        this.scanner = scanner;
    }

    /**
     * Starts scanner.
     */
    public void start() {
        scannerThread = new IgniteSpiThread(igniteInstanceName, "grid-uri-scanner", log) {
            /** {@inheritDoc} */
            @SuppressWarnings({"BusyWait"})
            @Override protected void body() throws InterruptedException {
                try {
                    while (!isInterrupted()) {
                        try {
                            scanner.scan(UriDeploymentScannerManager.this);
                        }
                        catch (Exception e) {
                            log.error("Uncaught error in URI deployment scanner", e);
                        }
                        finally {
                            // Do it in finally to avoid any hanging.
                            if (firstScan) {
                                firstScan = false;

                                lsnr.onFirstScanFinished();
                            }
                        }

                        Thread.sleep(freq);
                    }
                }
                finally {
                    // Double check. If we were cancelled before anything has been scanned.
                    if (firstScan) {
                        firstScan = false;

                        lsnr.onFirstScanFinished();
                    }
                }
            }
        };

        scannerThread.start();

        if (log.isDebugEnabled())
            log.debug("Grid URI deployment scanner started: " + this);
    }

    /**
     * Cancels scanner execution.
     */
    public void cancel() {
        U.interrupt(scannerThread);
    }

    /**
     * Joins scanner thread.
     */
    public void join() {
        U.join(scannerThread, log);

        if (log.isDebugEnabled())
            log.debug("Grid URI deployment scanner stopped: " + this);
    }

    /** {@inheritDoc} */
    @Override public boolean isCancelled() {
        assert scannerThread != null;

        return scannerThread.isInterrupted();
    }

    /** {@inheritDoc} */
    @Override public File createTempFile(String fileName, File tmpDir) throws IOException {
        assert fileName != null;

        int idx = fileName.lastIndexOf('.');

        if (idx == -1)
            idx = fileName.length();

        String prefix = fileName.substring(0, idx);
        if (idx < 3) { // Prefix must be at least 3 characters long. See File.createTempFile(...).
            prefix += "___";
        }

        String suffix = fileName.substring(idx);

        return File.createTempFile(prefix, suffix, tmpDir);
    }

    /** {@inheritDoc} */
    @Override public boolean isFirstScan() {
        return firstScan;
    }

    /** {@inheritDoc} */
    @Override public URI getUri() {
        return uri;
    }

    /** {@inheritDoc} */
    @Override public File getDeployDirectory() {
        return deployDir;
    }

    /** {@inheritDoc} */
    @Override public FilenameFilter getFilter() {
        return filter;
    }

    /** {@inheritDoc} */
    @Override public GridUriDeploymentScannerListener getListener() {
        return lsnr;
    }

    /** {@inheritDoc} */
    @Override public IgniteLogger getLogger() {
        return log;
    }

    /** {@inheritDoc} */
    @Override public String toString() {
        return S.toString(UriDeploymentScannerManager.class, this, "uri", U.hidePassword(uri.toString()));
    }
}
