<?php

namespace Dipper\Console\Commands;

use Exception;
use Illuminate\Support\Str;
use Dipper\Console\PackageManifest;
use Dipper\Console\Components\Domain;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;

/**
 * @author HollyTeng <n.haoyuan@gmail.com>
 */
class DomainMakeCommand extends GeneratorCommand
{
    /**
     * The base namespace for this command.
     *
     * @var string
     */
    private $namespace;

    /**
     * The Domains path.
     *
     * @var string
     */
    private $path;

    /**
     * The console command name.
     *
     * @var string
     */
    protected $name = 'make:domain';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Create a new Domain';

    /**
     * 保留关键字
     *
     * @var array
     */
    protected $reserved_keyword = [
        'name',
        'env',
        'key',
        'url',
        'debug',
        'cipher',
        'locale',
        'aliases',
        'timezone',
        'providers',
        'fallback_locale',
    ];

    /**
     * The directories to be created under the service directory.
     *
     * @var array
     */
    protected $directories = [
        'Routes',
        'Http/',
        'Http/Controllers/',
        'Providers/',
    ];

    /**
     * The directories to be created under the service directory.
     *
     * @var array
     */
    protected $directoriesFull = [
        'Database/',
        'Database/migrations/',
        'Database/seeds/',
        'Database/factories/',
        'Resources/',
        'Resources/lang/',
        'Resources/views/',
        'Console/',
        'Http/Middleware/',
        'Http/Requests/',
        'Repositories',
        'Services/',
        'Tests/',
        'Tests/Services/',
    ];

    /**
     * Get the stub file for the generator.
     *
     * @return string
     */
    protected function getStub()
    {
    }

    /**
     * Execute the console command.
     *
     * @return bool|null
     */
    public function fire()
    {
        try {
            $name = $this->argument('name');

            if (in_array($name, $this->reserved_keyword)) {
                $this->error("The word '$name' is a reserved keyword. Please enter another word to replace！");
                return false;
            }

            $domain = $this->generate($name);

            (new PackageManifest)->build();
            
            $this->info('Domain ' . $domain->name . ' created successfully.' . "\n");

            $this->info('and you can create database migration use:' . "\n");

            $this->info('<comment>php artisan make:migration --path=app/Domains/'.$domain->name.'/Database/migrations</comment>' . "\n");
        } catch (\Exception $e) {
            $this->error($e->getMessage() . "\n" . $e->getFile() . ' at ' . $e->getLine());
        }
    }

    public function generate($name)
    {
        $name = Str::studly($name);
        $slug = Str::snake($name, '-');
        $path = $this->findDomainPath($name);

        if ($this->exists($path)) {
            throw new Exception('Domain already exists!');

            return false;
        }

        // create service directory
        $this->createDirectory($path);
        // create .gitkeep file in it
        $this->createFile($path . '/.gitkeep');

        $this->createDomainDirectories($path);

        $this->addComposerFile($name, $slug, $path);

        $this->addServiceProviders($name, $slug, $path);

        $this->addRoutesFiles($name, $slug, $path);

        if ($this->isFull()) {
            $this->addWelcomeViewFile($path);
            $this->addModelFactory($path);
            $this->addConfigFile($path);
        }

        return new Domain(
            $name,
            $slug,
            $path,
            $this->relativeFromReal($path)
        );
    }

    /**
     * Create the default directories at the given service path.
     *
     * @param  string $path
     *
     * @return void
     */
    public function addComposerFile($name, $slug, $path)
    {
        $domainProvider = $this->getDomainServiceProvider($name);

        $composerFile = file_get_contents(__DIR__ . '/stubs/composer.stub');
        $composerFile = str_replace(['{{slug}}', '{{provider}}'], [$slug, str_replace('\\', '\\\\', $domainProvider)], $composerFile);

        $this->createFile($path . '/composer.json', $composerFile);
        unset($composerFile);
    }

    /**
     * Create the default directories at the given service path.
     *
     * @param  string $path
     *
     * @return void
     */
    public function createDomainDirectories($path)
    {
        $directories = $this->directories;

        if ($this->isFull()) {
            $directories = array_merge($this->directoriesFull);
        }

        foreach ($directories as $directory) {
            $this->createDirectory($path . '/' . $directory);
        }
    }

    /**
     * Add the corresponding service provider for the created service.
     *
     * @param string $name
     * @param string $path
     *
     * @return bool
     */
    public function addServiceProviders($name, $slug, $path)
    {
        $namespace = $this->findDomainNamespace($name) . '\\Providers';

        $this->createRegistrationServiceProvider($name, $path, $slug, $namespace);

        $this->createRouteServiceProvider($name, $path, $slug, $namespace);

        if ($this->isFull()) {
            $this->createMiddlewareServiceProvider($name, $path, $slug, $namespace);
        }
    }

    /**
     * Create the service provider that registers this service.
     *
     * @param  string $name
     * @param  string $path
     */
    public function createRegistrationServiceProvider($name, $path, $slug, $namespace)
    {
        $foundation = $this->findFoundationNamespace();

        if ($this->isFull()) {
            $content = file_get_contents(__DIR__ . '/stubs/serviceprovider-full.stub');
        } else {
            $content = file_get_contents(__DIR__ . '/stubs/serviceprovider.stub');
        }

        $content = str_replace(
            ['{{name}}', '{{slug}}', '{{namespace}}', '{{foundation_namespace}}'],
            [$name, $slug, $namespace, $foundation],
            $content
        );

        $this->createFile($path . '/Providers/' . $name . 'ServiceProvider.php', $content);
    }

    /**
     * Create the routes service provider file.
     *
     * @param  string $name
     * @param  string $path
     * @param  string $slug
     * @param  string $namespace
     */
    public function createRouteServiceProvider($name, $path, $slug, $namespace)
    {
        $serviceNamespace = $this->findDomainNamespace($name);
        $controllers = $serviceNamespace . '\Http\Controllers';
        $foundation = $this->findFoundationNamespace();

        $content = file_get_contents(__DIR__ . '/stubs/routeserviceprovider.stub');
        $content = str_replace(
            ['{{name}}', '{{namespace}}', '{{controllers_namespace}}', '{{foundation_namespace}}'],
            [$name, $namespace, $controllers, $foundation],
            $content
        );

        $this->createFile($path . '/Providers/RouteServiceProvider.php', $content);
    }

    /**
     * Create the middleware service provider file.
     *
     * @param  string $name
     * @param  string $path
     * @param  string $slug
     * @param  string $namespace
     */
    public function createMiddlewareServiceProvider($name, $path, $slug, $namespace)
    {
        $serviceNamespace = $this->findDomainNamespace($name);
        $controllers = $serviceNamespace . '\Http\Controllers';
        $foundation = $this->findFoundationNamespace();

        $content = file_get_contents(__DIR__ . '/stubs/middlewareserviceprovider.stub');
        $content = str_replace(
            ['{{name}}', '{{namespace}}', '{{controllers_namespace}}', '{{foundation_namespace}}'],
            [$name, $namespace, $controllers, $foundation],
            $content
        );

        $this->createFile($path . '/Providers/MiddlewareServiceProvider.php', $content);
    }

    /**
     * Add the routes files.
     *
     * @param string $name
     * @param string $slug
     * @param string $path
     */
    public function addRoutesFiles($name, $slug, $path)
    {
        $controllers = 'Domains/' . $name . '/Http/Controllers';

        $contentApi = file_get_contents(__DIR__ . '/stubs/routes-api.stub');
        $contentApi = str_replace(['{{name}}', '{{slug}}', '{{controllers_path}}'], [$name, str_plural($slug), $controllers], $contentApi);

        $contentWeb = file_get_contents(__DIR__ . '/stubs/routes-web.stub');
        $contentWeb = str_replace(['{{name}}', '{{slug}}', '{{controllers_path}}'], [$name, str_plural($slug), $controllers], $contentWeb);

        $this->createFile($path . '/Routes/api.php', $contentApi);
        $this->createFile($path . '/Routes/web.php', $contentWeb);

        unset($contentApi, $contentWeb);
    }

    /**
     * Add the welcome view file.
     *
     * @param string $path
     */
    public function addWelcomeViewFile($path)
    {
        $this->createFile(
            $path . '/Resources/views/welcome.blade.php',
            file_get_contents(__DIR__ . '/stubs/welcome.blade.stub')
        );
    }

    /**
     * Add the ModelFactory file.
     *
     * @param string $path
     */
    public function addModelFactory($path)
    {
        $modelFactory = file_get_contents(__DIR__ . '/stubs/model-factory.stub');
        $this->createFile($path . '/Database/factories/ModelFactory.php', $modelFactory);

        unset($modelFactory);
    }

    /**
     * Add the ModelFactory file.
     *
     * @param string $path
     */
    public function addConfigFile($path)
    {
        $configFile = file_get_contents(__DIR__ . '/stubs/config.stub');
        $this->createFile($path . '/config.php', $configFile);

        unset($configFile);
    }

    public function getArguments()
    {
        return [
            ['name', InputArgument::REQUIRED, 'The domain name.'],
        ];
    }

    /**
     * Default implementation to get the options of this command.
     *
     * @return array
     */
    public function getOptions()
    {
        return [
            ['full', null, InputOption::VALUE_NONE, 'Generate an full domain.'],
        ];
    }

    protected function isFull()
    {
        return $this->option('full');
    }
}
