<?php

class ModuleSetup extends Module
{
  public UpdateManager $UpdateManager;

  function __construct(System $System)
  {
    global $DatabaseRevision;

    parent::__construct($System);
    $this->Name = 'Setup';
    $this->Version = '1.0';
    $this->Creator = 'Chronos';
    $this->License = 'GNU/GPLv3';
    $this->Description = 'Base setup module';
    $this->Revision = 1;
    $this->Type = ModuleType::System;

    // Check database persistence structure
    $this->UpdateManager = new UpdateManager();
    $this->UpdateManager->Database = &$this->Database;
    $this->UpdateManager->Revision = $DatabaseRevision;
    $this->UpdateManager->InstallMethod = 'FullInstall';
  }

  static function Cast(Module $Module): ModuleSetup
  {
    if ($Module instanceof ModuleSetup)
    {
      return $Module;
    }
    throw new Exception('Expected ModuleSetup type but '.gettype($Module));
  }

  function DoStart(): void
  {
    Core::Cast($this->System)->RegisterPage([''], 'PageSetupRedirect');
    Core::Cast($this->System)->RegisterPage(['setup'], 'PageSetup');
  }

  function DoStop(): void
  {
    unset($this->UpdateManager);
    Core::Cast($this->System)->UnregisterPage(['']);
    Core::Cast($this->System)->UnregisterPage(['setup']);
  }

  function CheckState(): bool
  {
    return $this->Database->Connected() and $this->UpdateManager->IsInstalled() and
      $this->UpdateManager->IsUpToDate();
  }

  function DoUpgrade(): string
  {
    $Updates = new Updates();
    $this->UpdateManager->Trace = $Updates->Get();
    $Output = $this->UpdateManager->Upgrade();
    return $Output;
  }
}

class PageSetup extends Page
{
  public UpdateManager $UpdateManager;
  public array $ConfigDefinition;
  public array $Config;
  public int $DatabaseRevision;
  public int $Revision;
  public string $ConfigDir;
  public array $YesNo;

  function __construct(System $System)
  {
    parent::__construct($System);
    $this->Title = T('Application setup');
    //$this->ParentClass = 'PageSetupRedirect';
    $this->ConfigDir = dirname(dirname(dirname(__FILE__))).'/Config';
    $this->YesNo = array(false => T('No'), true => T('Yes'));
  }

  function LoginPanel(): string
  {
    $Output = '<h3>Přihlášení k instalaci</h3>'.
      '<form action="?" method="post">'.
      '<table>'.
      '<tr><td>Systémové heslo:</td><td> <input type="password" name="SystemPassword" value=""/></td></tr>'.
      '</table>'.
      '<input type="submit" name="login" value="'.T('Login').'"/>'.
      '</form>';
    return $Output;
  }

  function ControlPanel(): string
  {
    $Output = '<h3>'.T('Instance management').'</h3>';

    $Output .= 'Je připojení k databázi: '.$this->YesNo[$this->UpdateManager->Database->Connected()].'<br/>';
    if ($this->UpdateManager->Database->Connected())
    {
      $Output .= 'Je instalováno: '.$this->YesNo[$this->UpdateManager->IsInstalled()].'<br/>';
      if ($this->UpdateManager->IsInstalled())
        $Output .= 'Je aktuální: '.$this->YesNo[$this->UpdateManager->IsUpToDate()].'<br/>'.
        'Verze databáze: '.$this->UpdateManager->GetDbVersion().'<br/>';
      $Output .= 'Verze databáze kódu: '.$this->UpdateManager->Revision.'<br/>';
      if ($this->UpdateManager->IsInstalled())
      {
        if (!$this->UpdateManager->IsUpToDate())
          $Output .= '<a href="?action=upgrade">'.T('Upgrade').'</a> ';
        $Output .= '<a href="?action=insert_sample_data">Vložit vzorová data</a> ';
        $Output .= '<a href="?action=reload-modules">Obnovit seznam modulů</a> ';
        $Output .= '<a href="?action=uninstall">Odinstalovat</a> ';
        $Output .= '<a href="'.$this->System->Link('/modules/').'">Správa modulů</a> ';
        $Output .= '<a href="?action=models">Přegenerovat modely</a> ';
      } else $Output .= '<a href="?action=install">Instalovat</a> ';
    }
    $Output .= '<a href="?action=configure">Nastavit</a> ';
    $Output .= '<a href="?action=logout">Odhlásit</a> ';
    $Output .= '<a href="'.$this->System->Link('/').'">'.T('Go to main page').'</a> ';
    $Output .= '';
    return $Output;
  }

  function Show(): string
  {
    global $ConfigDefinition, $DatabaseRevision, $Config, $Updates;

    $this->UpdateManager = ModuleSetup::Cast($this->System->GetModule('Setup'))->UpdateManager;
    $DefaultConfig = new DefaultConfig();
    $this->ConfigDefinition = $DefaultConfig->Get();
    $this->DatabaseRevision = $DatabaseRevision;
    $this->Config = &$Config;

    $Output = '';
    if (isset($this->Config))
    {
      if (!array_key_exists('SystemPassword', $_SESSION)) $_SESSION['SystemPassword'] = '';
      if (array_key_exists('login', $_POST)) $_SESSION['SystemPassword'] = $_POST['SystemPassword'];
      if (sha1($_SESSION['SystemPassword']) != $this->Config['SystemPassword'])
      {
        $Output .= $this->LoginPanel();
      } else
      {
        if (array_key_exists('action', $_GET)) $Action = $_GET['action'];
          else $Action = '';
        if ($Action == 'logout')
        {
          $_SESSION['SystemPassword'] = '';
          $Output .= 'Odhlášen';
          $Output .= $this->LoginPanel();
        }
        else if ($Action == 'models')
        {
          $this->System->FormManager->UpdateSQLMeta();
        }
        else if ($Action == 'upgrade')
        {
          $Output .= '<h3>Povýšení</h3>';
          try
          {
            $Output .= ModuleSetup::Cast($this->System->GetModule('Setup'))->DoUpgrade();
            $this->System->ModuleManager->UpgradeAll(array(ModuleCondition::System));
          } catch (Exception $E)
          {
            $Output .= $this->SystemMessage('Chyba aktualizace',
              'Došlo k chybě v SQL dotazu při aktualizaci: <br/>'.$E->getMessage());
          }
          $Output .= $this->ControlPanel();
        }
        else if ($Action == 'install')
        {
          $Output .= '<h3>Instalace systém</h3>';
          global $DatabaseRevision;

          $this->Database->query("CREATE TABLE IF NOT EXISTS `".$this->UpdateManager->VersionTable."` (".
            '`Id` int(11) NOT NULL AUTO_INCREMENT, '.
            '`Revision` int(11) NOT NULL, '.
            'PRIMARY KEY (`Id`) '.
            ') ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;');
          $DbResult = $this->Database->select($this->UpdateManager->VersionTable, 'Id');
          if ($DbResult->num_rows == 0)
          {
            $this->Database->query("INSERT INTO `".$this->UpdateManager->VersionTable.
              "` (`Id`, `Revision`) VALUES (1, ".$DatabaseRevision.");");
          }
          $this->System->ModuleManager->InstallAll(array(ModuleCondition::System));
          $this->System->ModuleManager->LoadModules();
          $this->System->ModuleManager->SaveState();
          //$Output .= ModuleSetup::Cast($this->System->GetModule('Setup'))->Upgrade();
          $Output .= $this->ControlPanel();
        }
        else if ($Action == 'uninstall')
        {
          $Output .= '<h3>Odinstalace vše</h3>';
          $this->System->ModuleManager->UninstallAll(array(ModuleCondition::All));
          $this->Database->query('DROP TABLE IF EXISTS `'.$this->UpdateManager->VersionTable.'`');
          $Output .= $this->ControlPanel();
        }
        else if ($Action == 'reload-modules')
        {
          $Output .= '<h3>Znovunačtení seznamu modulů</h3>';
          $this->System->ModuleManager->LoadModules();
          $this->System->ModuleManager->SaveState();
          $Output .= $this->ControlPanel();
        }
        else if ($Action == 'insert_sample_data')
        {
          $Output .= '<h3>Vložení vzorových dat</h3>';
          $this->System->ModuleManager->Perform(array(ModuleAction::InsertSampleData), array(ModuleCondition::Installed));
          $Output .= $this->ControlPanel();
        }
        else if ($Action == 'configure_save')
        {
           $Output .= $this->ConfigSave($this->Config);
           $Output .= $this->ControlPanel();
        }
        else if ($Action == 'configure')
        {
          $Output .= $this->PrepareConfig($this->Config);
        }
        else
        {
          $Output .= $this->ControlPanel();
        }
      }
    } else
    {
      if (array_key_exists('configure_save', $_POST))
      {
        $Output .= $this->ConfigSave(array());
        $Output .= 'Pokračujte k přihlášení <a href="">zde</a>';
      } else {
        $Output .= $this->PrepareConfig(array());
      }
    }
    return $Output;
  }

  function PrepareConfig($Config): string
  {
    $Output = '';
    if (!file_exists($this->ConfigDir.'/Config.php') and !is_writable($this->ConfigDir))
      $Output .= 'Varování: Konfigurační soubor nebude možné zapsat, protože složka "'.$this->ConfigDir.'" není povolená pro zápis!';
    if (file_exists($this->ConfigDir.'/Config.php') and !is_writable($this->ConfigDir.'/Config.php'))
      $Output .= 'Varování: Konfigurační soubor nebude možné zapsat, protože soubor "'.$this->ConfigDir.'/Config.php" není povolen pro zápis!';
    $Output .= '<h3>Nastavení systému</h3>'.
      '<form action="?action=configure_save" method="post">'.
      '<table>';
    foreach ($this->ConfigDefinition as $Def)
    {
      $PathParts = explode('/', $Def['Name']);
      $TempConfig = &$Config;
      foreach ($PathParts as $Part)
        if (array_key_exists($Part, $TempConfig))
        {
          $TempConfig = &$TempConfig[$Part];
        }
      if (!is_array($TempConfig)) $Value = $TempConfig;
        else $Value = $Def['Default'];
      $Output .= '<tr><td>'.$Def['Title'].'</td><td>';
      if ($Def['Type'] == 'String') $Output .= '<input type="text" name="'.$Def['Name'].'" value="'.$Value.'"/>';
      if ($Def['Type'] == 'Password') $Output .= '<input type="password" name="'.$Def['Name'].'"/>';
      if ($Def['Type'] == 'PasswordEncoded') $Output .= '<input type="password" name="'.$Def['Name'].'"/>';
      if ($Def['Type'] == 'Integer') $Output .= '<input type="text" name="'.$Def['Name'].'" value="'.$Value.'"/>';
      if ($Def['Type'] == 'Float') $Output .= '<input type="text" name="'.$Def['Name'].'" value="'.$Value.'"/>';
      if ($Def['Type'] == 'Boolean') $Output .= '<input type="text" name="'.$Def['Name'].'" value="'.$Value.'"/>';
      if ($Def['Type'] == 'Array') $Output .= '<input type="text" name="'.$Def['Name'].'" value="'.implode(',', $Value).'"/>';
    }
    $Output .= '</td></tr>'.
      '<tr><td colspan="2"><input type="submit" name="configure_save" value="'.T('Save').'"/></td></tr>'.
      '</table>'.
      '</form>';
    return $Output;
  }

  function ConfigSave($DefaultConfig)
  {
    $Config = $DefaultConfig;
    foreach ($this->ConfigDefinition as $Def)
    {
      $Value = null;
      if ($Def['Type'] == 'String') if (array_key_exists($Def['Name'], $_POST))
        $Value = $_POST[$Def['Name']];
      if ($Def['Type'] == 'Password') if (array_key_exists($Def['Name'], $_POST) and ($_POST[$Def['Name']] != ''))
        $Value = $_POST[$Def['Name']];
      if ($Def['Type'] == 'PasswordEncoded') if (array_key_exists($Def['Name'], $_POST) and ($_POST[$Def['Name']] != ''))
        $Value = sha1($_POST[$Def['Name']]);
      if ($Def['Type'] == 'Integer') if (array_key_exists($Def['Name'], $_POST))
        $Value = $_POST[$Def['Name']];
      if ($Def['Type'] == 'Float') if (array_key_exists($Def['Name'], $_POST))
        $Value = $_POST[$Def['Name']];
      if ($Def['Type'] == 'Boolean') if (array_key_exists($Def['Name'], $_POST))
        $Value = $_POST[$Def['Name']];
      if ($Def['Type'] == 'Array') if (array_key_exists($Def['Name'], $_POST))
        $Value = explode(',', $_POST[$Def['Name']]);
      if (!is_null($Value))
      {
        $PathParts = explode('/', $Def['Name']);
        $TempConfig = &$Config;
        foreach ($PathParts as $Part)
        {
          $TempConfig = &$TempConfig[$Part];
        }
        if (!is_array($TempConfig)) $TempConfig = $Value;
        else $Value = $Def['Default'];
      }
    }
    $ConfigText = $this->CreateConfig($Config);
    file_put_contents($this->ConfigDir.'/Config.php', $ConfigText);
    $Output = 'Konfigurace nastavena<br/>';
    return $Output;
  }

  function CreateConfig($Config): string
  {
    $Output = "<?php\n\n".
      "\$IsDeveloper = array_key_exists('REMOTE_ADDR', \$_SERVER) and  in_array(\$_SERVER['REMOTE_ADDR'], array('127.0.0.1'));\n\n";

    foreach ($this->ConfigDefinition as $Def)
    {
      $PathParts = explode('/', $Def['Name']);
      $Output .= "\$Config";
      foreach ($PathParts as $Part)
        $Output .= "['".$Part."']";
      $TempConfig = &$Config;
      $Output .= ' = ';
      foreach ($PathParts as $Part)
        if (array_key_exists($Part, $TempConfig))
        {
          $TempConfig = &$TempConfig[$Part];
        }
      if (!is_array($TempConfig)) $Value = $TempConfig;
        else $Value = $Def['Default'];
      if ($Def['Type'] == 'Array')
      {
        $Output .= ' array(';
        foreach ($Value as $Index => $Item)
          $Output .= '\''.$Item.'\', ';
        $Output .= ')';
      }
      else $Output .= "'".$Value."'";
      $Output .= ";\n";
    }
    $Output .= "\n\n";
    return $Output;
  }
}

class PageSetupRedirect extends Page
{
  function Show(): string
  {
    $Output = '';
    if (!$this->Database->Connected())
    {
      $Output .= T('Can\'t connect to database.').'<br>';
    } else
    {
      if (!ModuleSetup::Cast($this->System->GetModule('Setup'))->UpdateManager->IsInstalled())
      {
        $Output .= T('System requires database initialization.').'<br>';
      } else
      if (!ModuleSetup::Cast($this->System->GetModule('Setup'))->UpdateManager->IsUpToDate())
      {
        $Output .= T('System requires database upgrade.').'<br>';
      }
    }
    $Output .= sprintf(T('Front page was not configured. Continue to %s.'), '<a href="'.$this->System->Link('/setup/').'">'.T('setup').'</a>');
    return $Output;
  }
}
