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

/*
 * This file is part of php-restcord.
 *
 * (c) Aaron Scherer <aequasi@gmail.com>
 *
 * This source file is subject to the license that is bundled
 * with this source code in the file LICENSE
 */

function recursiveRemoveDirectory($directory)
{
    foreach (glob("{$directory}/*") as $file) {
        if (is_dir($file)) {
            recursiveRemoveDirectory($file);
        } else {
            unlink($file);
        }
    }
    rmdir($directory);
}

$path = __DIR__.'/../src/Model';
try {
    recursiveRemoveDirectory($path);
} catch (\Exception $e) {
}
mkdir($path, 02775, true);
$path = realpath($path);

require __DIR__.'/../vendor/autoload.php';

use gossi\codegen\generator\CodeGenerator;
use gossi\codegen\model\PhpClass;
use gossi\codegen\model\PhpMethod;
use gossi\codegen\model\PhpProperty;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

$loader = new Twig_Loader_Filesystem(__DIR__.'/../src/Resources/');
$twig   = new Twig_Environment($loader, ['debug' => true]);
$twig->addExtension(new Twig_Extension_Debug());

$license = <<<EOF
<?php

/*
 * Copyright 2017 Aaron Scherer
 *
 * This source file is subject to the license that is bundled
 * with this source code in the file LICENSE
 *
 * @package     restcord/restcord
 * @copyright   Aaron Scherer 2017
 * @license     MIT
 */
\n
EOF;

/** @noinspection PhpUnhandledExceptionInspection */
(new Application('Build Model Classes', '1.0.0'))
    ->register('buildModelClasses')
    ->addArgument('version', InputArgument::REQUIRED, 'Version to build')
    ->setCode(
        function (InputInterface $input, OutputInterface $output) use ($twig, $license, $path) {
            $style = new \Symfony\Component\Console\Style\SymfonyStyle($input, $output);
            $style->title("Building Model Classes for: ".$input->getArgument('version'));

            $definition = \GuzzleHttp\json_decode(
                file_get_contents(
                    __DIR__.'/../src/Resources/service_description-v'.$input->getArgument('version').'.json'
                ),
                true
            );

            $generator = new CodeGenerator();
            foreach ($definition['models'] as $resource => $models) {
                foreach ($models as $name => $model) {
                    $resource = str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $resource)));
                    $name     = str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $name)));
                    $class    = new PhpClass();
                    $class->setQualifiedName('RestCord\\Model\\'.ucwords($resource).'\\'.ucwords($name));
                    $class->setDescription(ucwords($name)." Model");
                    $class->setProperties(
                        array_map(
                            function (&$name, $property) use ($class, $resource, $model) {
                                $optional = strpos($name, '?') !== false;
                                $name     = lcfirst(str_replace([' ', '?'], '', str_replace('-', '_', $name)));
                                $prop     = new PhpProperty($name);

                                $prop->setType(normalizeType($name, $resource, $property).($optional ? "|null" : ""));
                                $prop->setDescription($property['description']);

                                if (isset($property['default'])) {
                                    $prop->setValue($property['default']);
                                }

                                return $prop;
                            },
                            array_keys($model['properties']),
                            $model['properties']
                        )
                    );

                    addTraits($class, $resource, $name);

                    $constructor = new PhpMethod('__construct');
                    $constructor->addSimpleParameter('content', 'array', null);
                    $constructor->setBody(
                        <<<EOF
                        if (null === \$content) {
    return;
}
                    
foreach (\$content as \$key => \$value) {
    \$key = lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', \$key))));
    if (property_exists(\$this, \$key)) {
        \$this->{\$key} = \$value;
    }
}
EOF
                    );
                    $constructor->setVisibility('public');
                    $class->setMethod($constructor);

                    @mkdir($path.'/'.ucwords($resource), 02775, true);
                    file_put_contents(
                        $path.'/'.ucwords($resource).'/'.ucwords($name).'.php',
                        $license.$generator->generate($class)
                    );
                }
            }

            $style->success('Finished. Classes built in: '.realpath($path));
        }
    )
    ->getApplication()
    ->setDefaultCommand('buildModelClasses', true)
    ->run();

function normalizeType(string $name, string $resource, array $property): string
{
    $type = $property['type'];
    if ($name === 'user' && $resource === 'Guild') {
        $type = "user/user";
    }

    switch ($type) {
        default:
            if (strpos($type, '/') !== false || strpos($type, 'Array<') === 0) {
                $array = false;
                if (stripos($type, 'Array') !== false) {
                    $array = true;
                    $type  = str_replace(['Array<', '>'], '', $type);
                }

                if ($type === 'snowflake') {
                    return 'int[]';
                }

                $cls   = "\\RestCord\\Model\\";
                $tmp   = explode('/', $type);
                $cls   .= ucwords($tmp[0])."\\";
                $clean = str_replace(['-object', '-'], ['', ' '], $tmp[1]);
                $clean = ucwords($clean);
                $clean = str_replace(' ', '', $clean);

                $fullClass = $cls.$clean.($array ? '[]' : '');
                $fullClass = mapBadDocs($fullClass);

                return $fullClass;
            }

            return $type;
        case 'array':
            if (strpos($property['description'], "ids") !== false) {
                return "int[]";
            }

            return "array";
        case 'ISO8601 timestamp':
        case 'ISO8601':
            return '\DateTimeImmutable';
        case 'datetime':
            return '\DateTime';
        case 'snowflake':
        case 'integer':
        case 'timestamp':
            return 'int';
        case 'object':
            return 'array';
        case 'boolean':
            return 'bool';
    }
}

function isTransformType(string $type): bool
{
    $nonTransform = ['string', 'array'];

    return array_search($type, $nonTransform) === false;
}

function mapBadDocs(string $cls): string
{
    switch ($cls) {
        case '\RestCord\Model\User\DmChannel':
            return '\RestCord\Model\Channel\DmChannel';
        case '\RestCord\Model\Channel\Invite':
        case '\RestCord\Model\Guild\Invite':
            return '\RestCord\Model\Invite\Invite';
        case '\RestCord\Model\Guild\GuildChannel':
            return '\RestCord\Model\Channel\GuildChannel';
        case '\RestCord\Model\Guild\User':
        case '\RestCord\Model\Channel\User':
            return '\RestCord\Model\User\User';
        case 'ISO8601':
        case '\RestCord\Model\Channel\ISO8601':
            return '\DateTimeImmutable';
        default:
            return $cls;
    }

    return $cls;
}

function addTraits(PhpClass $class, string $resource, string $name)
{
    if ($resource === 'User' && $name === 'User') {
        $class->addUseStatement(\RestCord\Traits\AvatarTrait::class)->addTrait("AvatarTrait");
    }

    if ($resource === 'Guild' && $name === 'Guild') {
        $class->addUseStatement(\RestCord\Traits\IconTrait::class)->addTrait("IconTrait");
        $class->addUseStatement(\RestCord\Traits\SplashTrait::class)->addTrait("SplashTrait");
    }
}
