<?php

/* This implementation will not support installation from remote source. Just
 * installation of already presented modules mainly to persistence preparation.
 */

class ModuleType
{
  const System = 0;
  const Normal = 1;
  const Application = 2;
}

class ModuleAction
{
  const Start = 0;
  const Stop = 1;
  const Install = 2;
  const Uninstall = 3;
  const Upgrade = 4;
  const Enable = 5;
  const Disable = 6;
}

class ModuleCondition
{
  const All = 0;
  const Enabled = 1;
  const NotEnabled = 2;
  const Installed = 3;
  const NotInstalled = 4;
  const Running = 5;
  const NotRunning = 6;
}

class AppModule
{
  var $Id;
  var $Name;
  var $Title;
  var $Version;
  var $License;
  var $Creator;
  var $HomePage;
  var $Description;
  var $Running;
  var $Enabled;
  var $Installed;
  var $InstalledVersion;
  /** @var ModuleType */
  var $Type;
  var $Dependencies;
  /** @var Database */
  var $Database;
  /** @var System */
  var $System;
  /** @var AppModuleManager */
  var $Manager;
  var $OnChange;

  function __construct(System $System)
  {
    $this->System = &$System;
    $this->Database = &$System->Database;
    $this->Installed = false;
    $this->Enabled = false;
    $this->Running = false;
    $this->HomePage = '';
    $this->License = '';
    $this->Version = '';
    $this->Creator = '';
    $this->Description = '';
    $this->Dependencies = array();
    $this->Type = ModuleType::Normal;
  }

  function Install()
  {
    if($this->Installed) return;
    $List = array();
    $this->Manager->EnumDependenciesCascade($this, $List, array(ModuleCondition::NotInstalled));
    $this->Manager->Perform($List, array(ModuleAction::Install), array(ModuleCondition::NotInstalled));
    $this->DoInstall();
    $this->Installed = true;
    $this->InstalledVersion = $this->Version;
    $this->Manager->Modules[$this->Name] = $this;
  }

  function Uninstall()
  {
    if(!$this->Installed) return;
    $this->Stop();
    $this->Installed = false;
    $List = array();
    $this->Manager->EnumSuperiorDependenciesCascade($this, $List, array(ModuleCondition::Installed));
    $this->Manager->Perform($List, array(ModuleAction::Uninstall), array(ModuleCondition::Installed));
    $this->DoUninstall();
  }

  function Upgrade()
  {
    if(!$this->Installed) return;
    if($this->InstalledVersion == $this->Version) return;
    $List = array();
    $this->Manager->EnumSuperiorDependenciesCascade($this, $List, array(ModuleCondition::Installed));
    $this->Manager->Perform($List, array(ModuleAction::Upgrade), array(ModuleCondition::Installed));
    $this->DoUpgrade();
  }

  function Reinstall()
  {
    $this->Uninstall();
    // TODO: Install also back dependecies
    $this->Install();
  }

  function Start()
  {
    if($this->Running) return;
    if(!$this->Installed) return;
    $List = array();
    $this->Manager->EnumDependenciesCascade($this, $List, array(ModuleCondition::NotRunning));
    $this->Manager->Perform($List, array(ModuleAction::Start), array(ModuleCondition::NotRunning));
    $this->DoStart();
    $this->Running = true;
  }

  function Stop()
  {
    if(!$this->Running) return;
    $this->Running = false;
    $List = array();
    $this->Manager->EnumSuperiorDependenciesCascade($this, $List, array(ModuleCondition::Running));
    $this->Manager->Perform($List, array(ModuleAction::Stop), array(ModuleCondition::Running));
    $this->DoStop();
  }

  function Restart()
  {
    $this->Stop();
    $this->Start();
  }

  function Enable()
  {
    if($this->Enabled) return;
    if(!$this->Installed) return;
    $List = array();
    $this->Manager->EnumDependenciesCascade($this, $List, array(ModuleCondition::NotEnabled));
    $this->Manager->Perform($List, array(ModuleAction::Enable), array(ModuleCondition::NotEnabled));
    $this->Enabled = true;
  }

  function Disable()
  {
    if(!$this->Enabled) return;
    $this->Stop();
    $this->Enabled = false;
    $List = array();
    $this->Manager->EnumSuperiorDependenciesCascade($this, $List, array(ModuleCondition::Enabled));
    $this->Manager->Perform($List, array(ModuleAction::Disable), array(ModuleCondition::Enabled));
  }

  protected function DoStart()
  {
  }

  protected function DoStop()
  {
  }

  protected function DoInstall()
  {
  }

  protected function DoUninstall()
  {
  }

  protected function DoUpgrade()
  {

  }
}

/* Manage installed modules */
class AppModuleManager
{
  var $Modules;
  var $System;
  var $FileName;
  var $ModulesDir;
  var $OnLoadModules;

  function __construct(System $System)
  {
    $this->Modules = array();
    $this->System = &$System;
    $this->FileName = dirname(__FILE__).'/../../Config/ModulesConfig.php';
    $this->ModulesDir = dirname(__FILE__).'/../../Modules';
  }

  function Perform($List, $Actions, $Conditions = array(ModuleCondition::All))
  {
    foreach($List as $Index => $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)))
      {
        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();
        }
      }
    }
  }

  function EnumDependenciesCascade($Module, &$List, $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
        ($Module->Running and in_array(ModuleCondition::Running, $Conditions)) or
        (!$Module->Running and in_array(ModuleCondition::NotRunning, $Conditions)) or
        ($Module->Enabled and in_array(ModuleCondition::Enabled, $Conditions)) or
        (!$Module->Enabled and in_array(ModuleCondition::NotEnabled, $Conditions)) or
        ($Module->Installed and in_array(ModuleCondition::Installed, $Conditions)) or
        (!$Module->Installed and in_array(ModuleCondition::NotInstalled, $Conditions)))
      {
        array_push($List, $DepModule);
        $this->EnumDependenciesCascade($DepModule, $List, $Conditions);
      }
    }
  }

  function EnumSuperiorDependenciesCascade($Module, &$List, $Conditions = array(ModuleCondition::All))
  {
    foreach($this->Modules as $RefModule)
    {
      if(in_array($Module->Name, $RefModule->Dependencies) and
          (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->Enabled and in_array(ModuleCondition::Enabled, $Conditions)) or
          (!$Module->Enabled and in_array(ModuleCondition::NotEnabled, $Conditions)) or
          ($Module->Installed and in_array(ModuleCondition::Installed, $Conditions)) or
          (!$Module->Installed and in_array(ModuleCondition::NotInstalled, $Conditions))))
      {
        array_push($List, $RefModule);
        $this->EnumSuperiorDependenciesCascade($RefModule, $List, $Conditions);
      }
    }
  }

  function Start()
  {
    $this->LoadModules();
    if(file_exists($this->FileName)) $this->LoadState();
    $this->StartEnabled();
  }

  function StartAll()
  {
    $this->Perform($this->Modules, array(ModuleAction::Start));
  }

  function StartEnabled()
  {
    $this->Perform($this->Modules, array(ModuleAction::Start), array(ModuleCondition::Enabled));
  }

  function StopAll()
  {
    $this->Perform($this->Modules, array(ModuleAction::Stop));
  }

  function InstallAll()
  {
    $this->Perform($this->Modules, array(ModuleAction::Install));
    $this->SaveState();
  }

  function UninstallAll()
  {
    $this->Perform($this->Modules, array(ModuleAction::Uninstall));
    $this->SaveState();
  }

  function EnableAll()
  {
    $this->Perform($this->Modules, array(ModuleAction::Enable));
    $this->SaveState();
  }

  function DisableAll()
  {
    $this->Perform($this->Modules, array(ModuleAction::Disable));
    $this->SaveState();
  }

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

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

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

  /* @return Module */
  function SearchModuleById($Id)
  {
    foreach($this->Modules as $Module)
    {
      //DebugLog($Module->Name.' '.$Module->Id);
      if($Module->Id == $Id) return($Module->Name);
    }
    return('');
  }

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

  function SaveState()
  {
    $Data = array();
    foreach($this->Modules as $Module)
    {
      $Data[] = array('Name' => $Module->Name, 'Enabled' => $Module->Enabled,
        'Version' => $Module->Version, 'Installed' => $Module->Installed);
    }
    file_put_contents($this->FileName, "<?php \n\n\$ConfigModules = ".var_export($Data, true).";\n");
  }

  function RegisterModule(AppModule $Module)
  {
    $this->Modules[$Module->Name] = &$Module;
    $Module->Manager = &$this;
    $Module->OnChange = &$this->OnModuleChange;
  }

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

  function LoadModulesFromDir($Directory)
  {
    $List = scandir($Directory);
    foreach($List as $Item)
    {
      if(is_dir($Directory.'/'.$Item) and ($Item != '.') and ($Item != '..') and ($Item != '.svn'))
      {
        include_once($Directory.'/'.$Item.'/'.$Item.'.php');
        $ModuleName = 'Module'.$Item;
        $this->RegisterModule(new $ModuleName($this->System));
      }
    }
  }

  function LoadModules()
  {
    if(method_exists($this->OnLoadModules[0], $this->OnLoadModules[1]))
      $this->OnLoadModules();
    else $this->LoadModulesFromDir($this->ModulesDir);
  }
}
