package de.duehl.basics.io.lock;

/*
 * Copyright 2021 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.io.exceptions.FileNotFoundRuntimeException;
import de.duehl.basics.system.SystemTools;

/**
 * Diese Klasse ist die Basisklasse für wartende Locks.
 *
 * Falls der Lock nicht auf Anhieb erhalten wird, wird eine gewisse Zeit lang immer wieder
 * versucht, den Lock zu erhalten.
 *
 * @version 1.01     2021-02-09
 * @author Christian Dühl
 */

abstract class WaitingLock<T extends Lock> implements Lock {

    /** Anzahl der Sekunden, die darauf gewartet wird, den Lock zu erhalten. */
    private final int waitForLockSeconds;

    /** Anzahl der Millisekunden, die nach einem nicht erhaltenen Lock gewartet wird. */
    private final int sleepBeforeRetryMilliseconds;

    /** Verwaltet das Locking der Datei. */
    private final T lock;

    /** Zeit als Anzahl Sekunden zu Beginn des Locks. */
    private long timeMillisAtStart;

    /**
     * Konstruktor.
     *
     * @param waitForLockSeconds
     *            Anzahl der Sekunden, die darauf gewartet wird, den Lock zu erhalten.
     * @param sleepBeforeRetryMilliseconds
     *            Anzahl der Millisekunden, die nach einem nicht erhaltenen Lock gewartet wird.
     * @param lock
     *            Der zu verwendende Lock.
     */
    public WaitingLock(int waitForLockSeconds, int sleepBeforeRetryMilliseconds, T lock) {
        this.waitForLockSeconds = waitForLockSeconds;
        this.sleepBeforeRetryMilliseconds = sleepBeforeRetryMilliseconds;
        this.lock = lock;
    }

    /**
     * Versucht einen Lock auf die Datei zu erhalten.
     *
     * Hat dies keinen Erfolg, wird eine gewisse Zeit lang erneut versucht, den Lock zu erhalten.
     *
     * @return Erfolg. Wird hier false zurückgegeben, konnte in keinem der Versuche der Lock
     *         erhalten werden.
     * @throws FileNotFoundRuntimeException
     *             Wenn die Datei nicht gefunden wurde.
     */
    @Override
    public boolean lock() {
        timeMillisAtStart = System.currentTimeMillis();
        boolean success = lockLoop();
        return success;
    }

    private boolean lockLoop() {
        boolean success = lock.lock();
        while (!success) {
            SystemTools.sleep(sleepBeforeRetryMilliseconds);

            if (lockTimeExceeded()) {
                return false;
            }

            success = lock.lock();
        }

        return true;
    }

    private boolean lockTimeExceeded() {
        long timeMillisNow = System.currentTimeMillis();
        long milliesSinceStart = timeMillisNow - timeMillisAtStart;
        long milliesWaitForLock = waitForLockSeconds * 1000L;
        return milliesSinceStart > milliesWaitForLock;
    }

    /**
     * Versucht den vorhandenen Lock wieder zu lösen.
     *
     * @return Erfolg.
     */
    @Override
    public boolean unlock() {
        boolean success = lock.unlock();
        return success;
    }

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

}
