<?php declare(strict_types=1);

namespace Newland\Toubiz\Sync\Neos\Utility;

use Psr\Log\LoggerAwareTrait;

class SimpleFilebasedLock
{
    use LoggerAwareTrait;
    public const MINUTE = 60;
    public const HOUR = 60 * self::MINUTE;

    /** @var string */
    protected $path;

    /** @var int */
    protected $ignoreLockIfOlderThan;

    public function __construct(string $path, int $ignoreLockIfOlderThan = 3 * self::HOUR)
    {
        $this->path = $path;
        $this->ignoreLockIfOlderThan = $ignoreLockIfOlderThan;
    }

    public function synchronize(callable $block, int $checkEverySeconds = 10): void
    {
        while (true) {
            if ($this->isLocked()) {
                $this->logger->warning(sprintf(
                    'Section is locked by process %s. Waiting until that process has released'
                    . ' the lock before continuing. If you are sure that no other process is running,'
                    . ' then either wait until %s or delete %s',
                    file_get_contents($this->path),
                    date(DATE_ATOM, filemtime($this->path) + $this->ignoreLockIfOlderThan),
                    $this->path
                ));
            } else {
                $this->acquire();
                $block();
                $this->release();
                return;
            }
            sleep($checkEverySeconds);
        }
    }

    public function isLocked(): bool
    {
        if (!file_exists($this->path)) {
            return false;
        }

        $lockAcquiredAt = filemtime($this->path);
        $automaticLockExpiryDate = $lockAcquiredAt + $this->ignoreLockIfOlderThan;

        return $automaticLockExpiryDate > time();
    }

    public function acquire(): void
    {
        if ($this->isLocked()) {
            throw new \Exception('Could not acquire lock: already locked by ' . file_get_contents($this->path));
        }

        file_put_contents($this->path, (string) getmypid());
    }

    public function release(): void
    {
        unlink($this->path);
    }
}
