<?php

class ModuleScheduler extends Module
{
  function __construct(System $System)
  {
    parent::__construct($System);
    $this->Name = 'Scheduler';
    $this->Version = '1.0';
    $this->Creator = 'Chronos';
    $this->License = 'GNU/GPLv3';
    $this->Description = 'Allow to setup and execute planned and recurring tasks';
    $this->Models = array(SchedulerAction::GetClassName(), Scheduler::GetClassName());
  }

  function DoStart(): void
  {
    $this->System->FormManager->RegisterClass('Scheduler', array(
      'Title' => 'Plánovač',
      'Table' => 'Scheduler',
      'DefaultSortColumn' => 'ScheduledTime',
      'Items' => array(
        'Name' => array('Type' => 'String', 'Caption' => 'Jméno', 'Default' => ''),
        'Enabled' => array('Type' => 'Boolean', 'Caption' => 'Povoleno', 'Default' => '0'),
        'ScheduledTime' => array('Type' => 'DateTime', 'Caption' => 'Plánovaný čas', 'Default' => ''),
        'Action' => array('Type' => 'TSchedulerAction', 'Caption' => 'Akce', 'Default' => ''),
        'Period' => array('Type' => 'Integer', 'Caption' => 'Opakovat po', 'Default' => '', 'Null' => true, 'Suffix' => 'sekund'),
        'LastExecutedTime' => array('Type' => 'DateTime', 'Caption' => 'Čas posledního spuštění', 'Default' => '', 'ReadOnly' => true),
        'Duration' => array('Type' => 'TimeDiff', 'Caption' => 'Trvání', 'Default' => '', 'ReadOnly' => true),
        'Log' => array('Type' => 'Text', 'Caption' => 'Poslední záznam', 'Default' => '', 'ReadOnly' => true, 'NotInList' => true),
      ),
    ));
    $this->System->FormManager->RegisterClass('SchedulerAction', array(
      'Title' => 'Akce plánovače',
      'Table' => 'SchedulerAction',
      'DefaultSortColumn' => 'Name',
      'ReadOnly' => true,
      'Items' => array(
        'Name' => array('Type' => 'String', 'Caption' => 'Jméno', 'Default' => '', 'ReadOnly' => true),
        'Class' => array('Type' => 'String', 'Caption' => 'Vykonat třídu', 'Default' => '', 'ReadOnly' => true),
      ),
    ));
    $this->System->FormManager->RegisterFormType('TSchedulerAction', array(
      'Type' => 'Reference',
      'Table' => 'SchedulerAction',
      'Id' => 'Id',
      'Name' => 'Name',
      'Filter' => '1',
    ));
    $this->System->RegisterCommandLine('run-scheduler', 'Runs scheduled tasks',
      array(ModuleScheduler::Cast($this->System->GetModule('Scheduler')), 'Run'));
  }

  function Run(array $Parameters): void
  {
    while (true)
    {
      $DbResult = $this->Database->query('SELECT `Scheduler`.*, `SchedulerAction`.`Class` AS `Class` FROM `Scheduler` '.
        'LEFT JOIN `SchedulerAction` ON `SchedulerAction`.`Id` = `Scheduler`.`Action` '.
        'WHERE (`Scheduler`.`Enabled`=1) AND ( '.
        '(`Scheduler`.`ScheduledTime` < "'.TimeToMysqlDateTime(time()).'") OR '.
        '(`Scheduler`.`ScheduledTime` IS NULL))');
      while ($DbRow = $DbResult->fetch_assoc())
      {
        echo('Executing '.$DbRow['Name']."\n");
        $Output = '';
        $StartTime = time();
        if (class_exists($DbRow['Class']))
        {
          $Class = new $DbRow['Class']($this->System);
          try {
            $Output = $Class->Execute();
          } catch (Exception $E) {
            echo($E->getMessage()."\n");
          }
          echo($Output);
        } else echo('Class '.$DbRow['Class'].' not found'."\n");
        $StopTime = time();
        $Duration = $StopTime - $StartTime;
        $this->Database->update('Scheduler', 'Id='.$DbRow['Id'],
          array('Log' => $Output, 'LastExecutedTime' => TimeToMysqlDateTime($StartTime),
            'Duration' => $Duration));
        if ($DbRow['Period'] != '') {
          if ($DbRow['ScheduledTime'] == '') $NewScheduledTime = $StartTime + $DbRow['Period'];
            else $NewScheduledTime = MysqlDateTimeToTime($DbRow['ScheduledTime']) + $DbRow['Period'];
          if ($NewScheduledTime < $StopTime) $NewScheduledTime = $StopTime + $DbRow['Period'];
          $this->Database->update('Scheduler', 'Id='.$DbRow['Id'],
            array('ScheduledTime' => TimeToMysqlDateTime($NewScheduledTime)));
        }
      }
      echo('.');
      sleep(1);
    }
  }

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

class SchedulerTask extends Model
{
  function Execute(): string
  {
    return '';
  }
}

class ScheduleTaskTest extends SchedulerTask
{
  function Execute(): string
  {
    $Output = '#';
    return $Output;
  }
}

class Scheduler extends Model
{
  static function GetModelDesc(): ModelDesc
  {
    $Desc = new ModelDesc(self::GetClassName());
    $Desc->AddString('Name');
    $Desc->AddBoolean('Enabled');
    $Desc->AddDateTime('ScheduledTime');
    $Desc->AddReference('Action', SchedulerAction::GetClassName());
    $Desc->AddInteger('Period');
    $Desc->AddDateTime('LastExecutedTime');
    $Desc->AddInteger('Duration');
    $Desc->AddText('Log');
    return $Desc;
  }
}

class SchedulerAction extends Model
{
  static function GetModelDesc(): ModelDesc
  {
    $Desc = new ModelDesc(self::GetClassName());
    $Desc->AddString('Name');
    $Desc->AddString('Class');
    return $Desc;
  }
}
