package de.duehl.basics.io.lock;

/*
 * Copyright 2017 Christian Dühl. All rights reserved.
 *
 * This program is free software. You can redistribute it and/or
 * modify it under the same terms as perl:
 *
 * general:  http://dev.perl.org/licenses/
 * GPL:      http://dev.perl.org/licenses/gpl1.html
 * artistic: http://dev.perl.org/licenses/artistic.html
 */

import de.duehl.basics.logging.Logger;
import de.duehl.basics.retry.Retry;
import de.duehl.basics.retry.Retryable;

/**
 * Diese Klasse kümmert sich das Erhalten und Freigeben eines Locks auf der Lockdatei. Zum Locken
 * wird eine Weile lang versucht, das Lock zu erhalten.
 *
 * @version 1.01     2017-04-25
 * @author Christian Dühl
 */

public class RetryingThrowingFileLock implements Lock {

    /** Name der Datei, die gelockt wird. */
    private final String lockfile;

    /** Maximale Anzahl an versuchen, das Lock auf Lockfile zu erhalten. */
    private final int maximumNumberOfTries;

    /** Zeit die gewartet wird zwischen vergeblichen Versuchen das Lock zu erhalten. */
    private final long millisecondsToSleep;

    /** Lock für exklusiven Zugriff auf das Erzeugen der Lockdatei. */
    private final Lock lock;

    /** Der Logger. */
    private final Logger logger;

    /**
     * Gibt an, ob die übergeben Zeit, die geschlafen wird, variiert werden soll, um immer wieder
     * gleiche Versuche zur selben Zeit von verschiedenen Prozessen zu vermeiden.
     */
    private boolean randomizeSleepTime;

    /**
     * Konstruktor.
     *
     * @param lockfile
     *            Name der Datei, die gelockt werden soll.
     * @param maximumNumberOfTries
     *            Maximale Anzahl an versuchen, das Lock auf das Lockfile zu erhalten.
     * @param millisecondsToSleepByErrors
     *            Zeit die gewartet wird zwischen vergeblichen Versuchen das Lock zu erhalten.
     */
    public RetryingThrowingFileLock(String lockfile, int maximumNumberOfTries,
            long millisecondsToSleepByErrors) {
        this(lockfile, maximumNumberOfTries, millisecondsToSleepByErrors, null);
    }

    /**
     * Konstruktor.
     *
     * @param lockfile
     *            Name der Datei, die gelockt werden soll.
     * @param maximumNumberOfTries
     *            Maximale Anzahl an versuchen, das Lock auf das Lockfile zu erhalten.
     * @param millisecondsToSleep
     *            Zeit die gewartet wird zwischen vergeblichen Versuchen das Lock zu erhalten.
     * @param logger
     *            Der Logger.
     */
    public RetryingThrowingFileLock(String lockfile, int maximumNumberOfTries,
            long millisecondsToSleep, Logger logger) {
        this.lockfile = lockfile;
        this.maximumNumberOfTries = maximumNumberOfTries;
        this.millisecondsToSleep = millisecondsToSleep;
        this.logger = logger;
        lock = new ThrowingFileLock(lockfile);
        log("locke auf " + lock.getFilename());
        randomizeSleepTime = false;
    }

    /**
     * Variiert die übergeben Zeit, die geschlafen wird, um immer wieder gleiche Versuche zur
     * selben Zeit von verschiedenen Prozessen zu vermeiden.
     */
    public void randomizeSleepTime() {
        randomizeSleepTime = true;
    }

    /**
     * Versucht einen Lock auf die Systemweite Lock-Datei zu erhalten.
     *
     * @return Gibt an, ob der Lock erhalten wurde.
     */
    @Override
    public boolean lock() {
        Retry retry = createRetry();
        retry.tryAndTry();
        boolean success = retry.isSucessfullyDone();
        log("lock bekommen? Erfolg: " + success);
        return success;
    }

    private Retry createRetry() {
        Retryable retryable = createGetLockRetryable();
        Retry retry = new Retry(retryable, maximumNumberOfTries, millisecondsToSleep, logger);
        if (randomizeSleepTime) {
            retry.randomizeSleepTime();
        }
        return retry;
    }

    private Retryable createGetLockRetryable() {
        return new Retryable() {
            @Override
            public void tryIt() {
                lock.lock();
            }
        };
    }

    /**
     * Versucht einen Lock auf die Systemweite Lock-Datei freizugeben.
     *
     * @return Gibt an, ob der Lock freigegeben wurde.
     */
    @Override
    public boolean unlock() {
        boolean success = lock.unlock();
        log("lock gelöst? Erfolg: " + success);
        return success;
    }

    /** Getter für die Datei, die gelockt wird oder die zum Lock benutzt wird. */
    @Override
    public String getFilename() {
        return lockfile;
    }

    /** Loggt die übergebene Nachricht. */
    private void log(String message) {
        if (logger != null) {
            logger.log(message, 1);
        }
    }

}
