<?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 $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->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;
    $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()
  {
  }
}

/* Manage installed modules */
class AppModuleManager 
{
  var $Modules;
  var $System;
  var $FileName;
  var $OnLoadModules;
  
  function __construct(System $System)
  {
    $this->Modules = array();
    $this->System = &$System;
    $this->FileName = 'Config/Modules.php';     
  }
  
  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();
        }
      }
    }
  }
  
  function EnumDependenciesCascade($Module, &$List, $Conditions = array(ModuleCondition::All))
  {
    foreach($Module->Dependencies as $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 UninstallAll()
  {
    $this->Perform($this->Modules, array(ModuleAction::Uninstall));
    $this->SaveState();
  }
  
  function ModulePresent($Name)
  {
    return(array_key_exists($Name, $this->Modules));
  }
  
  /* @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(dirname(__FILE__).'/../Modules');
  }
}

