<?php
namespace Helpers\Trad;

use Helpers\Environnement\Recette\Traduction;
use Helpers\Log\LogSysteme;
use Psr\SimpleCache\CacheInterface;

class Trad implements ITrad
{
    protected \PDO $_pdo;
    protected CacheInterface $_cache;
    
    private string $_langageCode;
    private string $_brokerNumber;
    protected string $_cacheKeyPrefix;

    /**
     * @var array<mixed> $bufferTrad
     */
    protected $bufferTrad;
    
    public function __construct(
        \PDO $pdo,
        CacheInterface $cache,
        string $langageCode,
        string $brokerNumber
    ) {
        $this->_pdo = $pdo;
        $this->_cache = $cache;
        $this->_langageCode = !empty($langageCode) ?
            $langageCode : ($_COOKIE['OxaprocDeflangue'] ?? 'FR');// TODO : get it form the source instead of a COOKIE
        $this->_brokerNumber = $brokerNumber;
        $this->_cacheKeyPrefix = 'TRAD_'.$langageCode.$brokerNumber.'_';
        $this->bufferTrad = [];
        
        // Case : cache not loaded -> load it
        if (! $this->_cache->has($this->_cacheKeyPrefix)) {
            $this->initCache();
        }
    }
    
    public function copy(string $lang) :ITrad
    {
        return new self(
            $this->_pdo,
            $this->_cache,
            $lang,
            $this->_brokerNumber
        );
    }
    
//_____________________________________________________________________________
// Accessor
    
    public function getProdCode() :string
    {
        return 'OXAPROC';
    }
    public function getLangCode() :string
    {
        return $this->_langageCode;
    }
    
//_____________________________________________________________________________
// Modifier
    
    /**
     * Soft delete by removing the "loaded" flag,
     * forcing a cache reset at next initialisation
     */
    public function deleteCache() :self
    {
        // Remove "loaded" flag
        $this->_cache->delete($this->_cacheKeyPrefix);
        return $this;
    }
    
    /**
     * /!\ This will interfere with other feature using this cache
     * DO NOT USE UNLESS INTENDED
     */
    // public function deleteCacheHard() :self
    // {
    //     $this->_cache->clear();
    //     return $this;
    // }
    
    public function setLang(string $langageCode) :self
    {
        $this->_langageCode = $langageCode;
        $this->_cacheKeyPrefix = 'TRAD_'.$langageCode.$this->_brokerNumber.'_';

        if (! $this->_cache->has($this->_cacheKeyPrefix)) {
            $this->initCache();
        }

        return $this;
    }
    
//_____________________________________________________________________________
// Process
    
    public function t(string $term) :string
    {
        if (isset($this->bufferTrad[$term])) {
            return $this->bufferTrad[$term];
        }
        
        $mapTrad = [];
        
        // Note : wincache does not handle null for missing key, intead an entry with an empty string is created
        // and this is why we must use has() before get
        if ($this->_cache->has($this->_cacheKeyPrefix)) {
            // (see Memory leak below for more info on serialize)
            $mapTrad = $this->getMapTradCache();
        }
        
        $trad = $mapTrad[$term] ?? null;
        
        // Case : retrieve from cache
        if ($trad !== null) {
            $this->setBuffer($term, $trad);
            return $trad;
        }

        // Get from DB
        $row = Traduction::getTradByTerm(
            $this->_pdo,
            $this->getProdCode(),
            $this->getLangCode(),
            $term
        );
        
        // Case : term does not exit in DB
        if ($row === false) {
            LogSysteme::prefixErrorLog('Missing term "'.$term.'"');
        }
        
        if (is_array($row) && !empty($row['TRAD'])) {
            $trad = $row['TRAD'];
        } else {
            $trad = $term;
        }
        
        // Update cache (see Memory leak below for more info on serialize)
        $mapTrad[ $term ] = $trad;
        $this->setMapTradCache($mapTrad);
        unset($mapTrad);
        
        
        return $trad;
    }

//_____________________________________________________________________________
// Shorthand
    
    public function colon() :string
    {
        return "\xc2\xa0: ";
    }
    
//_____________________________________________________________________________
// Sub-routine
    
    protected function getCacheKey(string $term) :string
    {
        return $this->_cacheKeyPrefix
        . str_replace(
            str_split('{}()/\\@: '),
            '_',
            (string) json_encode($term)
        );
    }
    
    protected function initCache() :self
    {
        $mapTrad = [];
        Traduction::getAllTradBatch(
            $this->_pdo,
            'OXAPROC',
            $this->_langageCode,
            function ($row) use (&$mapTrad) {
                $trad = $row['TXT'];
                if (!empty($row['TRAD'])) {
                    $trad = $row['TRAD'];
                }
                $mapTrad[ $row['TXT'] ] = $trad;
            }
        );

        // file_put_contents(
        //     'TRAD.php',
        //     '<?php return ' . var_export($mapTrad, true) . ';'
        // );
        // file_put_contents(
        //     'TRAD2.txt',
        //     gzcompress(serialize($mapTrad))
        // );
        
        // (see Memory leak below for more info on serialize)
        $this->setMapTradCache($mapTrad);
        
        return $this;
    }
    
//_____________________________________________________________________________
// Memory leak
    
    // It seems that wincache generate memory leaks when used with php arrays
    // TODO : remonter ça au dev de wincache
    
    // Repo case :
    
    // wincache_ucache_clear();
    
    // foreach( range(0,1000) as $i) {
    //     error_log('1');
    //     error_log(var_export(memory_get_usage(),true));
    //     $value = wincache_ucache_get('TOTO', $success);
    //     error_log('2');
    //     error_log(var_export(memory_get_usage(),true));
    //     unset($value);
    //     error_log('3');
    //     error_log(var_export(memory_get_usage(),true));
    // }
    
//_____________________________________________________________________________
// Static routine
    
    public static function getPreferedLang() :string
    {
        if (isset($_SESSION['Session_Langue'])) {
            return $_SESSION['Session_Langue'];
        } elseif (isset($_COOKIE['OxaprocDeflangue'])) {
            return $_COOKIE['OxaprocDeflangue'];
        }
        return '';
    }
    
    
    /**
     * Le nom de cette fonction est le meme que coté magic,
     * il va substituer, les args passé en parametre dans la chaine de base
     * Ensuite s'il reste des elements de type @#@ alors il seront supprimé
     *
     * @param string $subject
     * @param string $args
     * @return string
     */
    public static function strBuild(string $subject)
    {
        if (strpos($subject, '@') === false) {
            LogSysteme::prefixErrorLog(
                (string) new \Exception("PHP Fatal error: This trad : '".$subject."' does not have an '@' in the initial text => ".$_SERVER['REQUEST_URI'])
            );
            return $subject;
        }

        $args = func_get_args();
        
        /** @phpstan-ignore-next-line */
        return (string) preg_replace_callback(
            '|@(\d+)@|',
            function ($matches) use ($args) {
                /**
                 * On prend la valeur qui se trouve entre les 2 obtenus
                 */
                try {
                    $val = (int) $matches[1];
                    if (is_array($args) && array_key_exists($val, $args)) {
                        return $args[$val];
                    }
                } catch (\Exception $e) {
                    // Silent error
                }
                return '';
            },
            $args[0]
        );
    }
    
    /**
     * initSession
     *
     * @return void
     */
    public static function initSession()
    {
        if (isset($_SESSION['traduction'])) {
            unset($_SESSION['traduction']);
        }
    }

    public function setBuffer(string $term, string $trad) : void
    {
        $this->bufferTrad[$term] = $trad;
    }

    /**
     * @param array<mixed> $mapTrad
     */
    protected function setMapTradCache(array $mapTrad) :void
    {
        $comp = gzcompress(serialize($mapTrad));
        if ($comp === false) {
            LogSysteme::prefixErrorLog('Compression failure');
            return;
        }
        $b = $this->_cache->set($this->_cacheKeyPrefix, $comp);
        if ($b === false) {
            LogSysteme::prefixErrorLog('Cache failure');
        }
    }

    /**
     * @return array<mixed>
     */
    protected function getMapTradCache() :array
    {
        $res = gzuncompress($this->_cache->get($this->_cacheKeyPrefix));
        if ($res === false) {
            $this->_cache->delete($this->_cacheKeyPrefix);
            LogSysteme::prefixErrorLog('Cache reset');
            return [];
        }
        return unserialize($res);
    }
}
