<?php declare(strict_types=1);

require_once __DIR__ . '/../data/QrbMultiLanguageText.php';
require_once __DIR__ . '/QrbException.php';

// Helper class to deal with files.
// This class is used:
// a) by the QrbValidator to check the uploaded (temporary) files before they get stored
// b) by the QrbFileStorage as implementation to read stored files from disk
final class QrbFiles
{
    // Returns the real path for the given path.
    public static function fixPath(string $path) : string
    {
        $path  = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path);
        // remove empty elements (eg. "//" or "/" at the end => strlen == 0)
        $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
        $absolutes = array(DIRECTORY_SEPARATOR);
    
        foreach ($parts as $part)
        {
            if ('.' == $part)   continue;               // ignore
            if ('..' == $part)  array_pop($absolutes);  // remove parent
            else                $absolutes[] = $part;   // add part
        }
        
        // special treating for Windows local paths like "C:"
        if  (  (count($absolutes) > 1)               
            && (str_ends_with($absolutes[1], ":")))     // the 2nd element contains a drive letter
        {
            array_shift($absolutes);    // remove the first element ("/"), which expands in Windows to "\\"
        }
    
        return implode(DIRECTORY_SEPARATOR, $absolutes);
    }

    // Deletes all files below the given path and the path itself if deletePathItSelf is true.
    //
    // Returns true if success, false if delete fails somehow.
    public static function deletePath(string $path, bool $deletePathItSelf) : bool
    {
        $dirIt = new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS);
        $fileIt = new RecursiveIteratorIterator($dirIt, RecursiveIteratorIterator::CHILD_FIRST);

        foreach ($fileIt as $file)
        {
            if ($file->isDir()) 
            {
                if (!rmdir($file->getPathname())) return false;
            }
            else 
            {
                if (!unlink($file->getPathname())) return false;
            }
        }

        if ($deletePathItSelf)
        {
            if (!rmdir($path)) return false;
        }
        
        return true;
    }
    
    // Reads a multi-language text from disk.
    // The file with the given file name is mandatory and its contents is set as default text.
    // After this, the given file name is extended with each of the supported languages.
    // If such a file is found, it is read and its contents is appended to the 
    // multi-language text for the found language.
    //
    // fileName:            base file name (file name of the default text)
    // supportedLanguages:  array with supported languages (language codes like "de", "en" etc.)
    // additionalLanguages: array with languages (language codes like "de", "en" etc.) that **must** exist
    //
    // Throws QrbException if read fails.
    // Throws QrbException if a required language is missing.
    public static function readMultiLanguageText(
            string $fileName, 
            array  $supportedLanguages,
            array  $additionalLanguages
        ) : QrbMultiLanguageText
    {
        $text = self::readTextFile($fileName);
        $mlText = new QrbMultiLanguageText($text);
        
        $posDot = strrpos($fileName, '.');
        
        // If we do not find a '.' character then the file has no extension.
        // This is typically the case when a file is uploaded ("tmp_name" in file upload in POST has no extension).
        // For now, we just ignore that and do not look for multi-language support.
        if ($posDot !== false)    // '.' found
        {
            foreach ($supportedLanguages as $language)
            {
                $fileNameLanguage = substr_replace($fileName, "_" . $language, $posDot, 0);
        
                if (file_exists($fileNameLanguage))
                {
                    $text = self::readTextFile($fileNameLanguage);
                    $mlText->addText($language, $text);
                }
            }
            
            foreach ($additionalLanguages as $language)
            {
                if (! $mlText->existsLocalizedText($language))
                    throw new QrbException(static::class . " - input file for language '{$language}' is not provided for {$fileName}.",
                                           "exception.files.missingInputFileForLanguage",
                                           "{$fileName}, {$language}, ");
            }
        }
        
        return $mlText;
    }
    
    // Reads the file with the given name and decodes the data as JSON encoded text.
    //
    // Throws QrbException if read fails.
    public static function readJsonFile(string $fileName) : array
    {
        $dataJson = self::readTextFile($fileName);
        $data = json_decode($dataJson, true);
        if ($data == NULL)
            throw new QrbException(static::class . " - error decoding JSON file '{$fileName}': '" . json_last_error_msg() . "'",
                                   "exception.files.errorDecodingJsonFile",
                                   "{$fileName}");
        return $data; 
    }
    
    // Writes the given array encoded as JSON encoded text to a file with the given file name.
    //
    // Throws QrbException if write fails.
    public static function writeJsonFile(string $fileName, array $data) : void
    {
        if (($dataJson = json_encode($data, JSON_PRETTY_PRINT)) === false)
            throw new QrbException(static::class . " - error encoding JSON data for file '{$fileName}': '" . json_last_error_msg() . "'",
                                   "exception.files.errorEncodingJsonFile",
                                   "{$fileName}");
        self::writeTextFile($fileName, $dataJson);
    }
    
    // Reads a text file.
    //
    // Throws QrbException if read fails.
    public static function readTextFile(string $fileName) : string
    {
        if (!file_exists($fileName))
            throw new QrbException(static::class . " - File '{$fileName}' does not exist",
                                   "exception.files.fileDoesNotExist",
                                   "{$fileName}");
        if (($file = fopen($fileName, "r")) === false)
            throw new QrbException(static::class . " - Unable to open file '{$fileName}'",
                                   "exception.files.unableToOpenFile",
                                   "{$fileName}");
        
        try
        {
            clearstatcache();
            
            if (($text = fread($file, filesize($fileName))) === false)
                throw new QrbException(static::class . " - Unable to read file '{$fileName}'",
                                       "exception.files.unableToReadFile",
                                       "{$fileName}");
            return $text;
        }
        finally
        {
            if(!fclose($file))
                throw new QrbException(static::class . " - Unable to close file '{$fileName}'",
                                       "exception.files.unableToCloseFile",
                                       "{$fileName}");
        }
    }
    
    // Writes a text file.
    //
    // Throws QrbException if write fails.
    public static function writeTextFile(string $fileName, string $text) : void
    {
        if (($file = fopen($fileName, "w")) === false)
            throw new QrbException(static::class . " - Unable to open file '{$fileName}'",
                                   "exception.files.unableToOpenFile",
                                   "{$fileName}");
        
        try
        {
            if (fwrite($file, $text) === false)
                throw new QrbException(static::class . " - Unable to write file '{$fileName}'",
                                       "exception.files.unableToWriteFile",
                                       "{$fileName}");
            if (!fflush($file))
                throw new QrbException(static::class . " - Unable to flush file '{$fileName}'",
                                       "exception.files.unableToFlushFile",
                                       "{$fileName}");
        }
        finally
        {
            if (!fclose($file))
                throw new QrbException(static::class . " - Unable to close file '{$fileName}'",
                                       "exception.files.unableToCloseFile",
                                       "{$fileName}");
        }
    }
    
    // Reads a CSV file.
    // 
    // Throws QrbException if read fails.
    public static function readCsvFile(string $fileName) : array
    {
        if (!file_exists($fileName))
            throw new QrbException(static::class . " - File '{$fileName}' does not exist",
                                   "exception.files.fileDoesNotExist",
                                   "{$fileName}");
        if (($file = fopen($fileName, "r")) === false)
            throw new QrbException(static::class . " - Unable to open file '{$fileName}'",
                                   "exception.files.unableToOpenFile",
                                   "{$fileName}");

        try
        {
            $delimiter = self::getCsvDelimiter($fileName);
            
            // read each line in CSV file at a time
            while (! feof($file))
            {
                if ( ($row = fgetcsv($file, NULL, $delimiter)) === false )
                    throw new QrbException(static::class . " - Unable to read file '{$fileName}'",
                                           "exception.files.unableToReadFile",
                                           "{$fileName}");
                $data[] = $row;
            }
            
            return $data;
        }
        finally
        {        
            if (!fclose($file))
                throw new QrbException(static::class . " - Unable to close file '{$fileName}'",
                                       "exception.files.unableToCloseFile",
                                       "{$fileName}");
        }
    }
    
    // Just checks which of the possible delimiters (',', '\t', ';', '|', ':') has the 
    // highest count in the given file in the given number of lines (defaults to 2).
    // 
    // Returns the delimiter that has the highest count.
    private static function getCsvDelimiter(string $fileName, int $noOfLinesToCheck = 2) : string
    {
        $fileObject = new SplFileObject($fileName);
        $delimiters = array(',', '\t', ';', '|', ':');
        $results = array();
        $i = 0;
        
        while ($fileObject->valid() && ($i <= $noOfLinesToCheck))
        {
            $line = $fileObject->fgets();
            
            foreach ($delimiters as $delimiter)
            {
                $regExp = '/['.$delimiter.']/';
                $fields = preg_split($regExp, $line);
                
                if (count($fields) > 1)
                {
                    if (!empty($results[$delimiter]))
                    {
                        $results[$delimiter]++;
                    }
                    else
                    {
                        $results[$delimiter] = 1;
                    }   
                }
            }
            
            $i++;
        }
        
        $results = array_keys($results, max($results));
        return $results[0];
    }
}

?>
