<?php

namespace Dipper\Console\Commands;

use Dipper\Console\Finder;
use Dipper\Console\Command;
use Illuminate\Support\Str;
use Dipper\Console\Filesystem;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Command\Command as SymfonyCommand;

/**
 * @author HollyTeng <n.haoyuan@gmail.com>
 */
abstract class GeneratorCommand extends SymfonyCommand
{
    use Finder;
    use Command;
    use Filesystem;

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

    /**
     * Execute the console command.
     *
     * @return bool|null
     */
    public function fire()
    {
        $domain = studly_case($this->getDomainInput());

        $name = $this->qualifyClass($this->getNameInput(), $domain);

        $path = $this->getPath($name, $domain);
        
        if ($this->exists($path)) {
            $this->error($this->type.' already exists!');
            return false;
        }

        $stub = $this->buildClass($name, $domain);

        $this->createFile($path, $stub);

        $instance = new static;

        if (method_exists($instance, 'getTestStub')) {
            $rootNamespace = $this->findDomainTestsPath($domain);
            $testname = $this->qualifyTestClass($this->getNameInput(), $domain);
            $path = $this->getTestPath($testname, $domain);
            $stub = $this->buildTestClass($testname.'Test', $domain, $name);
            $this->createFile($path, $stub);
        }

        $this->info($this->type.' created successfully.');
    }

    /**
     * Parse the class name and format according to the root namespace.
     *
     * @param  string  $name
     * @return string
     */
    protected function qualifyClass($name, $domain = null)
    {
        $name = ltrim($name, '\\/');
        $rootNamespace = $this->findDomainNamespace($domain);

        $name = str_replace('/', '\\', $name);

        $name = ($this->type !== 'Model') ? $name . $this->type : $name;

        return $this->getDefaultNamespace(rtrim($rootNamespace, '\\')).'\\'.$name;
    }

    /**
     * Parse the class name and format according to the root namespace.
     *
     * @param  string  $name
     * @return string
     */
    protected function qualifyTestClass($name, $domain = null)
    {
        $name = ltrim($name, '\\/');
        
        $rootNamespace = $this->findDomainTestsNamespace($domain);

        if (Str::startsWith($name, $rootNamespace)) {
            return $name;
        }

        $name = str_replace('/', '\\', $name);

        $name = ($this->type !== 'Model') ? $name . $this->type : $name;

        return $this->qualifyTestClass(
            $this->getDefaultNamespace(trim($rootNamespace, '\\')).'\\'.$name,
            $domain
        );
    }

    /**
     * Get the default namespace for the class.
     *
     * @param  string  $rootNamespace
     * @return string
     */
    protected function getDefaultNamespace($rootNamespace)
    {
        return $rootNamespace.'\\'.str_plural($this->type);
    }

    /**
     * Get the destination class path.
     *
     * @param  string  $name
     * @return string
     */
    protected function getPath($name, $domain)
    {
        $name = Str::replaceFirst($this->findDomainNamespace($domain), '', $name);
        return $this->findDomainPath($domain).str_replace('\\', DIRECTORY_SEPARATOR, $name).'.php';
    }

    /**
     * Get the destination class path.
     *
     * @param  string  $name
     * @return string
     */
    protected function getTestPath($name, $domain)
    {
        $name = Str::replaceFirst($this->findDomainTestsNamespace($domain), '', $name);
        return $this->findDomainTestsPath($domain).str_replace('\\', DIRECTORY_SEPARATOR, $name).'Test.php';
    }

    /**
     * Build the class with the given name.
     *
     * @param  string  $name
     * @return string
     */
    protected function buildClass($name, $domain = null)
    {
        $stub = file_get_contents($this->getStub());
        return $this->replaceNamespace($stub, $name)->replaceClass($stub, $name, $domain);
    }

    /**
     * Build the test class with the given name.
     *
     * @param  string  $name
     * @return string
     */
    protected function buildTestClass($name, $domain = null, $use = null)
    {
        $stub = file_get_contents($this->getTestStub());
        return $this->replaceNamespace($stub, $name)->replaceClass($stub, $name, $domain, $use);
    }

    /**
     * Replace the namespace for the given stub.
     *
     * @param  string  $stub
     * @param  string  $name
     * @return $this
     */
    protected function replaceNamespace(&$stub, $name)
    {
        $stub = str_replace(
            ['{{namespace}}', '{{foundation_namespace}}'],
            [$this->getNamespace($name), $this->findFoundationNamespace()],
            $stub
        );

        return $this;
    }

    /**
     * Replace the class name for the given stub.
     *
     * @param  string  $stub
     * @param  string  $name
     * @return string
     */
    protected function replaceClass($stub, $name, $domain, $use = null)
    {
        $classname = str_replace($this->getNamespace($name).'\\', '', $name);

        $class = Str::replaceLast($this->type, '', $classname);
        
        return str_replace(
            ['{{classname}}', '{{class}}', '{{domain}}', '{{use}}'],
            [$classname, $class, $domain, $use],
            $stub
        );
    }

    /**
     * Get the desired class name from the input.
     *
     * @return string
     */
    protected function getNameInput()
    {
        return Str::studly(trim($this->argument('name')));
    }

    /**
     * Get the desired class name from the input.
     *
     * @return string
     */
    protected function getDomainInput()
    {
        try {
            return Str::studly(trim($this->argument('domain')));
        } catch (\Exception $e) {
            return null;
        }
    }

    public function getArguments()
    {
        return [
            ['name', InputArgument::REQUIRED, 'The name of the class'],
            ['domain', InputArgument::REQUIRED, 'The domain to be responsible for the job.'],
        ];
    }
}
