<?php
// Copyright (c) Pickware GmbH. All rights reserved.
// This file is part of software that is released under a proprietary license.
// You must not copy, modify, distribute, make publicly available, or execute
// its contents or parts thereof without express permission by the copyright
// holder, unless otherwise permitted by law.

namespace Shopware\Plugins\ViisonCommon\Classes\Installation;

use Shopware\Models\Config\Element;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\EntityManager;

class ExpiringLock extends Lock
{

    /**
     * @var int
     */
    private $expirationTimeInSeconds;

    public function __construct($lockIdentifier, $lockDescription, $expirationTimeInSeconds, EntityManager $entityManager)
    {
        parent::__construct($lockIdentifier, $lockDescription, $entityManager);
        $this->expirationTimeInSeconds = $expirationTimeInSeconds;
    }

    protected function getLockType()
    {
        return 'number';
    }

    protected function getLockValue()
    {
        return $this->getCurrentTime();
    }

    private function getCurrentTime()
    {
        return time(); // Now.
    }

    protected function handleExistingLock(Element $existingLock)
    {
        if (!$this->elementIsExpired($existingLock)) {
            return parent::handleExistingLock($existingLock);
        }

        $entityManager = $this->getEntityManager();
        $entityManager->beginTransaction();
        try {
            $entityManager->lock($this->fetchLock(), LockMode::PESSIMISTIC_READ);

            // Refetch to have guaranteed current data.
            $existingLock = $this->fetchLock();

            // Recheck with fresh data.
            if (!$this->elementIsExpired($existingLock)) {
                $returnValue = parent::handleExistingLock($existingLock);
                $entityManager->rollback();

                return $returnValue;
            }

            // Refresh lock to claim as our own.
            $existingLock->setValue($this->getLockValue());
            $entityManager->persist($existingLock);

            $entityManager->flush();
            $entityManager->commit();

            return $this->createLockStatus(true, $existingLock);
        } catch (\Exception $e) {
            $entityManager->rollback();
            throw $e;
        }
    }

    private function elementIsExpired(Element $existingLock)
    {
        $currentTime = time();

        $elapsedTime = $currentTime - $existingLock->getValue();
        if ($elapsedTime < $this->expirationTimeInSeconds) {
            return false;
        }

        return true;
    }

    /**
     * {@inheritdoc}
     *
     * @return ExpiringLockStatus
     */
    protected function createLockStatus($hasBeenAcquired, Element $lock)
    {
        $remainingLockTimeInSeconds = $this->expirationTimeInSeconds - time() + $lock->getValue();
        if ($remainingLockTimeInSeconds < 0) {
            $remainingLockTimeInSeconds = 0;
        }

        return new ExpiringLockStatus($hasBeenAcquired, $lock->getDescription(), $remainingLockTimeInSeconds);
    }
}
