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


/**
 * @file
 * This script allows you to generate a custom NetForum xWeb PHP SDK.
 *
 * Use this script when you have customizations in NetForum that are absent
 * from the included SDK.
 *
 * @note
 * The recommended approach is to generate your own SDK and then
 * create a patch for composer to apply to future module updates.
 *
 * @phpcs:disable Drupal.Classes.ClassFileName.NoMatch
 */

declare(strict_types=1);

namespace Drupal\netforum;

define('APP_NAME', 'Drupal NetForum xWeb PHP SDK Generator');
define('APP_VERSION', '1.0.0');

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

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressIndicator;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\PhpSubprocess;
use WsdlToPhp\PackageGenerator\ConfigurationReader\GeneratorOptions;
use WsdlToPhp\PackageGenerator\Generator\Generator;

/**
 * Default command to allow for launching the real command in a new process.
 */
#[AsCommand(name: 'generate', hidden: TRUE,)]
class DefaultCommand extends Command {

  /**
   * Timeout in seconds for the PHP process that is generating the SDK.
   *
   * Takes about 4 minutes to run on a recent processor.
   */
  private const GENERATE_TIMEOUT = 60 * 10;

  protected function configure(): void {
    $this->addArgument('WSDL', InputArgument::REQUIRED, 'URL or Path to WSDL file.');
    $this->addOption('nfversion', NULL, InputOption::VALUE_OPTIONAL, 'Version of NetForum that generated the WSDL file.', 'Custom');
    $this->addOption('official', NULL, InputOption::VALUE_NEGATABLE, 'Whether the generated SDK is an official release or not.', FALSE);
  }

  protected function execute(InputInterface $input, OutputInterface $output): int {
    $start = new \DateTime();
    $output->writeln(sprintf(' Start at %s', $start->format('Y-m-d H:i:s')));

    $progressIndicator = new ProgressIndicator($output);
    $progressIndicator->start('Generating...');

    $genProcess = new PhpSubprocess([
      __FILE__,
      'generate-sdk',
      '--nfversion=' . $input->getOption('nfversion'),
      $input->getOption('official') ? '--official' : '--no-official',
      $input->getArgument('WSDL'),
    ]);
    $genProcess->setTimeout(self::GENERATE_TIMEOUT);
    $genProcess->start(function (string $type, string $buffer) use ($output) {
      $output->write($buffer);
    });

    while ($genProcess->isRunning()) {
      $genProcess->checkTimeout();

      $progressIndicator->advance();

      usleep(250000);
    }

    if (!$genProcess->isSuccessful()) {
      $progressIndicator->finish('Failed', '💥');

      return Command::FAILURE;
    }

    $progressIndicator->finish('Finished', '✔️');

    $end = new \DateTime();
    $output->writeln(sprintf(' End at %s, duration: %s', $end->format('Y-m-d H:i:s'), $start->diff($end)
      ->format('%H:%I:%S')));

    return Command::SUCCESS;
  }

}

#[AsCommand(name: 'generate-sdk', hidden: TRUE,)]
final class GenerateSdkCommand extends DefaultCommand {

  private const GENERATED_DIR = __DIR__ . '/src/xWeb/Generated';

  private string $generateDate;

  private string $generateRelease;

  private string $generateNFVersion;

  protected function execute(InputInterface $input, OutputInterface $output): int {
    if (is_dir(self::GENERATED_DIR)) {
      // Ensure Generated directory is empty.
      self::deleteRecursive(self::GENERATED_DIR);
    }
    else {
      $output->writeln(sprintf("\nError: Generated directory(%s) does not exist.", self::GENERATED_DIR));

      return Command::FAILURE;
    }

    $this->generateDate = date("Y-m-d");
    $this->generateRelease = $input->getOption('official') ? 'Official' : 'Custom';
    $this->generateNFVersion = $input->getOption('nfversion');

    $options = GeneratorOptions::instance()
      ->setOrigin($input->getArgument('WSDL'))
      ->setDestination(self::GENERATED_DIR)
      ->setNamespace('Drupal\\netforum\\xWeb\\Generated')
      ->setSoapClientClass('Drupal\\netforum\\xWeb\\SoapClientBase')
      ->setSrcDirname('')
      ->setGatherMethods(GeneratorOptions::VALUE_NONE)
      ->setNamespaceDictatesDirectories(FALSE)
      ->setStandalone(FALSE)
      ->setAddComments([
        'author' => APP_NAME,
        'version' => APP_VERSION,
        'license' => 'GPL-2.0-or-later https://www.gnu.org/licenses/old-licenses/gpl-2.0.html',
        'see' => 'https://www.drupal.org/project/netforum',
        'generatedDate' => $this->generateDate,
        'generatedRelease' => $this->generateRelease,
        'generatedNetForumVersion' => $this->generateNFVersion,
      ])
      ->setGenerateTutorialFile(FALSE);
    $generator = new Generator($options);
    $generator->generatePackage();

    $this->writeGeneratedInfo();

    return Command::SUCCESS;
  }

  private static function deleteRecursive(string $path): void {
    $files = new \FilesystemIterator($path, \FilesystemIterator::SKIP_DOTS);
    /** @var \SplFileInfo $file */
    foreach ($files as $file) {
      if ($file->isDir()) {
        self::deleteRecursive($file->getPathname());
        rmdir($file->getPathname());
      }
      else {
        unlink($file->getPathname());
      }
    }
  }

  private function writeGeneratedInfo(): void {
    $generateInfoPhp = <<<ENDL
<?php

/**
 * @file
 * Generated info on this SDK release.
 */

namespace Drupal\\netforum\\xWeb\\Generated;

final class GeneratedInfo {

  public const DATE = '$this->generateDate';

  public const RELEASE = '$this->generateRelease';

  public const NF_VERSION = '$this->generateNFVersion';

}

ENDL;

    file_put_contents(self::GENERATED_DIR . '/GeneratedInfo.php', $generateInfoPhp);
  }

}

if (PHP_SAPI !== 'cli') {
  // This is meant to be used from the command line only.
  return;
}

$application = new Application(APP_NAME, APP_VERSION);
$application->add(new GenerateSdkCommand());
$application->add(new DefaultCommand());
$application->setDefaultCommand('generate');
$application->run();
