<?php
/*************************************************************************************/
/*      This file is part of the Thelia package.                                     */
/*                                                                                   */
/*      Copyright (c) OpenStudio                                                     */
/*      email : dev@thelia.net                                                       */
/*      web : http://www.thelia.net                                                  */
/*                                                                                   */
/*      For the full copyright and license information, please view the LICENSE.txt  */
/*      file that was distributed with this source code.                             */
/*************************************************************************************/

namespace Thelia\Action;

use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Filesystem\Filesystem;
use Thelia\Core\Event\Cache\CacheEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\Translation\TranslationEvent;
use Thelia\Core\Translation\Translator;
use Thelia\Log\Tlog;

/**
 * Class Translation
 * @package Thelia\Action
 * @author Manuel Raynaud <manu@raynaud.io>
 */
class Translation extends BaseAction implements EventSubscriberInterface
{
    /** @var ContainerInterface */
    protected $container;

    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    public function getTranslatableStrings(TranslationEvent $event)
    {
        $stringCount = $this->walkDir(
            $event->getDirectory(),
            $event->getMode(),
            $event->getLocale(),
            $event->getDomain(),
            $strings
        );

        $event
            ->setTranslatableStrings($strings)
            ->setTranslatableStringCount($stringCount)
            ;
    }
    /**
     * Recursively examine files in a directory tree, and extract translatable strings.
     *
     * Returns an array of translatable strings, each item having with the following structure:
     * 'files' an array of file names in which the string appears,
     * 'text' the translatable text
     * 'translation' => the text translation, or an empty string if none available.
     * 'dollar'  => true if the translatable text contains a $
     *
     * @param  string $directory the path to the directory to examine
     * @param  string $walkMode type of file scanning: WALK_MODE_PHP or WALK_MODE_TEMPLATE
     * @param  string $currentLocale the current locale
     * @param  string $domain the translation domain (fontoffice, backoffice, module, etc...)
     * @param  array $strings the list of strings
     * @throws \InvalidArgumentException if $walkMode contains an invalid value
     * @return number the total number of translatable texts
     */
    protected function walkDir($directory, $walkMode, $currentLocale, $domain, &$strings)
    {
        $numTexts = 0;

        if ($walkMode == TranslationEvent::WALK_MODE_PHP) {
            $prefix = '\-\>[\s]*trans[\s]*\([\s]*';

            $allowedExts = array('php');
        } elseif ($walkMode == TranslationEvent::WALK_MODE_TEMPLATE) {
            $prefix = '\{intl(?:.*?)[\s]l=[\s]*';

            $allowedExts = array('html', 'tpl', 'xml', 'txt');
        } else {
            throw new \InvalidArgumentException(
                Translator::getInstance()->trans(
                    'Invalid value for walkMode parameter: %value',
                    array('%value' => $walkMode)
                )
            );
        }

        try {
            Tlog::getInstance()->debug("Walking in $directory, in mode $walkMode");

            /** @var \DirectoryIterator $fileInfo */
            foreach (new \DirectoryIterator($directory) as $fileInfo) {
                if ($fileInfo->isDot()) {
                    continue;
                }

                if ($fileInfo->isDir()) {
                    $numTexts += $this->walkDir(
                        $fileInfo->getPathName(),
                        $walkMode,
                        $currentLocale,
                        $domain,
                        $strings
                    );
                }

                if ($fileInfo->isFile()) {
                    $ext = $fileInfo->getExtension();

                    if (\in_array($ext, $allowedExts)) {
                        if ($content = file_get_contents($fileInfo->getPathName())) {
                            $short_path = $this->normalizePath($fileInfo->getPathName());

                            Tlog::getInstance()->debug("Examining file $short_path\n");

                            $matches = array();

                            if (preg_match_all(
                                '/'.$prefix.'((?<![\\\\])[\'"])((?:.(?!(?<![\\\\])\1))*.?)*?\1/ms',
                                $content,
                                $matches
                            )) {
                                Tlog::getInstance()->debug("Strings found: ", $matches[2]);

                                $idx = 0;

                                foreach ($matches[2] as $match) {
                                    $hash = md5($match);

                                    if (isset($strings[$hash])) {
                                        if (! \in_array($short_path, $strings[$hash]['files'])) {
                                            $strings[$hash]['files'][] = $short_path;
                                        }
                                    } else {
                                        $numTexts++;

                                        // remove \' (or \"), that will prevent the translator to work properly, as
                                        // "abc \def\" ghi" will be passed as abc "def" ghi to the translator.

                                        $quote = $matches[1][$idx];

                                        $match = str_replace("\\$quote", $quote, $match);

                                        // Ignore empty strings
                                        if (\strlen($match) == 0) {
                                            continue;
                                        }

                                        $strings[$hash] = array(
                                            'files'   => array($short_path),
                                            'text'  => $match,
                                            'translation' => Translator::getInstance()->trans(
                                                $match,
                                                [],
                                                $domain,
                                                $currentLocale,
                                                false,
                                                false
                                            ),
                                            'custom_fallback' => Translator::getInstance()->trans(
                                                sprintf(
                                                    Translator::GLOBAL_FALLBACK_KEY,
                                                    $domain,
                                                    $match
                                                ),
                                                [],
                                                Translator::GLOBAL_FALLBACK_DOMAIN,
                                                $currentLocale,
                                                false,
                                                false
                                            ),
                                            'global_fallback' => Translator::getInstance()->trans(
                                                $match,
                                                [],
                                                Translator::GLOBAL_FALLBACK_DOMAIN,
                                                $currentLocale,
                                                false,
                                                false
                                            ),
                                            'dollar'  => strstr($match, '$') !== false
                                        );
                                    }

                                    $idx++;
                                }
                            }
                        }
                    }
                }
            }
        } catch (\UnexpectedValueException $ex) {
            // Directory does not exists => ignore it.
        }

        return $numTexts;
    }

    public function writeTranslationFile(TranslationEvent $event, $eventName, EventDispatcherInterface $dispatcher)
    {
        $file = $event->getTranslationFilePath();

        $fs = new Filesystem();

        if (! $fs->exists($file) && true === $event->isCreateFileIfNotExists()) {
            $dir = dirname($file);

            if (! $fs->exists($file)) {
                $fs->mkdir($dir);

                $this->cacheClear($dispatcher);
            }
        }

        if ($fp = @fopen($file, 'w')) {
            fwrite($fp, '<' . "?php\n\n");
            fwrite($fp, "return array(\n");

            $texts = $event->getTranslatableStrings();
            $translations = $event->getTranslatedStrings();

            // Sort keys alphabetically while keeping index
            asort($texts);

            foreach ($texts as $key => $text) {
                // Write only defined (not empty) translations
                if (! empty($translations[$key])) {
                    $text = str_replace("'", "\'", $text);

                    $translation = str_replace("'", "\'", $translations[$key]);

                    fwrite($fp, sprintf("    '%s' => '%s',\n", $text, $translation));
                }
            }

            fwrite($fp, ");\n");

            @fclose($fp);
        } else {
            throw new \RuntimeException(
                Translator::getInstance()->trans(
                    'Failed to open translation file %file. Please be sure that this file is writable by your Web server',
                    array('%file' => $file)
                )
            );
        }
    }

    public function writeFallbackFile(TranslationEvent $event, $eventName, EventDispatcherInterface $dispatcher)
    {
        $file = THELIA_LOCAL_DIR . 'I18n' . DS . $event->getLocale() . '.php';

        $fs = new Filesystem();
        $translations = [];

        if (! $fs->exists($file)) {
            if (true === $event->isCreateFileIfNotExists()) {
                $dir = dirname($file);
                $fs->mkdir($dir);

                $this->cacheClear($dispatcher);
            } else {
                throw new \RuntimeException(
                    Translator::getInstance()->trans(
                        'Failed to open translation file %file. Please be sure that this file is writable by your Web server',
                        array('%file' => $file)
                    )
                );
            }
        } else {
            /*$loader = new PhpFileLoader();
            $catalogue = $loade     r->load($file);
            $translations = $catalogue->all();
            */
            $translations = require $file;

            if (! \is_array($translations)) {
                $translations = [];
            }
        }

        if ($fp = @fopen($file, 'w')) {
            $texts = $event->getTranslatableStrings();
            $customs = $event->getCustomFallbackStrings();
            $globals = $event->getGlobalFallbackStrings();

            // just reset current translations for this domain to remove strings that do not exist anymore
            $translations[$event->getDomain()] = [];

            foreach ($texts as $key => $text) {
                if (!empty($customs[$key])) {
                    $translations[$event->getDomain()][$text] = $customs[$key];
                }

                if (!empty($globals[$key])) {
                    $translations[$text] = $globals[$key];
                } else {
                    unset($translations[$text]);
                }
            }

            fwrite($fp, '<' . "?php\n\n");
            fwrite($fp, "return [\n");

            // Sort keys alphabetically while keeping index
            ksort($translations);

            foreach ($translations as $key => $text) {
                // Write only defined (not empty) translations
                if (!empty($translations[$key])) {
                    if (\is_array($translations[$key])) {
                        $key = str_replace("'", "\'", $key);
                        fwrite($fp, sprintf("    '%s' => [\n", $key));
                        ksort($translations[$key]);
                        foreach ($translations[$key] as $subKey => $subText) {
                            $subKey = str_replace("'", "\'", $subKey);
                            $translation = str_replace("'", "\'", $subText);
                            fwrite($fp, sprintf("        '%s' => '%s',\n", $subKey, $translation));
                        }
                        fwrite($fp, "    ],\n");
                    } else {
                        $key = str_replace("'", "\'", $key);
                        $translation = str_replace("'", "\'", $text);
                        fwrite($fp, sprintf("    '%s' => '%s',\n", $key, $translation));
                    }
                }
            }

            fwrite($fp, "];\n");

            @fclose($fp);
        }
    }

    protected function normalizePath($path)
    {
        $path = str_replace(
            str_replace('\\', '/', THELIA_ROOT),
            '',
            str_replace('\\', '/', realpath($path))
        );

        return ltrim($path, '/');
    }

    protected function cacheClear(EventDispatcherInterface $dispatcher)
    {
        $cacheEvent = new CacheEvent(
            $this->container->getParameter('kernel.cache_dir')
        );

        $dispatcher->dispatch(TheliaEvents::CACHE_CLEAR, $cacheEvent);
    }

    /**
     * {@inheritdoc}
     */
    public static function getSubscribedEvents()
    {
        return array(
            TheliaEvents::TRANSLATION_GET_STRINGS => array('getTranslatableStrings', 128),
            TheliaEvents::TRANSLATION_WRITE_FILE => [
                ['writeTranslationFile', 128],
                ['writeFallbackFile', 128]
            ]
        );
    }
}
