<?php
namespace nur\tools\compctl;

use Exception;
use nur\A;

class ComposerWalker {
  protected static function get_project_dir($name, string $projects_dir) {
    $project_dir = A::get(ConfigFile::PROJNAMES, $name);
    if ($project_dir === null) {
      $project_dir = preg_replace('/\//', "-", $name, 1);
    }
    return "$projects_dir/$project_dir";
  }

  static function parse_project(string $dir, array &$projects, ?array &$new_projects=null, bool $throw=false): ?array {
    try {
      $composer = new ComposerFile("$dir/composer.json");
    } catch (Exception $e) {
      if ($throw) throw $e;
      else return null;
    }

    $pname = $composer->getv("name");
    if (array_key_exists($pname, $projects) && $projects[$pname] !== null) {
      # projet déjà analysé
      return $projects[$pname];
    }

    $project = [
      "composer" => $composer,
      "name" => $pname,
      "requires" => [],
      "branches" => [],
      "dir" => realpath($dir),
    ];
    $requires = $composer->geta("require");
    if ($requires !== null) {
      foreach ($requires as $dep => $version) {
        # ignorer les projets qui n'ont pas le préfixe requis
        $include_project = false;
        foreach (ConfigFile::PREFIXES as $prefix) {
          if (substr($dep, 0, strlen($prefix)) === $prefix) {
            $include_project = true;
            break;
          }
        }
        if (!$include_project) continue;
        # sinon, ajouter le projet aux dépendances
        $project["requires"][] = $dep;
        if (!array_key_exists($dep, $projects)) {
          $projects[$dep] = null;
          $new_projects[$dep] = true;
        }
        if (substr($version, 0, 4) === "dev-") {
          $project["branches"][$dep] = substr($version, 4);
        } else {
          $project["branches"][$dep] = $version;
        }
      }
    }
    $projects[$pname] = $project;
    return $project;
  }

  static function build_projects(array $project_dirs, ?string &$projects_dir): array {
    $projects = [];
    foreach ($project_dirs as $project_dir) {
      if ($projects_dir === null) $projects_dir = dirname($project_dir);
      # analyser le projet initial
      $new_projects = [];
      if (self::parse_project($project_dir, $projects, $new_projects) === null) {
        continue;
      }
      # puis analyser récursivement tous les projets dépendants
      while (true) {
        $have_new = false;
        foreach ($new_projects as $name => &$new) {
          if ($new) {
            $new = false;
            $have_new = true;
            $project_dir = self::get_project_dir($name, $projects_dir);
            self::parse_project($project_dir, $projects, $new_projects);
          }
        }; unset($new);
        if (!$have_new) break;
      }
    }
    return $projects;
  }

  static function order_deps(?array $deps, array $projects): array {
    if ($deps === null) $deps = array_keys($projects);
    $pi = 0;
    $max = count($deps);
    while ($pi < $max) {
      $modified = false;
      $project = $projects[$deps[$pi]];
      if ($project !== null) {
        # dépendance dont on a trouvé la correspondance sur disque
        foreach ($project["requires"] as $dep) {
          $di = array_search($dep, $deps);
          if ($di > $pi) {
            # si la dépendance se situe après le projet, la placer avant
            array_splice($deps, $di, 1);
            array_splice($deps, $pi, 0, [$dep]);
            $modified = true;
          }
        }
      }
      if (!$modified) $pi++;
    }
    return $deps;
  }
}