#!/usr/bin/env php
<?php

require './vendor/autoload.php';

class Antonella
{
    public $dir = __DIR__;

    /** paths */
    public $paths = [
        'controllers' => __DIR__.'/src/Controllers',
        'widgets' => __DIR__.'/src/Widgets',
        'helpers' => __DIR__.'/src/Helpers',
        'classes' => __DIR__.'/src/Classes',
        'blocks' => __DIR__.'/components',
        'stubs' => __DIR__.'/stubs',
        'config' => __DIR__.'/src/Config.php',
        'gutenberg' => __DIR__.'/src/Gutenberg.php',
    ];

    protected $files_to_exclude = [
        '.gitignore',
        '.gitmodules',
        '.gitattributes',
        '.travis.yml',
        'composer.json',
        'composer.lock',
        'package-lock.json',
        'antonella',
        'readme.md',
        'bitbucket-pipelines.yml',
        'CHANGELOG.md',
        'CONTRIBUTING.md',
        'Gruntfile.js',
        'LICENSE',
        'readme.md',
        'README.md',
        'readme.txt',
        'bitbucket-pipelines.yml',
        'wp-cli.phar',
        '.env',
        '.env-example',
    ];
    protected $dirs_to_exclude_win = [
        '.',
        '..',
        '.git',
        '.github',
        'docu',
        'docs',
        'wp-test',
        'framework',
        'node_modules',
        'components',
        'vendor'.DIRECTORY_SEPARATOR.'vlucas',
        'vendor'.DIRECTORY_SEPARATOR.'symfony'.DIRECTORY_SEPARATOR.'polyfill-php80',
        'vendor'.DIRECTORY_SEPARATOR.'symfony'.DIRECTORY_SEPARATOR.'polyfill-mbstring',
        'vendor'.DIRECTORY_SEPARATOR.'symfony'.DIRECTORY_SEPARATOR.'var-dumper',
    ];
    protected $dirs_to_exclude_linux = [
        '.git',
        '.github',
        'docu',
        'docs',
        'wp-test',
        'framework',
        'node_modules',
        'components',
        'vendor'.DIRECTORY_SEPARATOR.'vlucas',
        'vendor'.DIRECTORY_SEPARATOR.'symfony'.DIRECTORY_SEPARATOR.'polyfill-php80',
        'vendor'.DIRECTORY_SEPARATOR.'symfony'.DIRECTORY_SEPARATOR.'polyfill-mbstring',
        'vendor'.DIRECTORY_SEPARATOR.'symfony'.DIRECTORY_SEPARATOR.'var-dumper',
    ];
    protected $testdir = 'wp-test';
    protected $port = '8010';
    protected $locale = 'en_US';
    protected $understant = 'Antonella no understand you. please read the manual in https://antonellaframework.com';

    public function process($data)
    {
        switch ($data[1]) {
            case 'makeup':
                return $this->makeup();
                break;
            case 'namespace':
                return $this->newname($data);
                break;
            case 'make':
                return $this->makeController($data);
                break;
            case 'helper':
                return $this->makeHelper($data);
                break;
            case 'widget':
                return $this->MakeWidget($data);
                break;
            case 'shortcode':
                return $this->makeShortCode($data);
                break;
            case 'remove':
                return $this->Remove($data);
                break;
            case 'add':
                return $this->Add($data);
                break;
            case 'help':
                return $this->Help();
                break;
            case 'serve':
                return $this->Serve($data);
                break;
            case 'test':
                return $this->Test($data);
                break;
            case 'cpt':
                return $this->CustomPost($data);
                break;
            case 'block':
                return $this->makeGutenbergBlock($data);
                break;
            case 'maketheme':
                return $this->makeTheme($data);
                break;
            default:
                echo $this->understant;
                exit();
        }
    }

    /**
     * Devuelve una ruta completa.
     */
    public function getPath($dir, $file = '')
    {
        $dir = strtolower($dir);

        switch ($dir) {
            case 'stubs':
                $ext = '.stub';
                break;
            default:
                $ext = '.php';
        }

        return
            !empty($file) ?
                str_replace('\\', '/', $this->paths[strtolower($dir)].'/'.$file.$ext) :
                    str_replace('\\', '/', $this->paths[strtolower($dir)]);
    }

    /** Devuelve el namespace */
    public function read_namespace()
    {
        $composer = file_get_contents($this->dir.'/composer.json');
        $composer_json = json_decode($composer);
        $psr = $composer_json->autoload->{'psr-4'};
        $namespace = substr(key($psr), 0, -1);

        return $namespace;
    }

    /** enpaqueta el plugin en un fichero zip */
    public function makeup()
    {
        echo "Antonella is packing the plugin \n";
        $SO = strtoupper(substr(PHP_OS, 0, 3));
        if ($SO === 'WIN') {
            $this->makeup_win();
        } else {
            $this->makeup_linux();
        }
        echo "The plugin's zip file is OK!";
    }

    /** comprime para windows */
    public function makeup_win()
    {
        file_exists($this->dir.'/'.basename($this->dir).'.zip') ? unlink($this->dir.'/'.basename($this->dir).'.zip') : false;
        $zip = new ZipArchive();
        $zip->open(basename($this->dir).'.zip', ZipArchive::CREATE);

        $dirName = $this->dir;

        if (!is_dir($dirName)) {
            throw new Exception('Directory '.$dirName.' does not exist');
        }

        $dirName = realpath($dirName);
        if (substr($dirName, -1) != '/') {
            $dirName .= '/';
        }

        /*
        * NOTE BY danbrown AT php DOT net: A good method of making
        * portable code in this case would be usage of the PHP constant
        * DIRECTORY_SEPARATOR in place of the '/' (forward slash) above.
        */

        $dirStack = [$dirName];
        //Find the index where the last dir starts
        $cutFrom = strrpos(substr($dirName, 0, -1), '/') + strlen($this->dir) + 1;

        while (!empty($dirStack)) {
            $currentDir = array_pop($dirStack);
            $filesToAdd = [];

            $dir = dir($currentDir);

            while (false !== ($node = $dir->read())) {
                if (in_array($node, $this->files_to_exclude) || in_array($node, $this->dirs_to_exclude_win)) {
                    continue;
                }
                if (is_dir($currentDir.$node)) {
                    array_push($dirStack, $currentDir.$node.'/');
                }
                if (is_file($currentDir.$node)) {
                    $filesToAdd[] = $node;
                }
            }

            $localDir = substr($currentDir, $cutFrom);

            $zip->addEmptyDir($localDir);
            foreach ($filesToAdd as $file) {
                $zip->addFile($currentDir.$file, $localDir.$file);
                // echo("Added $localDir$file into plugin  \n");
            }
        }

        $zip->close();
    }

    /** comprime para linux */
    public function makeup_linux()
    {
        file_exists($this->dir.'/'.basename($this->dir).'.zip') ? unlink($this->dir.'/'.basename($this->dir).'.zip') : false;

        $zip = new ZipArchive();
        $zip->open(basename($this->dir).'.zip', ZipArchive::CREATE);

        $dirName = $this->dir;

        if (!is_dir($dirName)) {
            throw new Exception('Directory '.$dirName.' does not exist');
        }

        $dirName = realpath($dirName);
        $filesToExclude = $this->files_to_exclude;
        $dirToExclude = $this->dirs_to_exclude_linux;

        $files = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($dirName),
            RecursiveIteratorIterator::LEAVES_ONLY
        );

        foreach ($files as $name => $file) {
            if (!$file->isDir() && !in_array($file->getFilename(), $filesToExclude)) {
                $filePath = $file->getRealPath();
                $relativePath = substr($filePath, strlen($dirName) + 1);
                // Add current file to archive
                $zip->addFile($filePath, $relativePath);
            }
        }

        for ($i = 0; $i < $zip->numFiles; ++$i) {
            $entry_info = $zip->statIndex($i);
            foreach ($dirToExclude as $dirExclude) {
                $pos = strpos($entry_info['name'], $dirExclude);
                if ($pos !== false) {
                    $zip->deleteIndex($i);
                }
            }
        }

        $zip->close();
    }

    /** estable un nuevo namespace */
    public function newname($data)
    {
        echo "Renaming the namespace... \n";
        $newname = (isset($data[2]) && trim($data[2]) != '') ? strtoupper($data[2]) : $this->get_rand_letters(6);
        $slash = DIRECTORY_SEPARATOR;
        $composer = file_get_contents($this->dir.$slash.'composer.json');
        $namespace = $this->read_namespace();
        $core = file_get_contents($this->dir.$slash.'antonella-framework.php');
        $core = str_replace($namespace, $newname, $core);
        $composer = str_replace($namespace, $newname, $composer);
        file_put_contents($this->dir.$slash.'antonella-framework.php', $core);
        file_put_contents($this->dir.$slash.'composer.json', $composer);
        $dirName = $this->dir.$slash.'src';
        $dirName = realpath($dirName);
        if (substr($dirName, -1) != '/') {
            $dirName .= $slash;
        }
        $dirStack = [$dirName];
        while (!empty($dirStack)) {
            $currentDir = array_pop($dirStack);
            $filesToAdd = [];
            $dir = dir($currentDir);
            while (false !== ($node = $dir->read())) {
                if (($node == '..') || ($node == '.')) {
                    continue;
                }
                if (is_dir($currentDir.$node)) {
                    array_push($dirStack, $currentDir.$node.$slash);
                }
                if (is_file($currentDir.$node)) {
                    $file = file_get_contents($currentDir.$node);
                    $file = str_replace($namespace, $newname, $file);
                    file_put_contents($currentDir.$node, $file);
                }
            }
        }
        exec('composer dump-autoload');
        exit("The new namespace is $newname ");
    }

    /**
     * Crea un controllador dentro de la carpeta src/Controllers.
     *
     * @param array $data datos de la linea de comandos donde $data[2] contiene el nombre del controlador
     *                    Example:
     *                    php antonella make ExampleController	out: src/Controllers/ExampleController.php
     *                    php antonella make Admin/AdminController	out: src/Controllers/Admin/AdminController.php
     */
    public function makeController($data)
    {
        $namespace = $this->read_namespace();

        $target = $this->getPath('controllers', $data[2]);
        if (!file_exists(dirname($target))) {
            mkdir(dirname($target), 0755, true);
        }
        // Crea una clase a partir de una fichero plantilla (stubs/controller.stub)
        $StubGenerator = $this->read_namespace().'\Classes\StubGenerator';
        $stub = new $StubGenerator(
            $this->getPath('stubs', 'controller'),	// __DIR__ . '/stubs/controller.stub',
            $target
        );

        $folder = array_reverse(explode('/', dirname($target)))[1];
        $stub->render([
            '%NAMESPACE%' => $namespace.'\\Controllers'.($folder == 'src' ? '' : '\\'.str_replace('/', '\\', dirname($data[2]))),
            '%CLASSNAME%' => array_reverse(explode('/', $data[2]))[0],
        ]);

        exit("\033[01;33m  Controller $data[2].php created into src\Controllers folder \033[0m  \n");
    }

    /**
     *	Crea un widget desde la consola.
     *
     *	@param array $data arguments leidos desde la consola
     *	@param string --enque Optional indica si queremos añadirlo al array widgets de config.php
     *	example => php antonella widget MyFirstWidget
     * 	example => php antonella widget MyFirtWidget --enque
     */
    public function MakeWidget($data)
    {
        $namespace = $this->read_namespace();

        $target = $this->getPath('widgets', $data[2]);
        if (!file_exists(dirname($target))) {
            mkdir(dirname($target), 0755, true);
        }
        // Crea una clase a partir de una fichero plantilla (stubs/controller.stub)
        $StubGenerator = $this->read_namespace().'\Classes\StubGenerator';
        $stub = new $StubGenerator(
            $this->getPath('stubs', 'widget'),	//__DIR__ . '/stubs/widget.stub',
            $target
        );

        $stub->render([
            '%NAMESPACE%' => $namespace.'/Widgets',
            '%CLASSNAME%' => $data[2],
        ]);

        /* Comprobamos si hemos pasado el parametro --enque */
        if (isset($data[3]) && $data[3] == '--enque') {
            $target = $this->paths['config'];							// src/config.php
            $content = explode("\n", file_get_contents($target));

            $this->__search_and_replace($content,
            [
                'public$widgets=[];' => sprintf("\tpublic \$widgets = [ \n\t\t[__NAMESPACE__ . '\Widgets\%s']\n\t];", $data[2]),
                'public$widgets=[' => sprintf("\tpublic \$widgets = [ \n\t\t[__NAMESPACE__ . '\Widgets\%s']", $data[2]),	// append
            ]);

            $newContent = implode("\n", $content);
            file_put_contents($target, $newContent);

            echo "\033[01;33m The Config.php File has been updated \033[0m  \n";
        }

        exit("\033[01;33m The Widget $data[2].php created into src/Widgets folder \033[0m  \n");
    }

    /**
     * Crea un shortCode y lo añade al array $shortcodes[] del fichero config.php.
     *
     * @param array $data Datos de entrada
     *                    Use:
     *                    php antonella shortcode name:Controller@method [--enque]
     */
    public function makeShortCode($data)
    {
        if (count($data) < 3) {
            echo "Parámetos insuficentes\n";
            echo "Example: php antonella shortcode tag:Controller@method [--enque]\n";
            exit;
        }

        list($tag, $callable) = explode(':', $data[2]);
        list($controller, $method) = array_pad(explode('@', $callable), 2, 'short_code');
        $enque = (isset($data[3]) && $data[3] == '--enque' ? true : false);

        $namespace = $this->read_namespace();
        $class = str_replace('/', '\\', sprintf('%s\Controllers\%s', $namespace, $controller));

        /* Si no existe el method o el controller lo añade y/o crea */
        if (!method_exists($class, $method)) {
            $this->__append_method_to_class([
                'class' => $class,
                'method' => $method,
            ]);
            echo "The method $method has been added to Controller $class\n";
        }

        /* Encolamos el metodo al array $actions[] de config.php */
        if ($enque) {
            $target = $this->paths['config'];							// src/config.php
            $content = explode("\n", file_get_contents($target));

            $class = ltrim($class, $namespace); // removemos el namespace
            $this->__search_and_replace($content,
            [
                'public$shortcodes=[];' => sprintf("\tpublic \$shortcodes = [\n\t\t['%s', __NAMESPACE__ . '%s::%s']\n\t];", $tag, $class, $method),
                'public$shortcodes=[' => sprintf("\tpublic \$shortcodes = [\n\t\t['%s', __NAMESPACE__ . '%s::%s']", $tag, $class, $method),	// append
            ]);

            $newContent = implode("\n", $content);
            file_put_contents($target, $newContent);
            echo "\033[01;33m The array shortcodes has been updated \033[0m  \n";
            echo "\033[01;33m The Config.php File has been updated \033[0m  \n";
        }
    }

    /**
     * Crea un fichero helpers para albergar funciones auxiliares.
     *
     * @param array $data argumentos de la linea de comandos
     *                    donde $data[2] representa el nombre del fichero
     *                    Uso:	php antonella helper auxiliares
     *                    Out: src/Helpers/auxiliares.php
     */
    public function makeHelper($data)
    {
        $target = $this->getPath('helpers', $data[2]);
        if (!file_exists(dirname($target))) {
            mkdir(dirname($target), 0755, true);
        }
        // Crea una clase a partir de una fichero plantilla (stubs/controller.stub)
        $StubGenerator = $this->read_namespace().'\Classes\StubGenerator';
        $stub = new $StubGenerator(
            $this->getPath('stubs', 'helper'), 	// __DIR__ . '/stubs/helper.stub',
            $target
        );

        $folder = array_reverse(explode('/', dirname($target)))[0];
        $stub->render([
            '%NAME%' => $data[2],
        ]);

        exit("\033[01;33m The Helper $data[2].php created into src/Helper folder \033[0m  \n");
    }

    /**
     * Elimina el package's instalados mediante composer.
     *
     * @param array $data donde $data[2] contiene el modulo a eliminar
     *                    Uso: php antonella remove blade
     */
    public function Remove($data)
    {
        switch ($data[2]) {
            case 'blade':
                return $this->RemoveBlade();
                break;
            case 'dd':
                return $this->RemoveDD();
                break;
            default:
                echo $this->understant;
                exit();
        }
    }

    /**
     * Permite añadir nuevos modulos de antonella, filter's o action's, en cuanto a modulos solo es posible añadir blade.
     */
    public function Add($data)
    {
        switch ($data[2]) {
            case 'blade':
                return $this->AddBlade();
                break;
            case 'dd':
                return $this->AddDD();
                break;
            case 'action':
                return $this->AddAction($data);
                break;
            case 'filter':
                return $this->AddFilter($data);
                break;
            default:
                echo $this->understant;
                exit();
        }
    }

    /** Elimina blade instalado mediante composer */
    public function RemoveBlade()
    {
        echo "Removing Blade... \n";
        exec('composer remove jenssegers/blade');
        echo "Blade Removed! \n";
    }

    /** Añade el package blade mediante composer para trabajar con vistas blade */
    public function AddBlade()
    {
        echo "You need add blade? (Template system)  Type 'yes' or 'y' to continue: ";
        $handle = fopen('php://stdin', 'r');
        $line = fgets($handle);
        echo $line;
        fclose($handle);
        if (trim($line) === 'yes' || trim($line === 'y')) {
            echo "\n";
            echo "Adding Blade... \n";
            exec('composer require jenssegers/blade');
            echo "Blade Added! \n";
        } else {
            echo "ABORTING!\n";
            echo "Remember: if you need add blade only  type 'php antonella add blade' ";
            exit;
        }
    }

    /** Añade dd
     * @see https://since1979.dev/using-laravel-d-and-dd-helpers-in-wordpress/
     */
    public function AddDD()
    {
        echo "\n";
        echo "Adding Var-Dumper dd() ... \n";
        exec('composer require symfony/var-dumper --dev');
        echo "Var-Dumper dd() Added! \n";
    }

    /** Elimina dd */
    public function RemoveDD()
    {
        echo "Removing Var-Dumper dd()... \n";
        exec('composer remove symfony/var-dumper');
        echo "Var-Dumper dd() Removed! \n";
    }

    public function AddCMB2()
    {
    }

    public function RemoveCMB2()
    {
    }

    /**
     *	Añade un hook de action y lo encola al array $add_action[] del fichero config.php.
     *
     *	@param array $data argumentos de la linea de comando
     *	Uso
     *		php antonella add action tag:Controller@method:prioridad:num_args --enque
     *		php antonella add action tag:Controller@method --enque
     *
     *	@see https://developer.wordpress.org/reference/functions/add_action/
     */
    public function AddAction($data)
    {
        if (count($data) == 3) {
            echo "Parámetos insuficentes\n";
            echo "Example: php antonella add action tag:Controller@method\n";
            echo "Example: php antonella add action tag:Controller@method:priority:args --enque\n";
            exit;
        }

        list($tag, $callable, $priority, $args) = array_pad(explode(':', $data[3]), 4, null);
        $priority = $priority ?? 10; 		// IF IS_NULL asigna le 10
        $args = $args ?? 1; 				// Si IS_NULL asigna le 1
        list($controller, $method) = array_pad(explode('@', $callable), 2, 'index');
        $enque = (isset($data[4]) && $data[4] == '--enque' ? true : false);

        $namespace = $this->read_namespace();
        $class = str_replace('/', '\\', sprintf('%s\Controllers\%s', $namespace, $controller));

        /* Si no existe el method o el controller lo añade y/o crea */
        if (!method_exists($class, $method)) {
            $this->__append_method_to_class([
                'class' => $class,
                'method' => $method,
                'args' => $args, ]);
            echo "The method $method has been added to Controller $class\n";
        }

        /* Encolamos el metodo al array $actions[] de config.php */
        if ($enque) {
            $target = $this->paths['config'];							// src/config.php
            $content = explode("\n", file_get_contents($target));

            $class = ltrim($class, $namespace); // removemos el namespace
            $this->__search_and_replace($content,
            [
                'public$add_action=[];' => sprintf("\tpublic \$add_action = [\n\t\t['%s', [__NAMESPACE__ . '%s','%s'], %s, %s]\n\t];", $tag, $class, $method, $priority, $args),
                'public$add_action=[' => sprintf("\tpublic \$add_action = [\n\t\t['%s', [__NAMESPACE__ . '%s','%s'], %s, %s]", $tag, $class, $method, $priority, $args),	// append
            ]);

            $newContent = implode("\n", $content);
            file_put_contents($target, $newContent);
            echo "\033[01;33m The array add_action has been updated \033[0m  \n";
            echo "\033[01;33m The Config.php File has been updated \033[0m  \n";
        }
    }

    /**
     * 	Añade un hook de tipo filter y lo encola al array $add_filter[] del fichero config.php.
     *
     *	@param array $data argumentos de la linea de comando
     *	Uso
     *		php antonella add filter tag:Controller@method:prioridad:num_args --enque
     *		php antonella add filter tag:Controller@method --enque
     *
     * 	@see https://developer.wordpress.org/reference/functions/add_filter/
     */
    public function AddFilter($data)
    {
        if (count($data) == 3) {
            echo "Parámetos insuficentes\n";
            echo "Example: php antonella add filter tag:Controller@method\n";
            echo "Example: php antonella add filter tag:Controller@method:priority:args --enque\n";
            exit;
        }

        list($tag, $callable, $priority, $args) = array_pad(explode(':', $data[3]), 4, null);
        $priority = $priority ?? 10; 		// IF IS_NULL asigna le 10
        $args = $args ?? 1; 				// Si IS_NULL asigna le 1
        list($controller, $method) = array_pad(explode('@', $callable), 2, 'index');
        $enque = (isset($data[4]) && $data[4] == '--enque' ? true : false);

        $namespace = $this->read_namespace();
        $class = str_replace('/', '\\', sprintf('%s\Controllers\%s', $namespace, $controller));

        /* Si no existe el method o el controller lo añade y/o crea */
        if (!method_exists($class, $method)) {
            $this->__append_method_to_class([
                'class' => $class,
                'method' => $method,
                'args' => $args, ]);
            echo "The method $method has been added to Controller $class\n";
        }

        /* Encolamos el metodo al array $actions[] de config.php */
        if ($enque) {
            $target = $this->paths['config'];							// src/config.php
            $content = explode("\n", file_get_contents($target));

            $class = ltrim($class, $namespace); // removemos el namespace
            $this->__search_and_replace($content,
            [
                'public$add_filter=[];' => sprintf("\tpublic \$add_filter = [\n\t\t['%s', [__NAMESPACE__ . '%s','%s'], %s, %s]\n\t];", $tag, $class, $method, $priority, $args),
                'public$add_filter=[' => sprintf("\tpublic \$add_filter = [\n\t\t['%s', [__NAMESPACE__ . '%s','%s'], %s, %s]", $tag, $class, $method, $priority, $args),	// append
            ]);

            $newContent = implode("\n", $content);
            file_put_contents($target, $newContent);
            echo "\033[01;33m The array add_filter has been updated \033[0m  \n";
            echo "\033[01;33m The Config.php File has been updated \033[0m  \n";
        }
    }

    public function Test($data)
    {
        switch ($data[2]) {
            case'refresh':
                $this->Refresh();
            break;
        }
    }

    public function Refresh($data = false)
    {
        $loader = require __DIR__.'/vendor/autoload.php';
        $dotenv = Dotenv\Dotenv::create(__DIR__);
        $dotenv->load();
        $dbname = getenv('DBNAME');
        $dbuser = getenv('DBUSER');
        $dbpass = getenv('DBPASS');
        $testDIR = getenv('TEST_DIR') ? getenv('TEST_DIR') : $this->testdir;
        $port = getenv('PORT') ? getenv('PORT') : $this->port;
        $locale = getenv('LOCALE') ? getenv('LOCALE') : $this->locale;
        $slash = DIRECTORY_SEPARATOR;
        $pluginname = basename($this->dir);
        $filename = basename($this->dir).'.zip';
        $origen = $this->dir.$slash.$filename;
        $destiny = $this->dir.$slash.basename($testDIR).$slash.'wp-content'.$slash.'plugins'.$slash.$filename;
        $plugindir = $this->dir.$slash.basename($testDIR).$slash.'wp-content'.$slash.'plugins'.$slash.basename($this->dir);
        $force = $data == 'force' ? true : false;
        $extra_php = $force ? ' --force ' : '';
        $this->InstallWPCLI();
        echo " \n";
        if (!file_exists($testDIR) && !is_dir($testDIR)) {
            echo "\033[01;33mFolder Test not exist!!! create the folder...  \033[0m \n";
            mkdir($testDIR);
        }
        echo " \n";
        if (!file_exists($testDIR.$slash.'index.php')) {
            echo "\033[01;32mDownloading WordPress [$locale]...  \033[0m \n";
            exec("php wp-cli.phar core download --locale=$locale --path=$testDIR");
        }
        echo " \n";
        exec("php wp-cli.phar config create --dbname=$dbname --dbuser=$dbuser --dbpass=$dbpass --path=$testDIR  --extra-php=\" define( 'WP_DEBUG', true ); define( 'WP_DEBUG_LOG', true );  \" $extra_php");
        exec("php wp-cli.phar core install --url=localhost:$port --title=\"Antonella Framework Test\" --path=$testDIR --admin_user=test --admin_password=test --admin_email=test@test.com --skip-email");
        $this->makeup();
        echo " \n";
        if (file_exists($plugindir) && is_dir($plugindir)) {
            $it = new RecursiveDirectoryIterator($plugindir, RecursiveDirectoryIterator::SKIP_DOTS);
            $files = new RecursiveIteratorIterator($it,
                        RecursiveIteratorIterator::CHILD_FIRST);
            foreach ($files as $file) {
                if ($file->isDir()) {
                    rmdir($file->getRealPath());
                } else {
                    unlink($file->getRealPath());
                }
            }
            // rmdir($plugindir);
        }
        file_exists($destiny) ? unlink($destiny) : false;
        copy($origen, $destiny);
        $zip = new ZipArchive();
        $res = $zip->open($destiny);
        $zip->extractTo($plugindir);
        $zip->close();
        file_exists($destiny) ? unlink($destiny) : false;
        system("php wp-cli.phar plugin activate $pluginname --path=$testDIR");
        // add Show Current Template
        system("php wp-cli.phar plugin install show-current-template --path=$testDIR --activate");
        echo "\033[01;33mRemember: adminuser:test | password: test  \033[0m \n";
        echo "\033[01;33mYour plugin has been refreshed in test.  \033[0m \n";
        echo "\033[01;33mhttp:\\\\localhost:$port \033[0m \n";
    }

    public function InstallWPCLI()
    {
        if (!file_exists('wp-cli.phar')) {
            exec('curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar');
        }
    }

    public function Serve($data)
    {
        if (!file_exists('.env')) {
            echo "\033[01;33m Antonella response: You need create and config the .env file. you have a .env-example file reference \033[0m ";
            die();
        }
        $loader = require __DIR__.'/vendor/autoload.php';
        $dotenv = Dotenv\Dotenv::create(__DIR__);
        $dotenv->load();
        if (!getenv('DBNAME')) {
            echo "\033[01;33m Antonella response: You need config the DBNAME into .env file \033[0m ";
            die();
        }
        $testDIR = getenv('TEST_DIR') ? getenv('TEST_DIR') : $this->testdir;
        $port = getenv('PORT') ? getenv('PORT') : $this->port;
        $this->Refresh();

        echo " \n";
        exec("php -S localhost:$port --docroot=$testDIR");
    }

    /**
     *	Crea un block de gutenberg haciendo uso de js moderno.
     *
     * 	@params array $data La data para crear el block
     *	Uso
     *		php antonella block namespace/block-name [--callable] [--enque]
     *	Donde
     *		--callable Indica que se trata de un block dynamic y sera renderizado desde php
     *		--enque	Si queremos que se agregue al array $gutenberg_blocks[]
     */
    public function makeGutenbergBlock($data)
    {
        if (isset($data[2]) && $data[2] != '') {
            $block = str_replace('\\', '/', $data[2]);
            if (count(explode('/', $block)) == 1) {
                // si no le proporcionamos un namespace agrega 'antonella' por default
                $block = sprintf('antonella/%s', $block);
            }

            // creamos el directorio dentro de components
            $folder = array_reverse(explode('/', $block))[0];
            $target = str_replace('\\', '/', sprintf(__DIR__.'/components/%s/index.js', $folder));
            if (!file_exists(dirname($target))) {
                mkdir(dirname($target), 0755, true);
                echo sprintf("The directory %s has been created\n", dirname($target));
            }
        }

        // creamos el fichero index.js mediante la plantilla ./stubs/block.stub
        // ó ./stubs/server-side-block.stub
        $StubGenerator = $this->read_namespace().'\Classes\StubGenerator';
        $template = 'block';
        if (in_array('--callable', $data)) {
            $template = 'server-side-block';
        }
        $stub = new $StubGenerator(
            $this->getPath('stubs', $template),	// __DIR__ . '/stubs/block.stub',
            $target
        );
        $stub->render([
            '%BLOCK%' => $block,
            '%TITLE%' => ucwords($folder),
        ]);

        file_put_contents(__DIR__."/components/$folder/editor.css", '/* Your style for editor and front-end */', FILE_APPEND | LOCK_EX);
        file_put_contents(__DIR__."/components/$folder/style.css", '/* Your style for front-end */', FILE_APPEND | LOCK_EX);
        echo "Your block $folder has been created\n";

        if (in_array('--enque', $data)) {
            $target = $this->paths['config'];							// src/config.php
            $content = explode("\n", file_get_contents($target));

            if (!in_array('--callable', $data)) {
                $this->__search_and_replace($content,
                [
                    'public$gutenberg_blocks=[' => sprintf("\tpublic \$gutenberg_blocks = [ \n\t\t'%s' => [],", $block),	// append
                ]);
            } else {
                // Dynamic Block
                $slug = str_replace('/', '_', $block);
                $this->__search_and_replace($content,
                [
                    'public$gutenberg_blocks=[' => sprintf("\tpublic \$gutenberg_blocks = [ \n\t\t'%s' => [\n\t\t\t'render_callback' => __NAMESPACE__ . '\Gutenberg::%s_render_callback'\n\t\t],", $block, $slug),	// append
                ]);

                // Todo append render_callback Gutenber file
                $gutenberg = $this->paths['gutenberg'];	// src/Gutenberg.php
                $contenido = explode("\n", file_get_contents($gutenberg));
                $this->__search_and_replace($contenido, [
                    '}/*generatedwithantonellaframework*/' => sprintf("\n\tpublic static function %s_render_callback(\$attr){\n\t\t\\\\TODO\n\t}\n} /* generated with antonella framework */", $slug),
                ]);

                $nuevoContenido = implode("\n", $contenido);
                file_put_contents($gutenberg, $nuevoContenido);
                echo "\033[01;33m The src/Gutenberg.php File has been updated \033[0m  \n";
            }
            $newContent = implode("\n", $content);
            file_put_contents($target, $newContent);

            echo "\033[01;33m The Config.php File has been updated \033[0m  \n";

            // Todo append component componets/index.js
            $content = explode("\n", file_get_contents(str_replace('\\', '/', __DIR__.'/components/index.js')));
            $found = false;
            foreach ($content as $line) {
                if (trim($line) === sprintf('import "./%s"', $folder)) {
                    $found = true;
                    break;
                }
            }

            if (!$found) {
                file_put_contents(__DIR__.'/components/index.js', sprintf("\nimport \"./%s\";", $folder), FILE_APPEND | LOCK_EX);
                echo "\033[01;33m The components/index.js File has been updated \033[0m  \n";
            }
        }
    }

    public function assign_rand_value($num)
    {
        // accepts 1 - 26
        switch ($num) {
            case '1': $rand_value = 'A'; break;
            case '2': $rand_value = 'B'; break;
            case '3': $rand_value = 'C'; break;
            case '4': $rand_value = 'D'; break;
            case '5': $rand_value = 'E'; break;
            case '6': $rand_value = 'F'; break;
            case '7': $rand_value = 'G'; break;
            case '8': $rand_value = 'H'; break;
            case '9': $rand_value = 'I'; break;
            case '10': $rand_value = 'J'; break;
            case '11': $rand_value = 'K'; break;
            case '12': $rand_value = 'L'; break;
            case '13': $rand_value = 'M'; break;
            case '14': $rand_value = 'N'; break;
            case '15': $rand_value = 'O'; break;
            case '16': $rand_value = 'P'; break;
            case '17': $rand_value = 'Q'; break;
            case '18': $rand_value = 'R'; break;
            case '19': $rand_value = 'S'; break;
            case '20': $rand_value = 'T'; break;
            case '21': $rand_value = 'U'; break;
            case '22': $rand_value = 'V'; break;
            case '23': $rand_value = 'W'; break;
            case '24': $rand_value = 'X'; break;
            case '25': $rand_value = 'Y'; break;
            case '26': $rand_value = 'Z'; break;
        }

        return $rand_value;
    }

    public function get_rand_letters($length)
    {
        if ($length > 0) {
            $rand_id = '';
            for ($i = 1; $i <= $length; ++$i) {
                mt_srand((float) microtime() * 1000000);
                $num = mt_rand(1, 26);
                $rand_id .= $this->assign_rand_value($num);
            }
        }

        return $rand_id;
    }

    public function Help()
    {
        $this->AntonellaLogo();
        echo " \n";
        echo "\033[01;33m Usage:  \033[0m \n";
        echo "\033[01;37m php antonella [option] [name or value] \033[0m \n";
        echo " \n";
        echo "\033[00;33m Options: \033[0m \n";
        echo "\033[00;32m    namespace: \033[0m";
        echo "\033[00;37m                     Generate or regenerate a new namespace for your plugin project. \033[0m \n";
        echo "\033[00;32m    makeup: \033[0m";
        echo "\033[00;37m                        Compress and generate a .zip plugin's file for upload to WordPress. \033[0m \n";
        echo "\033[00;32m    make: \033[0m";
        echo "\033[00;37m                          Generate a new php Controller's file. \033[0m \n";
        echo "\033[00;32m    helper: \033[0m";
        echo "\033[00;37m                        Generate a new php Helper's file. \033[0m \n";
        echo "\033[00;32m    remove: \033[0m";
        echo "\033[00;37m                        Remove Antonella's Modules. Now only is possible remove blade \033[0m \n";
        echo "\033[00;32m    add: \033[0m";
        echo "\033[00;37m                           Add Antonella's Modules. Now only is possible add blade \033[0m \n";
        echo "\033[00;32m    widget: \033[0m";
        echo "\033[00;37m                        Generate a new php file's Class for use Widget \033[0m \n";
        echo "\033[00;32m    serve: \033[0m";
        echo "\033[00;37m                         Generate enviroment for test in a local WordPress \033[0m \n";
        echo "\033[00;32m    test: [refresh] \033[0m";
        echo "\033[00;37m                Regenerate the plugin's files in local server WordPress \033[0m \n";
        echo " \n";
        echo " \n";
        echo "\033[01;37m All Documentation: \033[0m \033[01;33m  https://antonellaframework.com \033[0m \n";
        echo "\033[01;37m See Video Tutorial: \033[0m \033[01;33m https://tipeos.com/anto \033[0m \n";
        // $this->AntonellaIcon();
    }

    public function AntonellaIcon()
    {
        echo "\033[01;33m MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM  \033[0m \n";
        echo "\033[01;33m MMMMMMMMMMMMMMMMMMMMMMMMMMMmymMMMMMMMMMMNhmMMMMMMMMMMMMMMMMMMMMMMMMMMM  \033[0m \n";
        echo "\033[01;33m MMMMMMMMMMMMMMMMMMMMMMMMh+.``.:omMMMMmy+/:::ohMMMMMMMMMMMMMMMMMMMMMMMM  \033[0m \n";
        echo "\033[01;33m MMMMMMMMMMMMMMMMMMMMms:.`````..-:NMMN:///::::::+smMMMMMMMMMMMMMMMMMMMM  \033[0m \n";
        echo "\033[01;33m MMMMMMMMMMMMMMMMMdo:.`````.-:://:NMMN:///:::::::::+shNMMMMMMMMMMMMMMMM  \033[0m \n";
        echo "\033[01;33m MMMMMMMMMMMMMNy+.``````.-://////:NMMN:///:::::::::::::ohNMMMMMMMMMMMMM  \033[0m \n";
        echo "\033[01;33m MMMMMMMMMMms:``````..-:////////ydMMMN:::-..--::::::::::::/smMMMMMMMMMM  \033[0m \n";
        echo "\033[01;33m MMMMMMNy/-``````.-:////////+ydMMMMMMM+.``````.--:::::::::::::ohNMMMMMM  \033[0m \n";
        echo "\033[01;33m MMMMh/.``````.-:////////+ymMMMMMMMMMMMMdo-.`````..-:::::::::::::+hMMMM  \033[0m \n";
        echo "\033[01;33m MMMM+:-..`````..-:///ohNMMMMMMMMMMMMMMMMMMms/.``````.--::::::::::+MMMM  \033[0m \n";
        echo "\033[01;33m MMMM+::::--.``````.-smMMMMMMMMMMMMMMMMMMMMMMMNh+-``````..-:::::::+MMMM  \033[0m \n";
        echo "\033[01;33m MMMM+::::::::-..``````-odMMMMMMMMMMMMMMMMMMMMMMMMy:-.`````..--:::+MMMM  \033[0m \n";
        echo "\033[01;33m MMMM+:::::::::::--.``````./yNMMMMMMMMMMMMMMMMMhs+////:-.``````.--+MMMM  \033[0m \n";
        echo "\033[01;33m MMMMmy+::::::::::::--..``````:sdMMMMMMMMMMMdo////////::-.``````:omMMMM  \033[0m \n";
        echo "\033[01;33m MMMMMMMmh+/::::::::::::-..`````./MMMMMMmho////////:-.``````./smMMMMMMM  \033[0m \n";
        echo "\033[01;33m MMMMMMMMMMMdo/::::::::::::---::/:NMMMy+///////::-.``````-+dMMMMMMMMMMM  \033[0m \n";
        echo "\033[01;33m MMMMMMMMMMMMMMmy/::::::::::::///:NMMN://///:-..`````.:sdMMMMMMMMMMMMMM  \033[0m \n";
        echo "\033[01;33m MMMMMMMMMMMMMMMMMNho:::::::::///:NMMN:/::-.``````-+hNMMMMMMMMMMMMMMMMM  \033[0m \n";
        echo "\033[01;33m MMMMMMMMMMMMMMMMMMMMNmy/:::::////NMMN:-.`````.:sdNMMMMMMMMMMMMMMMMMMMM  \033[0m \n";
        echo "\033[01;33m MMMMMMMMMMMMMMMMMMMMMMMMmyo::+sdMMMMMMd+-`./ymMMMMMMMMMMMMMMMMMMMMMMMM  \033[0m \n";
        echo "\033[01;33m MMMMMMMMMMMMMMMMMMMMMMMMMMMMNMMMMMMMMMMMMNMMMMMMMMMMMMMMMMMMMMMMMMMMMM  \033[0m \n";
    }

    public function AntonellaLogo()
    {
        echo "\033[01;33m *******************************************************************  \033[0m \n";
        echo "\033[01;33m *******************************************************************  \033[0m \n";
        echo "\033[01;33m ***                    _                   _ _                  ***  \033[0m \n";
        echo "\033[01;33m ***       /\         | |                 | | |                  ***  \033[0m \n";
        echo "\033[01;33m ***      /  \   _ __ | |_ ___  _ __   ___| | | __ _             ***  \033[0m \n";
        echo "\033[01;33m ***     / /\ \ | '_ \| __/ _ \| '_ \ / _ \ | |/ _` |            ***  \033[0m \n";
        echo "\033[01;33m ***    / ____ \| | | | || (_) | | | |  __/ | | (_| |            ***  \033[0m \n";
        echo "\033[01;33m ***   /_/____\_\_| |_|\__\___/|_| |_|\___|_|_|\__,_|    _       ***  \033[0m \n";
        echo "\033[01;33m ***   |  ____|                                         | |      ***  \033[0m \n";
        echo "\033[01;33m ***   | |__ _ __ __ _ _ __ ___   _____      _____  _ __| | __   ***  \033[0m \n";
        echo "\033[01;33m ***   |  __| '__/ _` | '_ ` _ \ / _ \ \ /\ / / _ \| '__| |/ /   ***  \033[0m \n";
        echo "\033[01;33m ***   | |  | | | (_| | | | | | |  __/\ V  V / (_) | |  |   <    ***  \033[0m \n";
        echo "\033[01;33m ***   |_|  |_|  \__,_|_| |_| |_|\___| \_/\_/ \___/|_|  |_|\_\   ***  \033[0m \n";
        echo "\033[01;33m ***                                                             ***  \033[0m \n";
        echo "\033[01;33m *******************************************************************  \033[0m \n";
        echo "\033[01;33m *******************************************************************  \033[0m \n";
    }

    /**
     * CustomPost function
     * crea dentro del array post_types en config.php un nuevo custom.
     *
     * @author Alberto Leon <email@email.com>
     *
     * @version 1.0.0
     *
     * @param array $data el dato que viene desde la consola
     *
     * @return void
     */
    public function CustomPost($data)
    {
        if (isset($data[2]) and $data[2] != '') {
            // Abrir el archivo
            $slash = DIRECTORY_SEPARATOR;
            $archivo = $this->dir.$slash.'src'.$slash.'Config.php';
            $abrir = fopen($archivo, 'r+');
            $contenido = fread($abrir, filesize($archivo));
            fclose($abrir);
            //Separar linea por linea
            $contenido = explode("\n", $contenido);
            //Modificar linea deseada
            for ($i = 0; $i < sizeof($contenido); ++$i) {
                if (strpos($contenido[$i], 'public $post_types =[') !== false) {
                    $contenido[$i] = '    public $post_types =[
        [
            "singular"      => "'.$data[2].'",
            "plural"        => "'.$data[2].'s",
            "slug"          => "'.$data[2].'",
            "position"      => 99,
            "taxonomy"      => [],
            "image"         => "antonella-icon.png",
            "gutemberg"     => true
        ],
';
                }
            }
            $contenido = implode("\n", $contenido);
            file_put_contents($archivo, $contenido);
            echo "\033[01;33m Add new Custom PostType {$data[2]} in config.php file \033[0m ";
        } else {
            echo "\033[01;33m Antonella reponse: need a custom PostType's Name \033[0m ";
        }
    }

    /**
     *	Generates starter code for a theme based on _s.
     *
     *	@see See the Underscores website for more details
     *	Example php antonella maketheme <slug> [--activate] [--theme_name=<title>] [--author=<full-name>] [--author_uri=<uri>] [--sassify] [--woocommerce] [--force]
     */
    public function makeTheme($data)
    {
        $testDIR = getenv('TEST_DIR') ? getenv('TEST_DIR') : $this->testdir;
        $diff = array_values(array_diff($data, ['antonella', 'maketheme', '_s', '--force']));

        $editor = str_replace('\\', '/', realpath(sprintf('%s/wp-content/themes/%s/.editorconfig', $testDIR, $diff[0])));
        if (file_exists($editor)) {
            @unlink($editor);
        }

        $scaffold = 'scaffold _s '.implode(' ', $diff);
        //exec("php wp-cli.phar $scaffold --path=$testDIR --force"); // no muestra los avisos en consola
        system("php wp-cli.phar $scaffold --path=$testDIR --force"); // muestra los avisos en consola
    }

    /**
     *	PRIVATE FUNTIONS.
     */

    /**
     * Busca y remplaza un valor sobre memoria.
     *
     * @param array $content Contenido del fichero donde cada posicon del aray contine una linea del fichero
     * @param array $search  Array asociativo que tiene como clave el valor a buscar y como valor el dato de remplazo, haciendo dicho remplazo sobre memoria
     */
    private function __search_and_replace(&$content, $search)
    {
        foreach ($content as $key => $line) {
            $value = preg_replace('/\s+/', '', $line);

            $coma = '';
            if (isset($content[$key + 1])) {
                $coma = substr(preg_replace('/\s+/', '', $content[$key + 1]), 0, 1) === '[' ? ',' : '';
            }

            $content[$key] = isset($search[$value]) ? $search[$value].$coma : $line;
        }
    }

    /**
     *	Añade un nuevo method a una clase, si el controlador no existe se crea.
     *
     *	@param array $data La Data para crear el Controlador y el Method
     */
    private function __append_method_to_class($data)
    {
        $args = 1;
        extract($data);
        $markwater = '}/*generatedwithantollenaframework*/';

        $controller = ltrim($class, sprintf('%s\\Controllers\\', $this->read_namespace()));
        $target = $this->getPath('controllers', $controller);

        if (file_exists($target)) {
            // append method
            $content = explode("\n", file_get_contents($target));
            $result = 'append method class';

            // patrones a buscar
            $pattern = [
                '}/*generatedwithantollenaframework*/',
                '}',
            ];

            $params = [];
            if ($args > 1 && isset($args)) {
                for ($i = 1; $i <= $args; ++$i) {
                    $params[] = '$arg'.$i;
                }
            }
            $args = implode(', ', $params);

            $found = false;
            foreach ($content as $key => $line) {
                $value = preg_replace('/\s+/', '', $line);
                if ($value === $pattern[0]) {
                    $found = true;
                    $content[$key] = sprintf("\tpublic static function %s(%s){\n\t\t//TODO\n\t}\n\n} /* generated with antollena framework */", $method, $args);
                    echo "Method Class Added\n";
                    break;
                }
            }

            if (!$found) {
                // buscamos la 1ra llave de cierre comenzando desde el final del fichero
                $i = count($content) - 1;
                while ($i >= 0 && !$found) {
                    $value = preg_replace('/\s+/', '', $line);
                    if ($value === $pattern[1]) {
                        $found = true;
                        $content[$key] = sprintf("\tpublic static function %s(%s){\n\t\t//TODO\n\t}\n\n} /* generated with antollena framework */", $method, $args);
                        echo "Method Class Added\n";
                        break;
                    }
                    --$i;
                }
            }

            $newContent = implode("\n", $content);
            file_put_contents($target, $newContent);
        } else {
            // crate controller and method class
            $result = 'created controller & method class';

            $classname = array_reverse(explode('\\', $class))[0];
            $namespace = rtrim($class, '\\'.$classname);

            $StubGenerator = $this->read_namespace().'\Classes\StubGenerator';
            $stub = new $StubGenerator(
                $this->getPath('stubs', 'controller-with-method'),	//__DIR__ . '/stubs/controller-with-method.stub',
                $target
            );

            $params = [];
            if ($args > 1 && isset($args)) {
                for ($i = 1; $i <= $args; ++$i) {
                    $params[] = '$arg'.$i;
                }
            }
            $args = implode(', ', $params);

            $stub->render([
                '%NAMESPACE%' => $namespace,
                '%CLASSNAME%' => $classname,
                '%METHOD%' => $method,
                '%PARAMS%' => $args,
            ]);
        }
    }
}

$antonella = new Antonella();
exit($antonella->process($argv));
