<?php

namespace Tobuli\Services;

use App\Events\TranslationUpdated;
use \File;

class TranslationService
{
    private $files;

    public function __construct()
    {
        $this->files = [
            'front',
            'admin',
            'global',
            'validation',
        ];
    }

    /**
     * Get translation file names
     *
     * @return Array
     */
    public function getFiles()
    {
        return $this->files;
    }

    /**
     * Save translations
     *
     * @param  String $lang         Translated language
     * @param  Array  $translations Translations array grouped by file
     * @return Array
     */
    public function save($lang, $translations)
    {
        $trans = array_dot($translations);
        $this->writeStorageFile($lang, $trans);
        $this->writeTranslationFiles($lang, $trans);
    }

    /**
     * Update server's translation files
     *
     * @return Array
     */
    public function updateTranslationFiles()
    {
        $this->copyTransFilesToOriginal();

        $langs = glob(storage_path('langs/*'));

        foreach ($langs as $lang) {
            $arr = explode('/', $lang);
            $files = glob($lang.'/*');
            $translations = [];
            $language = end($arr);

            foreach ($files as $file) {
                $fileName = pathinfo($file, PATHINFO_FILENAME);
                $trans = include($file);
                $translations = $this->mergeTranslations($translations, array_dot($trans, $fileName.'.'));
            }

            $this->writeTranslationFiles($language, $translations);
        }
    }

    /**
     * Get translations file content
     *
     * @param  String  $file     Specific translations file name
     * @param  String  $lang     Translations language
     * @param  Bool    $original Wheter to use original translations folder or standart
     * @return String
     */
    public function getTranslations($key, $lang, $original = false)
    {
        $translations = [];

        try {
            list($file, $transKey) = explode('.', $key, 2);
        } catch (\Exception $e) { }

        $files = $this->files;

        if (isset($file)) {
            $files = array_intersect($files, [$file]);
        }

        foreach ($files as $currFile) {
            if ($original) {
                $path = $this->getOriginalBasePath($currFile, $lang);
            } else {
                $path = $this->getBasePath($currFile, $lang);
            }

            $translations[$currFile] = include($path);
        }

        $translations = array_dot($translations);

        if (isset($transKey)) {
            return array_get($translations, $key);
        } else {
            return $translations;
        }
    }

    /**
     * Get translation file text with new translations
     *
     * @param  Array  $translations    Previous translations
     * @param  Array  $newTranslations New translations
     * @param  Bool   $useStorage      Whether to use translations from storage
     * @return String
     */
    private function parseTranslations($lang, $data, $useStorage = false)
    {
        $result = [];

        $originalTranslations = $useStorage
            ? $this->getStorageTranslations($lang, $data)
            : $this->getOriginalTranslations($lang, $data);
        $arr = $this->mergeTranslations($originalTranslations, $data);
        $translations = array_undot($arr);

        foreach ($translations as $file => $newTranslations) {
            $result[$file] = $this->formatFileContent($newTranslations);
        }

        return $result;
    }

    /**
     * Merge two translation arrays
     *
     * @param  Array  $translations    Previous translations
     * @param  Array  $newTranslations New translations
     * @return String
     */
    private function mergeTranslations($translations, $newTranslations)
    {
        return array_replace_recursive($translations, $newTranslations);
    }

    /**
     * Wraps data in php tag
     *
     * @param  String $data Data to be inserted
     * @return String
     */
    private function formatFileContent($data)
    {
        return "<?php\nreturn ".exportVar($data, '<br>').";\n";
    }

    /**
     * Finds placeholders in string
     *
     * @param  String $string
     * @return Array
     */
    private function getPlaceholdersInString($string)
    {
        preg_match_all('/:\w+/', $string, $matches);

        return empty($matches[0]) ? [] : $matches[0];
    }

    /**
     * Get existing placeholders in specific translation
     *
     * @param  String  $file File name to get translations
     * @param  String  $key  Translation key
     * @return Array
     */
    public function getPlaceholders($key)
    {
        $result = [];

        $translation = $this->getTranslations($key, 'en', true);

        if ($translation) {
            $result = $this->getPlaceholdersInString($translation);
        }

        return $result;
    }

    /**
     * Write new data to specified translation file
     *
     * @param  String  $lang Translations language
     * @param  String  $data New content to be put in translations file
     * @return Bool
     */
    private function writeTranslationFiles($lang, $data)
    {
        $parsedTranslations = $this->parseTranslations($lang, $data);

        foreach ($parsedTranslations as $file => $translations) {
            $filePath = $this->getBasePath($file, $lang);
            @chmod($filePath, 0777);

            if (!file_put_contents($filePath, $translations)) {
                throw new \Exception(trans('global.cant_write_to_file', ['file' => trans('admin.translations')]));
            }
        }
    }

    /**
     * Write new data to specified translation file in storage folder
     *
     * @param  String  $lang Translations language
     * @param  String  $data New content to be put in translations file
     * @return Bool
     */
    private function writeStorageFile($lang, $data)
    {
        $parsedTranslations = $this->parseTranslations($lang, $data, true);

        foreach ($parsedTranslations as $file => $translations) {
            $filePath = $this->getStoragePath($lang, $file);

            if (!file_put_contents($filePath, $translations)) {
                throw new \Exception(trans('global.cant_write_to_file', ['file' => trans('admin.storage')]));
            }

            event(new TranslationUpdated($file, $translations));
        }
    }

    /**
     * Get path of storage translations file
     *
     * @param  String  $file Specific translations file name
     * @param  String  $lang Translations language
     * @return String
     */
    private function getStoragePath($lang, $file)
    {
        $dir = storage_path("langs/{$lang}");

        if (strlen($dir) > 0 && !is_dir($dir)) {
            if (!@mkdir($dir, 0777, true)) {
                throw new \Exception(trans('global.cant_create_path', ['path' => trans('admin.storage')]));
            }
        }

        return "{$dir}/{$file}.php";
    }

    /**
     * Get base path of translations location
     *
     * @param  String  $file Specific translations file name. Cannot be used without language param
     * @param  String  $lang Translations language
     * @return String
     */
    private function getBasePath($file = null, $lang = null)
    {
        return base_path('resources/lang'.$this->formatLanguageAndFile($file, $lang));
    }

    /**
     * Get base path of original translations location
     *
     * @param  String  $file Specific translations file name. Cannot be used without language param
     * @param  String  $lang Translations language
     * @return String
     */
    private function getOriginalBasePath($file = null, $lang = null)
    {
        return base_path('resources/original_lang'.$this->formatLanguageAndFile($file, $lang));
    }

    /**
     * Returns language and file part of path
     *
     * @param  String  $file Specific translations file name. Cannot be used without language param
     * @param  String  $lang Translations language
     * @return String
     */
    private function formatLanguageAndFile($file = null, $lang = null)
    {
        $result = [];

        if ($lang) {
            $result['lang'] = '/'.$lang;

            if ($file) {
                $result['file'] = '/'.$file.(!pathinfo($file, PATHINFO_EXTENSION) ? '.php' : '');
            }
        }

        return implode('', $result);
    }

    /**
     * Get storage translations
     *
     * @param  String $lang Translations language
     * @param  Array  $trans Array of translation key and value pairs
     * @return Array
     */
    private function getStorageTranslations($lang, $trans)
    {
        $storageTranslations = [];
        $translations = array_undot($trans);

        foreach ($translations as $file => $transKeys) {
            $filePath = $this->getStoragePath($lang, $file);

            if (is_file($filePath)) {
                $fileTranslations = array_dot(include($filePath), $file.'.');
                $storageTranslations = $this->mergeTranslations($storageTranslations, $fileTranslations);
            }
        }

        return $storageTranslations;
    }

    /**
     * Get original translations
     *
     * @param  String $lang Translations language
     * @param  Array  $trans Array of translation key and value pairs
     * @return Array
     */
    private function getOriginalTranslations($lang, $trans)
    {
        $originalTranslations = [];
        $translations = array_undot($trans);

        foreach ($translations as $file => $transKeys) {
            if (!is_file($this->getBasePath($file, $lang))) {
                if (!is_file($this->getOriginalBasePath($file, $lang))) {
                    $fileTranslations = $this->getTranslations($file, 'en', true);
                } else {
                    $fileTranslations = $this->getTranslations($file, $lang, true);
                }
            } else {
                $fileTranslations = $this->getTranslations($file, $lang);
            }

            $originalTranslations = $this->mergeTranslations($originalTranslations, $fileTranslations);
        }

        return $originalTranslations;
    }

    /**
     * Copies current translations to original destination
     *
     */
    private function copyTransFilesToOriginal()
    {
        $current = $this->getBasePath();
        $original = $this->getOriginalBasePath();

        if (!File::exists($original)) {
            File::makeDirectory($original, $mode = 0777, true, true);
        }

        File::copyDirectory($current, $original);
    }
}
