<?php declare(strict_types=1);

require_once __DIR__ . '/common/QrbDataType.php';
require_once __DIR__ . '/common/QrbTranslator.php';

require_once __DIR__ . '/data/QrbValidator.php';
require_once __DIR__ . '/data/QrbProfileConfiguration.php';

require_once __DIR__ . '/process/QrbProcessor.php';
require_once __DIR__ . '/process/QrbProcessStatusTracker.php';

require_once __DIR__ . '/storage/QrbStorageFactory.php';
require_once __DIR__ . '/storage/QrbStorage.php';

// QR bills API.
//
// Some of the methods of this API may throw a QrbException.
// The QrbException provides the following information:
//   getLocalizeKey(): 
//     Returns the key for a localized message to be passed to the 
//     translation mechanism (method QrbApi::xl())
//   getAdditionalInformation():
//     Returns optional additional information 
//     (an empty string if there is no additional information)
//
// All methods that change or process profile data are guarded by a lock against each other.
// If the lock can't be acquired then a QrbLockException is thrown.
final class QrbApi
{
    private QrbStorage     $qrbStorage;
    private ?string        $locale = NULL;
    private ?QrbTranslator $qrbXl  = NULL;
    
    // Creates an API instance for the given locale.
    //
    // locale: if given then the translation uses this locale, otherwise defaults to "en"
    public function __construct(?string $locale = NULL)
    {
        $this->qrbStorage = QrbStorageFactory::getStorage();
        $this->locale     = $locale;

        $this->qrbStorage->writeLog("============================================================");
    }
    
    
    //////////////////////////////////////////////////////////////////////////////////////
    // Common functions

    // Translates the given key into the current user language using the locale passed into the constructor.
    //
    // key: key for message in translation file
    //
    // Returns translation.
    public function xl(string $key) : string
    {
        if (!$this->qrbXl)
        {
            $this->qrbXl = new QrbTranslator();
            
            if ($this->locale)
            {
                $this->qrbXl->setLocale($this->locale);
            }
        }
        
        return $this->qrbXl->xl($key);
    }

    // Writes the given message to the log.
    //
    // msg: message to write into the log
    public function writeLog(string $msg) : void
    {
        $this->qrbStorage->writeLog($msg);
    }
    
    
    //////////////////////////////////////////////////////////////////////////////////////
    // Profile handling

    // Retrieves all profiles.
    //
    // Returns an array of string
    public function getProfiles() : array // of string
    {
        return $this->qrbStorage->getProfiles();
    }
    
    // Retrieves the current profile.
    //
    // Returns the current profile.
    public function getCurrentProfile() : string
    {
        return $this->qrbStorage->getCurrentProfile(); 
    }
    
    // Makes the given profile the current one.
    //
    // profile: profile to select (one of the profiles returned by getCurrentProfile())
    //
    // Returns false if the given profile does not exist.
    //
    // Throws a QrbLockException if lock can't be acquired.
    // Throws a QrbException if storing new profile fails.
    public function selectCurrentProfile(string $profile) : bool
    {
        return $this->qrbStorage->selectCurrentProfile($profile);
    }

    // Creates a new profile and makes it the current one.
    //
    // profile: new profile to create
    //
    // Returns false if the given profile is empty or does already exist.
    //
    // Throws a QrbLockException if lock can't be acquired.
    // Throws a QrbException if storing new profile fails.
    public function createProfile(string $profile) : bool
    {
        return $this->guard(
            function() use ($profile) : bool
            {
                if ($profile === "")
                {
                    $this->qrbStorage->writeLog("Creating profile '{$profile}' not possible as the profile name is empty.");
                    return false;
                }

                return $this->qrbStorage->createProfile($profile);
            });
    }
    
    // Deletes the profile with the given name and makes the default profile the current one.
    //
    // profile: profile to delete (one of the profiles returned by getCurrentProfile())
    //
    // Returns false if the given profile is the default profile.
    //
    // Throws a QrbLockException if lock can't be acquired.
    // Throws a QrbException if storing default profile fails.
    public function deleteProfile(string $profile) : bool
    {
        return $this->guard(
            function() use ($profile) : bool
            {
                return $this->qrbStorage->deleteProfile($profile);
            });
    }

    
    //////////////////////////////////////////////////////////////////////////////////////
    // Configuration
    
    // Returns the configuration of the current profile.
    // If the profile configuration is not found, creates a new instance and persists it.
    //
    // Returns the configuration.
    public function getProfileConfiguration() : QrbProfileConfiguration
    {
        return $this->qrbStorage->getProfileConfiguration();
    }
    
    // Stores the given configuration as the configuration of the current profile.    
    //
    // qrbCfg: changed configuration
    //
    // Throws a QrbLockException if lock can't be acquired.
    // Throws a QrbException if store fails.
    public function storeProfileConfiguration(QrbProfileConfiguration $qrbCfg) : void
    {
        $this->guard(
            function() use ($qrbCfg)
            {
                $this->qrbStorage->storeProfileConfiguration($qrbCfg);
                $this->qrbStorage->writeLog("Configuration after write: {$qrbCfg}");
            });
    }

    
    //////////////////////////////////////////////////////////////////////////////////////
    // Upload input files

    // Checks if the given input data exists and if so returns the relative path to the file,
    // which allows to embed this path in an HTML anchor tag for download.
    //
    // qrbDataType:  QrbDataType
    // language:     one of "de", "fr", "it", "en" or "" (no or default language)
    //
    // Returns NULL if file does not exist.
    public function checkInputDataExists(QrbDataType $qrbDataType, 
                                         string      $language) : ?string
    {
        return $this->qrbStorage->checkInputDataExists($qrbDataType, $language);
    }

    // Deletes input data of the current profile, but not the profile itself. 
    //
    // Returns false if delete fails.
    //
    // Throws a QrbLockException if lock can't be acquired.
    public function deleteInputData() : bool
    {
        return $this->guard(function() : bool
            {
                return $this->qrbStorage->deleteInputData();
            });
    }
    
    // Validates the given input data.
    // The concrete checks depend on the given profile configuration!
    //
    // qrbDataType: QrbDataType
    // tmpFileName: full file name including path (as stored by the PHP upload form)
    // extension:   file name extension, used for a rough file type check
    // qrbCfg:      QrbProfileConfiguration, validation depends on concrete configuration
    //
    // Throws a QrbException if data is invalid.
    public function validateInputData(QrbDataType             $qrbDataType, 
                                      string                  $tmpFileName, 
                                      string                  $extension, 
                                      QrbProfileConfiguration $qrbCfg) : void
    {
        QrbValidator::validate($qrbDataType, $tmpFileName, $extension, $qrbCfg);
    }
    
    // Stores the given input data.
    //
    // tmpFileName: full file name including path (as stored by the PHP upload form)
    // srcFileName: source file name (original file name), just used for logging
    // qrbDataType: QrbDataType
    // language:    one of "de", "fr", "it", "en" or "" ("" for no or default language)
    //
    // Throws a QrbLockException if lock can't be acquired.
    // Throws a QrbException if storing the data fails.
    public function storeInputData(string      $tmpFileName, 
                                   string      $srcFileName, 
                                   QrbDataType $qrbDataType, 
                                   string      $language) : void
    {
        $this->guard(function() use ($tmpFileName, $srcFileName, $qrbDataType, $language)
            {
                $this->qrbStorage->writeLog("Try to move the file '{$tmpFileName}' to the target '{$qrbDataType->value}' (for language '{$language}').");
                
                $this->qrbStorage->storeInputData($tmpFileName, $qrbDataType, $language);
                
                $encSrcFileName = htmlspecialchars($srcFileName);
                $this->qrbStorage->writeLog("The file '{$encSrcFileName}' has been uploaded to target '{$qrbDataType->value}' (for language '{$language}').");
            });
    }


    //////////////////////////////////////////////////////////////////////////////////////
    // Processing

    // Creates a process status tracker that allows to track the status file written by 
    // dryRun(), testRun() and processRun().
    // 
    // Returns a new instance of QrbProcessStatusTracker
    public static function createProcessStatusTracker() : QrbProcessStatusTracker
    {
        return new QrbProcessStatusTracker();
    }

    // Processes all QR bills by creating but not storing them and preparing but not 
    // sending them via e-mail.
    //
    // This method writes any output (including exceptions) into the process status file 
    // (see QrbProcessStatusTracker returned by createProcessStatusTracker()).
    //
    // Throws a QrbLockException if lock can't be acquired.
    public function dryRun() : void
    {
        $this->guard(function()
            {
                $qrbProcessor = new QrbProcessor($this->locale);
                $qrbProcessor->dryRun();
            });
    }

    // Processes one QR bill of a special RecipientsTestRun.csv by creating and storing it 
    // and sending it via e-mail to the given test e-mail address.
    //
    // testEmailAddress: valid e-mail address
    //
    // This method writes any output (including exceptions) into the process status file 
    // (see QrbProcessStatusTracker returned by createProcessStatusTracker()).
    //
    // Throws a QrbException if lock can't be acquired.
    public function testRun(string $testEmailAddress) : void
    {
        $this->guard(function() use ($testEmailAddress)
            {
                $qrbProcessor = new QrbProcessor($this->locale);
                $qrbProcessor->testRun($testEmailAddress);
            });
    }

    // Processes all QR bills by creating and storing them and sending them via e-mail.
    //
    // This method writes any output (including exceptions) into the process status file 
    // (see QrbProcessStatusTracker returned by createProcessStatusTracker()).
    //
    // Throws a QrbLockException if lock can't be acquired.
    public function processRun() : void
    {
        $this->guard(function()
            {
                $qrbProcessor = new QrbProcessor($this->locale);
                $qrbProcessor->processRun();
            });
    }

    // Locks the current profile, executes the given function and releases the lock.
    //
    // Throws a QrbLockException if lock can't be acquired.
    private function guard(Closure $guardedFunction)
    {
        $this->qrbStorage->lockCurrentProfile();
        
        try
        {
            return $guardedFunction();
        }
        finally 
        {
            $this->qrbStorage->unlockCurrentProfile();
        }
    }
}

?>
