<?php

/* Manage installed modules */
class ModuleManager
{
  public array $Modules; // Module
  public System $System;
  public string $FileName;
  public string $ModulesDir;
  public $OnLoadModules;
  public $OnInstallModel;
  public $OnUninstsallModel;
  public $OnInstallModule;
  public $OnUninstsallModule;
  private int $NewModuleId;

  function __construct(System $System)
  {
    $this->Modules = array();
    $this->System = &$System;
    $this->FileName = dirname(__FILE__).'/../../Config/ModulesConfig.php';
    $this->ModulesDir = dirname(__FILE__).'/../../Modules';
    $this->OnInstallModel = null;
    $this->OnUninstallModel = null;
    $this->OnInstallModule = null;
    $this->OnUninstallModule = null;
    $this->NewModuleId = 1;
  }

  function Perform(array $Actions, array $Conditions = array(ModuleCondition::All)): void
  {
    $this->PerformList($this->Modules, $Actions, $Conditions);
  }

  function PerformList(array $List, array $Actions, array $Conditions = array(ModuleCondition::All)): void
  {
    foreach ($List as $Module)
    {
      if (in_array(ModuleCondition::All, $Conditions) or
        ($Module->Running and in_array(ModuleCondition::Running, $Conditions)) or
        (!$Module->Running and in_array(ModuleCondition::NotRunning, $Conditions)) or
        ($Module->Installed and in_array(ModuleCondition::Installed, $Conditions)) or
        (!$Module->Installed and in_array(ModuleCondition::NotInstalled, $Conditions)) or
        ($Module->Enabled and in_array(ModuleCondition::Enabled, $Conditions)) or
        (!$Module->Enabled and in_array(ModuleCondition::NotEnabled, $Conditions)) or
        (($Module->Type == ModuleType::System) and in_array(ModuleCondition::System, $Conditions)) or
        (($Module->Type == ModuleType::Application) and in_array(ModuleCondition::Application, $Conditions)) or
        (($Module->Type == ModuleType::Library) and in_array(ModuleCondition::Library, $Conditions)))
      {
        foreach ($Actions as $Action)
        {
          if ($Action == ModuleAction::Start) $Module->Start();
          if ($Action == ModuleAction::Stop) $Module->Stop();
          if ($Action == ModuleAction::Install) $Module->Install();
          if ($Action == ModuleAction::Uninstall) $Module->Uninstall();
          if ($Action == ModuleAction::Enable) $Module->Enable();
          if ($Action == ModuleAction::Disable) $Module->Disable();
          if ($Action == ModuleAction::Upgrade) $Module->Upgrade();
          if ($Action == ModuleAction::InsertSampleData) $Module->InsertSampleData();
        }
      }
    }
  }

  function EnumDependenciesCascade(Module $Module, array &$List, array $Conditions = array(ModuleCondition::All))
  {
    foreach ($Module->Dependencies as $Dependency)
    {
      if (!array_key_exists($Dependency, $this->Modules))
        throw new Exception(sprintf(T('Module "%s" dependency "%s" not found'), $Module->Name, $Dependency));
      $DepModule = $this->Modules[$Dependency];
      if (in_array(ModuleCondition::All, $Conditions) or
        ($DepModule->Running and in_array(ModuleCondition::Running, $Conditions)) or
        (!$DepModule->Running and in_array(ModuleCondition::NotRunning, $Conditions)) or
        ($DepModule->Enabled and in_array(ModuleCondition::Enabled, $Conditions)) or
        (!$DepModule->Enabled and in_array(ModuleCondition::NotEnabled, $Conditions)) or
        ($DepModule->Installed and in_array(ModuleCondition::Installed, $Conditions)) or
        (!$DepModule->Installed and in_array(ModuleCondition::NotInstalled, $Conditions)) or
        (($DepModule->Type == ModuleType::System) and in_array(ModuleCondition::System, $Conditions)) or
        (($DepModule->Type == ModuleType::Application) and in_array(ModuleCondition::Application, $Conditions)) or
        (($DepModule->Type == ModuleType::Library) and in_array(ModuleCondition::Library, $Conditions)))
      {
        array_push($List, $DepModule);
        $this->EnumDependenciesCascade($DepModule, $List, $Conditions);
      }
    }
  }

  function EnumSuperiorDependenciesCascade(Module $Module, array &$List, array $Conditions = array(ModuleCondition::All))
  {
    foreach ($this->Modules as $RefModule)
    {
      if (in_array($Module->Name, $RefModule->Dependencies) and
          (in_array(ModuleCondition::All, $Conditions) or
          ($RefModule->Running and in_array(ModuleCondition::Running, $Conditions)) or
          (!$RefModule->Running and in_array(ModuleCondition::NotRunning, $Conditions)) or
          ($RefModule->Enabled and in_array(ModuleCondition::Enabled, $Conditions)) or
          (!$RefModule->Enabled and in_array(ModuleCondition::NotEnabled, $Conditions)) or
          ($RefModule->Installed and in_array(ModuleCondition::Installed, $Conditions)) or
          (!$RefModule->Installed and in_array(ModuleCondition::NotInstalled, $Conditions)) or
          (($RefModule->Type == ModuleType::System) and in_array(ModuleCondition::System, $Conditions)) or
          (($RefModule->Type == ModuleType::Application) and in_array(ModuleCondition::Application, $Conditions)) or
          (($RefModule->Type == ModuleType::Library) and in_array(ModuleCondition::Library, $Conditions))))
        {
        array_push($List, $RefModule);
        $this->EnumSuperiorDependenciesCascade($RefModule, $List, $Conditions);
      }
    }
  }

  function StartAll(array $Conditions = array(ModuleCondition::All)): void
  {
    $this->Perform(array(ModuleAction::Start), $Conditions);
  }

  function StopAll(array $Conditions = array(ModuleCondition::All)): void
  {
    $this->Perform(array(ModuleAction::Stop), $Conditions);
  }

  function InstallAll(array $Conditions = array(ModuleCondition::All)): void
  {
    $this->Perform(array(ModuleAction::Install), $Conditions);
    $this->SaveState();
  }

  function UninstallAll(array $Conditions = array(ModuleCondition::All)): void
  {
    $this->Perform(array(ModuleAction::Uninstall), $Conditions);
    $this->SaveState();
  }

  function EnableAll(array $Conditions = array(ModuleCondition::All)): void
  {
    $this->Perform(array(ModuleAction::Enable), $Conditions);
    $this->SaveState();
  }

  function DisableAll(array $Conditions = array(ModuleCondition::All)): void
  {
    $this->Perform(array(ModuleAction::Disable), $Conditions);
    $this->SaveState();
  }

  function UpgradeAll(array $Conditions = array(ModuleCondition::All)): void
  {
    $this->Perform(array(ModuleAction::Upgrade), $Conditions);
    $this->SaveState();
  }

  function ModulePresent(string $Name): bool
  {
    return array_key_exists($Name, $this->Modules);
  }

  function ModuleEnabled(string $Name): bool
  {
    return array_key_exists($Name, $this->Modules) and $this->Modules[$Name]->Enabled;
  }

  function ModuleRunning(string $Name): bool
  {
    return array_key_exists($Name, $this->Modules) and $this->Modules[$Name]->Running;
  }

  function GetModule(string $Name): Module
  {
    return $this->Modules[$Name];
  }

  function SearchModuleById(int $Id): string
  {
    foreach ($this->Modules as $Module)
    {
      if ($Module->Id == $Id) return $Module->Name;
    }
    return '';
  }

  function LoadState(): void
  {
    $ConfigModules = array();
    include $this->FileName;
    foreach ($ConfigModules as $Mod)
    {
      $ModuleName = $Mod['Name'];
      if (array_key_exists($ModuleName, $this->Modules))
      {
        if (array_key_exists('Id', $Mod))
        {
          $this->Modules[$ModuleName]->Id = $Mod['Id'];
        }
        if (array_key_exists('Enabled', $Mod))
        {
          $this->Modules[$ModuleName]->Enabled = $Mod['Enabled'];
        }
        if (array_key_exists('Installed', $Mod))
        {
          $this->Modules[$ModuleName]->Installed = $Mod['Installed'];
        }
        if (array_key_exists('InstalledVersion', $Mod))
        {
          $this->Modules[$ModuleName]->InstalledVersion = $Mod['InstalledVersion'];
        }
      }
    }
  }

  function SaveState(): void
  {
    $Data = array();
    foreach ($this->Modules as $Module)
    {
      $Data[] = array(
        'Id' => $Module->Id,
        'Name' => $Module->Name,
        'Enabled' => $Module->Enabled,
        'InstalledVersion' => $Module->InstalledVersion,
        'Installed' => $Module->Installed
      );
    }
    if (file_put_contents($this->FileName, "<?php \n\n\$ConfigModules = ".var_export($Data, true).";\n") === FALSE)
    {
      echo($this->FileName.' is not writeable.');
    }
  }

  function RegisterModule(Module $Module): void
  {
    if (array_key_exists($Module->Name, $this->Modules))
      die('Can\'t register module '.$Module->Name.' because it already exists.');
    $this->Modules[$Module->Name] = &$Module;
    $Module->Manager = &$this;
    $Module->OnChange = &$this->OnModuleChange;
  }

  function UnregisterModule(Module $Module): void
  {
    unset($this->Modules[array_search($Module, $this->Modules)]);
  }

  function GetUniqueModuleId()
  {
    $this->NewModuleId++;
    return $this->NewModuleId - 1;
  }

  function LoadModule(string $FileName): Module
  {
    $ClassName = 'Module'.pathinfo($FileName, PATHINFO_FILENAME);
    if (!array_key_exists($ClassName::GetName(), $this->Modules))
    {
      $Module = new $ClassName($this->System);
      $Module->Id = $this->GetUniqueModuleId();
      $this->RegisterModule($Module);
      return $Module;
    } else
    {
      $Module = $this->Modules[$ClassName::GetName()];
      return $Module;
    }
  }

  function LoadModulesFromDir(string $Directory): void
  {
    $FileNames = array();
    $List = scandir($Directory);
    foreach ($List as $Item)
    {
      if (is_dir($Directory.'/'.$Item) and ($Item != '.') and ($Item != '..') and ($Item != '.svn'))
      {
        $FileName = $Directory.'/'.$Item.'/'.$Item.'.php';
        $FileNames[] = $FileName;
        include_once($FileName);
      }
    }
    foreach ($FileNames as $FileName)
    {
      $this->LoadModule($FileName);
    }
  }

  function LoadModules(): void
  {
    if (is_array($this->OnLoadModules) and (count($this->OnLoadModules) == 2) and method_exists($this->OnLoadModules[0], $this->OnLoadModules[1]))
      call_user_func($this->OnLoadModules);
    else $this->LoadModulesFromDir($this->ModulesDir);
  }
}
