<?php

class ConfigCheckPorts extends NetworkConfigItem
{
  function CheckPortStatus(string $IP, int $Port, string $Protocol = 'tcp'): int
  {
    $Timeout = 1;
    $State = 0;
    if ($Protocol == 'tcp') $Prefix = '';
      else if ($Protocol == 'udp') $Prefix = 'udp://';
      else throw new Exception('Unsupported protocol "'.$Protocol.'"');
    try 
    {
      $LastErrorReporting = error_reporting();
      error_reporting(0);
      if ($Socket = @fsockopen($Prefix.$IP, $Port, $ErrorNumber, $ErrorString, $Timeout))
      {
        fclose($Socket);
        $State = 1;
      }
    }
    finally
    {
      error_reporting($LastErrorReporting);
    }
    return $State;
  }

  function CheckPorts(): void
  {
    $StartTime = time();

    // Load all ports to memory
    $Ports = array();
    $DbResult = $this->Database->query('SELECT `NetworkPort`.`Id`, `NetworkPort`.`Number`, `NetworkPort`.`Protocol`, '.
      '`NetworkPort`.`Online`, 0 AS `NewOnline`, `NetworkInterface`.`LocalIP` AS `LocalIP` '.
      'FROM `NetworkPort` '.
      'LEFT JOIN `NetworkInterface` ON `NetworkInterface`.`Id`=`NetworkPort`.`Interface` '.
      'LEFT JOIN `NetworkDevice` ON `NetworkDevice`.`Id`=`NetworkInterface`.`Device` '.
      'WHERE (`NetworkPort`.`Enabled`=1) AND (`NetworkInterface`.`LocalIP` !="") AND (`NetworkInterface`.`Enabled`=1) AND'.
      '(`NetworkDevice`.`Used`=1)');
    while ($DbRow = $DbResult->fetch_assoc())
      $Ports[$DbRow['Id']] = $DbRow;

    foreach ($Ports as $Index => $Port)
    {
      if ($Port['Protocol'] == 0) $Port['Protocol'] = 'tcp';
      if ($Port['Protocol'] == 1) $Port['Protocol'] = 'udp';
      $Port['NewOnline'] = $this->CheckPortStatus($Port['LocalIP'], $Port['Number'], $Port['Protocol']);

      // Update last online time if still online
      if ($Port['NewOnline'])
      {
        $DbResult = $this->Database->update('NetworkPort', '`Id` = "'.$Port['Id'].'"',
          array('Online' => 1, 'LastOnline' => TimeToMysqlDateTime($StartTime)));
      }

      // Update UpDown table
      if ($Port['Online'] != $Port['NewOnline'])
      {
        // Online state changed
        $DbResult = $this->Database->query('INSERT INTO `NetworkPortUpDown` (`Port`,
         `State`, `Time`, `Duration`) VALUES ('.$Port['Id'].', '.$Port['NewOnline'].', "'.
          TimeToMysqlDateTime($StartTime).'", NULL)');
        // Update previous record duration in UpDown table
        $this->Database->query('UPDATE `NetworkPortUpDown` AS `TM` SET `Duration` = TIMESTAMPDIFF(SECOND, '.
          '`TM`.`Time`, (SELECT `Time` FROM (SELECT * FROM `NetworkPortUpDown`) AS `TA` WHERE (`TA`.`Time` > `TM`.`Time`) '.
          'AND (`TA`.`Port`=`TM`.`Port`) ORDER BY `TA`.`Time` ASC LIMIT 1)) '.
          'WHERE (`TM`.`Duration` IS NULL) AND (`TM`.`Port` ='.$Port['Id'].')');
      }
    }
    $DbResult = $this->Database->update('NetworkPort', '`LastOnline` < "'.
      TimeToMysqlDateTime($StartTime).'"', array('Online' => 0));

    // Set offline all ports which were not updated as online
    $DbResult = $this->Database->select('NetworkPort', '*', '(`Online` = 1) AND '.
      '(`LastOnline` < "'.TimeToMysqlDateTime($StartTime).'")');
    while ($DbRow = $DbResult->fetch_assoc())
    {
      echo('Port '.$DbRow['Number'].' online but time not updated.'."\n");
    }
    $DbResult = $this->Database->select('NetworkPort', '*', '(`Online` = 0) AND '.
      '(`LastOnline` >= "'.TimeToMysqlDateTime($StartTime).'")');
    while ($DbRow = $DbResult->fetch_assoc())
    {
      echo('Port '.$DbRow['Number'].' not online but time updated.'."\n");
    }
  }

  function Run(): void
  {
    RepeatFunction(60, array($this, 'CheckPorts'));
  }
}
