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

/*
 * This file is part of the Predis package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

// -------------------------------------------------------------------------- //
// This script can be used to automatically generate a file with the scheleton
// of a test case to test a Redis command by specifying the name of the class
// in the Predis\Command namespace (only classes in this namespace are valid).
// For example, to generate a test case for SET (which is represented by the
// Predis\Command\Redis\StringSet class):
//
//   $ ./bin/generate-command-test --class=StringSet
//
// Here is a list of optional arguments:
//
// --realm: each command has its own realm (commands that operate on strings,
// lists, sets and such) but while this realm is usually inferred from the name
// of the specified class, sometimes it can be useful to override it with a
// custom one.
//
// --output: write the generated test case to the specified path instead of
// the default one.
//
// --overwrite: pre-existing test files are not overwritten unless this option
// is explicitly specified.
// -------------------------------------------------------------------------- //

use Predis\Command\CommandInterface;
use Predis\Command\PrefixableCommandInterface;

class CommandTestCaseGenerator
{
    private $options;

    public function __construct(array $options)
    {
        if (!isset($options['class'])) {
            throw new RuntimeException("Missing 'class' option.");
        }

        if (!isset($options['realm'])) {
            throw new RuntimeException("Missing 'realm' option.");
        }

        $this->options = $options;
    }

    public static function fromCommandLine()
    {
        $parameters = array(
            'c:'  => 'class:',
            'r::' => 'realm::',
            'o::' => 'output::',
            'x::' => 'overwrite::'
        );

        $getops = getopt(implode(array_keys($parameters)), $parameters);

        $options = array(
            'overwrite' => false,
            'tests' => __DIR__.'/../tests/Predis',
        );

        foreach ($getops as $option => $value) {
            switch ($option) {
                case 'c':
                case 'class':
                    $options['class'] = $value;
                    break;

                case 'r':
                case 'realm':
                    $options['realm'] = $value;
                    break;

                case 'o':
                case 'output':
                    $options['output'] = $value;
                    break;

                case 'x':
                case 'overwrite':
                    $options['overwrite'] = true;
                    break;
            }
        }

        if (!isset($options['class'])) {
            throw new RuntimeException("Missing 'class' option.");
        }

        if (!isset($options['realm'])) {
            throw new RuntimeException("Missing 'realm' option.");
        }

        $options['fqn'] = "Predis\\Command\\Redis\\{$options['class']}";
        $options['path'] = "Command/Redis/{$options['class']}.php";

        $source = __DIR__.'/../src/'.$options['path'];
        if (!file_exists($source)) {
            throw new RuntimeException("Cannot find class file for {$options['fqn']} in $source.");
        }

        if (!isset($options['output'])) {
            $options['output'] = sprintf("%s/%s", $options['tests'], str_replace('.php', '_Test.php', $options['path']));
        }

        return new self($options);
    }

    protected function getTestRealm()
    {
        if (empty($this->options['realm'])) {
            throw new RuntimeException('Invalid value for realm has been specified (empty).');
        }

        return $this->options['realm'];
    }

    public function generate()
    {
        $reflection = new ReflectionClass($class = $this->options['fqn']);

        if (!$reflection->isInstantiable()) {
            throw new RuntimeException("Class $class must be instantiable, abstract classes or interfaces are not allowed.");
        }

        if (!$reflection->implementsInterface('Predis\Command\CommandInterface')) {
            throw new RuntimeException("Class $class must implement Predis\Command\CommandInterface.");
        }

        /*
         * @var CommandInterface
         */
        $instance = $reflection->newInstance();

        $buffer = $this->getTestCaseBuffer($instance);

        return $buffer;
    }

    public function save()
    {
        $options = $this->options;
        if (file_exists($options['output']) && !$options['overwrite']) {
            throw new RuntimeException("File {$options['output']} already exist. Specify the --overwrite option to overwrite the existing file.");
        }
        file_put_contents($options['output'], $this->generate());
    }

    protected function getTestCaseBuffer(CommandInterface $instance)
    {
        $id = $instance->getId();
        $fqn = get_class($instance);
        $fqnParts = explode('\\', $fqn);
        $class = array_pop($fqnParts) . "Test";
        $realm = $this->getTestRealm();

        $buffer =<<<PHP
<?php

/*
 * This file is part of the Predis package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Predis\Command\Redis;

/**
 * @group commands
 * @group realm-$realm
 */
class $class extends PredisCommandTestCase
{
    /**
     * {@inheritdoc}
     */
    protected function getExpectedCommand(): string
    {
        return '$fqn';
    }

    /**
     * {@inheritdoc}
     */
    protected function getExpectedId(): string
    {
        return '$id';
    }

    /**
     * @group disconnected
     */
    public function testFilterArguments(): void
    {
        \$this->markTestIncomplete('This test has not been implemented yet.');

        \$arguments = array(/* add arguments */);
        \$expected = array(/* add arguments */);

        \$command = \$this->getCommand();
        \$command->setArguments(\$arguments);

        \$this->assertSame(\$expected, \$command->getArguments());
    }

    /**
     * @group disconnected
     */
    public function testParseResponse(): void
    {
        \$this->markTestIncomplete('This test has not been implemented yet.');

        \$raw = null;
        \$expected = null;

        \$command = \$this->getCommand();

        \$this->assertSame(\$expected, \$command->parseResponse(\$raw));
    }

PHP;

        if ($instance instanceof PrefixableCommandInterface) {
            $buffer .=<<<PHP

    /**
     * @group disconnected
     */
    public function testPrefixKeys(): void
    {
        \$this->markTestIncomplete('This test has not been implemented yet.');

        \$arguments = array(/* add arguments */);
        \$expected = array(/* add arguments */);

        \$command = \$this->getCommandWithArgumentsArray(\$arguments);
        \$command->prefixKeys('prefix:');

        \$this->assertSame(\$expected, \$command->getArguments());
    }

    /**
     * @group disconnected
     */
    public function testPrefixKeysIgnoredOnEmptyArguments(): void
    {
        \$command = \$this->getCommand();
        \$command->prefixKeys('prefix:');

        \$this->assertSame(array(), \$command->getArguments());
    }

PHP;
        }

        return "$buffer}\n";
    }
}

// ------------------------------------------------------------------------- //

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

$generator = CommandTestCaseGenerator::fromCommandLine();
$generator->save();
