<?php

/**
 * Class FileBuilder
 * Generates the module files for the new module.
 *
 * @copyright MTC media Ltd.
 * @author Rihards Silins
 *
 * @version 2 13/05/2016
 */
namespace Mtc\Modules\ModuleBuilder\Classes\Builders;

class FileBuilder
{
    /**
     * module folder name.
     *
     * @var string
     */
    private $folder_name;
    /**
     * module class name.
     *
     * @var string
     */
    private $class_name;
    /**
     * module class namespace prefix.
     *
     * @var string
     */
    private $namespace_prefix;
    /**
     * module class escaped namespace prefix.
     *
     * @var string
     */
    private $escaped_namespace_prefix;
    /**
     * module class namespace.
     *
     * @var string
     */
    private $namespace;
    /**
     * database table name.
     *
     * @var string
     */
    private $table_name;
    /**
     * module model name in singular.
     *
     * @var string
     */
    private $module_name_singular;
    /**
     * module model name in plural.
     *
     * @var string
     */
    private $module_name_plural;
    /**
     * the settings code template from which to create settings file.
     *
     * @var string
     */
    private $settings_template;
    /**
     * the eloquent class code template from which to create eloquent class file.
     *
     * @var string
     */
    private $eloquent_class_template;
    /**
     * results of the build process.
     *
     * @var string[]
     */
    public $result;
    /**
     * whether or not to overwrite files during the module creation process
     *
     * @var bool
     */
    public $overwrite_files = true;

    /**
     * FileBuilder::setFolderName()
     * Set folder name of the module to be built.
     *
     * @param string folder_name
     *
     * @return bool success
     */
    public function setFolderName($folder_name)
    {
        // validate
        if (preg_match('/^[a-z_A-Z-]+$/', $folder_name)) {
            $this->folder_name = $folder_name;

            return true;
        }

        return false;
    }

    /**
     * FileBuilder::setClassName()
     * Set class name of the module to be built.
     *
     * @param string class_name
     *
     * @return bool success
     */
    public function setClassName($class_name)
    {
        // validate
        if (preg_match('/^[a-z_A-Z]+$/', $class_name)) {
            $this->class_name = $class_name;

            return true;
        }

        return false;
    }

    /**
     * FileBuilder::setNamespacePrefix()
     * Set Namespace Prefix folder of the module to be built.
     *
     * @param string namespace_prefix
     *
     * @return bool success
     */
    public function setNamespacePrefix($namespace_prefix)
    {
        $this->namespace_prefix = $namespace_prefix;
        $this->escaped_namespace_prefix = str_replace("\\\\", "\\", $namespace_prefix);

        return true;
    }

    /**
     * FileBuilder::setNamespace()
     * Set Namespace Prefix folder of the module to be built.
     *
     * @param string namespace
     *
     * @return bool success
     */
    public function setNamespace($namespace)
    {
        $this->namespace = $namespace;

        return true;
    }

    /**
     * FileBuilder::setTableName()
     * Set table name of module table.
     *
     * @param string table_name
     *
     * @return bool success
     */
    public function setTableName($table_name)
    {
        // validate
        if (preg_match('/^[a-z_A-Z-]+$/', $table_name)) {
            $this->table_name = $table_name;

            return true;
        }

        return false;
    }

    /**
     * FileBuilder::setModuleNameSingular()
     * Set module name of the module to be built.
     *
     * @param string module_name_singular
     *
     * @return bool success
     */
    public function setModuleNameSingular($module_name_singular)
    {
        $this->module_name_singular = $module_name_singular;

        return true;
    }

    /**
     * FileBuilder::setModuleNamePlural()
     * Set module name of the module to be built.
     *
     * @param string module_name_plural
     *
     * @return bool success
     */
    public function setModuleNamePlural($module_name_plural)
    {
        $this->module_name_plural = $module_name_plural;

        return true;
    }

    /**
     * FileBuilder::setSettingsTemplate()
     * Set settings template from which the module settings file is to be built.
     *
     * @param string folder_name
     *
     * @return bool success
     */
    public function setSettingsTemplate($settings_template_path)
    {
        $settings_template = file_get_contents($settings_template_path);

        if (empty($settings_template)) {
            return false;
        }

        $this->settings_template = $settings_template;

        return true;
    }

    /**
     * FileBuilder::setEloquentClassTemplate()
     * Set eloquent class template from which the module main class is to be built.
     *
     * @param string folder_name
     *
     * @return bool success
     */
    public function setEloquentClassTemplate($eloquent_class_template_path)
    {
        $eloquent_class_template = file_get_contents($eloquent_class_template_path);

        if (empty($eloquent_class_template)) {
            return false;
        }

        $this->eloquent_class_template = $eloquent_class_template;

        return true;
    }

    /**
     * FileBuilder::build()
     * Generates the files of the module.
     *
     * @return string[][] result
     */
    public function build()
    {
        if (empty($this->module_name_singular)) {
            return false;
        }

        if (empty($this->module_name_plural)) {
            return false;
        }

        if (empty($this->folder_name)) {
            return false;
        }

        if (empty($this->settings_template)) {
            return false;
        }

        if (empty($this->eloquent_class_template)) {
            return false;
        }

        $this->result = array();

        if (! file_exists('../../'.$this->folder_name)) {
            mkdir('../../'.$this->folder_name);
        }

        // Copy admin files
        if (empty($this->admin_panel_root)) {
            $this->admin_panel_root = '../../'.$this->folder_name.'/admin';
        }
        $this->recurseCopyModuleFiles('module_files/admin/', $this->admin_panel_root, $this->overwrite_files);

        if (! empty($this->module_helper_class_namespace)) {
            $this->escaped_module_helper_class_namespace_prefix = str_replace(
                "\\\\",
                "\\",
                $this->module_helper_class_namespace,
            );
        } else {
            $this->recurseCopyModuleFiles(
                'module_files/Classes/ModuleHelper.php',
                '../../'.$this->folder_name.'/Classes/ModuleHelper.php',
                $this->overwrite_files,
            );
            $this->escaped_module_helper_class_namespace_prefix = $this->escaped_namespace_prefix;
        }

        if (! empty($this->module_actions_class_namespace)) {
            $this->escaped_module_action_class_namespace_prefix = str_replace(
                "\\\\",
                "\\",
                $this->module_actions_class_namespace,
            );
        } else {
            $this->recurseCopyModuleFiles(
                'module_files/Classes/ModuleActions.php',
                '../../'.$this->folder_name.'/Classes/ModuleActions.php',
                $this->overwrite_files,
            );
            $this->escaped_module_action_class_namespace_prefix = $this->escaped_namespace_prefix;
        }

        if (! empty($this->model_namespace)) {
            $this->namespace = 'namespace '.$this->model_namespace.';';
            $this->namespace_prefix = addslashes($this->model_namespace."\\");
        }

        if (empty($this->model_class_location)) {
            $this->model_class_location = '../../'.$this->folder_name.'/Classes/';
        }

        $placeholders = array(
            '{{{CLASS_NAME}}}' => $this->class_name,
            '{{{NAMESPACE}}}' => $this->namespace,
            '{{{NAMESPACE_PREFIX}}}' => $this->namespace_prefix,
            '{{{ESCAPED_NAMESPACE_PREFIX}}}' => $this->escaped_namespace_prefix,
            '{{{ESCAPED_MODULE_HELPER_CLASS_NAMESPACE_PREFIX}}}' => $this->escaped_module_helper_class_namespace_prefix,
            '{{{ESCAPED_MODULE_ACTION_CLASS_NAMESPACE_PREFIX}}}' => $this->escaped_module_action_class_namespace_prefix,
            '{{{TABLE_NAME}}}' => $this->table_name,
            '{{{FOLDER_NAME}}}' => $this->folder_name,
            '{{{MODULE_NAME_SINGULAR}}}' => $this->module_name_singular,
            '{{{MODULE_NAME_PLURAL}}}' => $this->module_name_plural,
        );

        $this->recursivelyFillInPlaceholdersForDir($this->admin_panel_root, $placeholders);

        // Create a settings file
        $settings_code = $this->fillInCodeTemplate(
            $this->settings_template,
            $placeholders,
        );

        if (file_exists($this->admin_panel_root.'/includes/settings.php')) {
            $this->result[] = 'Setting file unchanged. It already existed!';
        } else {
            file_put_contents($this->admin_panel_root.'/includes/settings.php', $settings_code);
        }

        // Create main module class
        $eloquent_class_code = $this->fillInCodeTemplate(
            $this->eloquent_class_template,
            $placeholders,
        );

        if (file_exists($this->model_class_location.'/'.$this->class_name.'.php')) {
            $this->result[] = 'Class file unchanged. It already existed!';
        } else {
            if (! file_exists($this->model_class_location)) {
                mkdir($this->model_class_location, 0777, true);
            }
            file_put_contents($this->model_class_location.'/'.$this->class_name.'.php', $eloquent_class_code);
        }
        $this->recursivelyFillInPlaceholdersForDir($this->model_class_location, $placeholders);

        return $this->result;
    }

    /**
     * FileBuilder::recurseCopyModuleFiles()
     * Recurse copy module file.
     *
     * @param string source_path
     * @param string destination_path
     * @param bool $overwrite_files
     */
    private function recurseCopyModuleFiles($source_path, $destination_path, $overwrite_files = true)
    {
        if (! file_exists($source_path)) {
            return;
        }
        if (! is_dir($source_path)) {
            if (file_exists($destination_path)) {
                if (! preg_match('/includes$/', $destination_path)) {
                    $this->result[] = $destination_path.' updated!';
                    copy($source_path, $destination_path);
                }
            } else {
                if (! file_exists(dirname($destination_path))) {
                    mkdir(dirname($destination_path), 0777, true);
                }
                copy($source_path, $destination_path);
            }

            return;
        }
        $dir = opendir($source_path);
        if (! file_exists($destination_path)) {
            mkdir($destination_path);
        }
        while (false !== ($file = readdir($dir))) {
            if ($file != '.' && $file != '..') {
                if (is_dir($source_path.'/'.$file)) {
                    $this->recurseCopyModuleFiles(
                        $source_path.'/'.$file,
                        $destination_path.'/'.$file,
                        $overwrite_files,
                    );
                } else {
                    if (file_exists($destination_path.'/'.$file)) {
                        if (! preg_match('/includes$/', $destination_path) && $overwrite_files) {
                            $this->result[] = $destination_path.'/'.$file.' updated!';
                            copy($source_path.'/'.$file, $destination_path.'/'.$file);
                        }
                    } else {
                        copy($source_path.'/'.$file, $destination_path.'/'.$file);
                    }
                }
            }
        }
        closedir($dir);
    }

    /**
     * Recurse through a directory and replace their file placeholders
     * @param string $directory
     * @param mixed[] $placeholders
     */
    protected function recursivelyFillInPlaceholdersForDir($directory, $placeholders)
    {
        $dir = opendir($directory);
        while (false !== ($file = readdir($dir))) {
            if ($file != '.' && $file != '..') {
                if (is_dir($directory.'/'.$file)) {
                    $this->recursivelyFillInPlaceholdersForDir(
                        $directory.'/'.$file,
                        $placeholders,
                    );
                } elseif (preg_match("/\.php$/", $file)) {
                    $file_template = file_get_contents($directory.'/'.$file);
                    $directory_count_to_root = substr_count(substr(realpath($directory), strlen(SITE_PATH)), '/');
                    $placeholders['{{{PATH_TO_ROOT}}}'] = '';
                    for ($i = 0; $i < $directory_count_to_root; $i++) {
                        $placeholders['{{{PATH_TO_ROOT}}}'] .= '../';
                    }
                    $file_template = $this->fillInCodeTemplate(
                        $file_template,
                        $placeholders,
                    );
                    file_put_contents($directory.'/'.$file, $file_template);
                }
            }
        }
        closedir($dir);
    }

    /**
     * FileBuilder::fillInCodeTemplate()
     * Fill in code template.
     *
     * @param string template_code
     * @param string[] replacements
     *
     * @return string template_code
     */
    private function fillInCodeTemplate($template_code, $replacements)
    {
        foreach ($replacements as $key => $value) {
            $template_code = str_replace(
                $key,
                $value,
                $template_code,
            );
        }

        return $template_code;
    }
}
