<?php

class ModuleType
{
  const System = 0;
  const Library = 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;
  const InsertSampleData = 7;
}

class ModuleCondition
{
  const All = 0;
  const Enabled = 1;
  const NotEnabled = 2;
  const Installed = 3;
  const NotInstalled = 4;
  const Running = 5;
  const NotRunning = 6;
  const System = 7;
  const Library = 8;
  const Application = 9;
}

class Module extends Model
{
  public int $Id;
  public string $Name;
  public string $Title;
  public string $Version;
  public string $License;
  public string $Creator;
  public string $HomePage;
  public string $Description;
  public int $Type; // ModuleType
  public array $Dependencies;
  public array $Models; // Model
  public bool $Running;
  public bool $Enabled;
  public bool $Installed;
  public string $InstalledVersion;
  public Database $Database;
  public System $System;
  public ModuleManager $Manager;
  public $OnChange;

  function __construct(System $System)
  {
    parent::__construct($System);
    $this->Name = '';
    $this->HomePage = '';
    $this->License = '';
    $this->Version = '';
    $this->Creator = '';
    $this->Title = '';
    $this->Description = '';
    $this->Dependencies = array();
    $this->Models = array();
    $this->Type = ModuleType::Library;
    $this->Installed = false;
    $this->InstalledVersion = '';
    $this->Enabled = false;
    $this->Running = false;
  }

  static function GetName()
  {
    $ClassName = get_called_class();
    if (substr($ClassName, 0, 6) == 'Module') return substr($ClassName, 6);
      else return $ClassName;
  }

  static function GetModelDesc(): ModelDesc
  {
    $Desc = new ModelDesc(self::GetClassName());
    $Desc->AddString('Name');
    $Desc->AddString('Title');
    $Desc->AddString('Creator');
    $Desc->AddString('Version');
    $Desc->AddString('License');
    $Desc->AddBoolean('Installed');
    $Desc->AddString('InstalledVersion');
    $Desc->AddString('Description');
    return $Desc;
  }

  function Install(): void
  {
    if ($this->Installed) return;
    $List = array();
    $this->Manager->EnumDependenciesCascade($this, $List, array(ModuleCondition::NotInstalled));
    $this->Manager->PerformList($List, array(ModuleAction::Install), array(ModuleCondition::NotInstalled));
    $this->Installed = true;
    $this->Enabled = true; // Automatically enable installed module
    $this->InstalledVersion = $this->Version;
    $this->Manager->Modules[$this->Name] = $this;
    $this->DoBeforeInstall();
    if ($this->Manager->OnInstallModule != null)
    {
      call_user_func($this->Manager->OnInstallModule, $this);
    }
    $this->InstallModels($this->Models);
    $this->DoInstall();
  }

  function Uninstall(): void
  {
    if (!$this->Installed) return;
    $this->Stop();
    $this->Installed = false;
    $List = array();
    $this->Manager->EnumSuperiorDependenciesCascade($this, $List, array(ModuleCondition::Installed));
    $this->Manager->PerformList($List, array(ModuleAction::Uninstall), array(ModuleCondition::Installed));
    $this->DoUninstall();
    $this->UninstallModels($this->Models);
    if ($this->Manager->OnUninstallModule != null)
    {
      call_user_func($this->Manager->OnUninstallModule, $this);
    }
    $this->DoAfterUninstall();
  }

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

  function InsertSampleData(): void
  {
    $this->DoInsertSampleData();
  }

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

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

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

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

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

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

  protected function DoStart(): void
  {
  }

  protected function DoStop(): void
  {
  }

  protected function DoBeforeInstall(): void
  {
  }

  protected function DoInstall(): void
  {
  }

  protected function DoUninstall(): void
  {
  }

  protected function DoAfterUninstall(): void
  {
  }

  protected function DoUpgrade(): string
  {
    return '';
  }

  protected function DoInsertSampleData(): void
  {
  }

  function AddModel(Model $Model): void
  {
    $this->Models[get_class($Model)] = $Model;
  }

  function InstallModels(array $Models): void
  {
    if ($this->Manager->OnInstallModel != null)
    {
      foreach ($Models as $Model)
      {
        call_user_func($this->Manager->OnInstallModel, $Model::GetModelDesc(), $this);
      }
    }
  }

  function UninstallModels(array $Models): void
  {
    if ($this->Manager->OnUninstallModel != null)
    {
      foreach (array_reverse($Models) as $Model)
      {
        call_user_func($this->Manager->OnUninstallModel, $Model::GetModelDesc(), $this);
      }
    }
  }
}
