<?php

declare(strict_types=1);

namespace Drupal\ffmpeg_media_streaming\Plugin\FFmpegOutput;

use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\ffmpeg_media\Attribute\FFmpegOutput;
use Drupal\ffmpeg_media\ValueObject\ConversionOptions;

/**
 * RTP (Real-time Transport Protocol) output format plugin.
 *
 * Provides low-latency streaming with SDP file generation.
 * Supports AV1 RTP packetization with FFmpeg 8.0 or later.
 *
 * @see https://www.rfc-editor.org/rfc/rfc3550
 */
#[FFmpegOutput(
  id: 'rtp',
  label: new TranslatableMarkup('RTP (Real-time Transport Protocol)'),
  description: new TranslatableMarkup('Low-latency streaming with SDP file generation and AV1 packetizer support.'),
  extension: 'sdp',
  mimeType: 'application/sdp',
  supportedCodecs: ['h264', 'hevc', 'vp9', 'av1'],
  defaultCodec: 'h264',
  supportedAudioCodecs: ['aac', 'libopus'],
  weight: 45,
)]
final class RtpOutput extends LowLatencyStreamingOutputBase {

  /**
   * Default RTP port for video streaming.
   */
  private const int DEFAULT_VIDEO_PORT = 5004;

  /**
   * Default RTP port for audio streaming.
   */
  private const int DEFAULT_AUDIO_PORT = 5006;

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function buildArguments(ConversionOptions $options): array {
    $config = $this->configFactory->get('ffmpeg_media_streaming.settings');
    $ttl = $config->get('rtp_ttl') ?? 64;

    $args = [
      '-f', 'rtp',
    ];

    // Add low-latency encoding options.
    $args = array_merge($args, $this->buildLowLatencyOptions());

    // RTP-specific options.
    $args[] = '-ttl';
    $args[] = (string) $ttl;

    // Packet size optimization for network efficiency.
    $args[] = '-pkt_size';
    $args[] = '1316';

    // Apply audio port for separate audio RTP stream.
    $audioPort = $config->get('rtp_audio_port') ?? self::DEFAULT_AUDIO_PORT;
    $destinationHost = $config->get('rtp_destination_host') ?? '127.0.0.1';
    $args[] = '-f';
    $args[] = 'rtp';
    $args[] = sprintf('rtp://%s:%d', $destinationHost, (int) $audioPort);

    return $args;
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function generateOutputPath(string $inputPath): string {
    $config = $this->configFactory->get('ffmpeg_media_streaming.settings');
    $destinationHost = $config->get('rtp_destination_host') ?? '127.0.0.1';
    $videoPort = $config->get('rtp_video_port') ?? self::DEFAULT_VIDEO_PORT;

    // RTP outputs to a network destination.
    return sprintf('rtp://%s:%d', $destinationHost, $videoPort);
  }

  /**
   * Generates an SDP file path for the RTP stream.
   *
   * @param string $basePath
   *   Base directory for the SDP file.
   * @param string $streamName
   *   Name of the stream.
   *
   * @return string
   *   Full path to the SDP file.
   */
  #[\NoDiscard]
  public function generateSdpPath(string $basePath, string $streamName): string {
    return $basePath . DIRECTORY_SEPARATOR . $streamName . '.sdp';
  }

  /**
   * Builds FFmpeg arguments for SDP file generation.
   *
   * Only includes SDP arguments when rtp_generate_sdp is enabled in config.
   *
   * @param string $sdpPath
   *   Path where the SDP file should be written.
   *
   * @return array<int, string>
   *   FFmpeg arguments for SDP output, or empty if SDP generation is disabled.
   */
  #[\NoDiscard]
  public function buildSdpArguments(string $sdpPath): array {
    $config = $this->configFactory->get('ffmpeg_media_streaming.settings');
    $generateSdp = $config->get('rtp_generate_sdp') ?? TRUE;

    if (!$generateSdp) {
      return [];
    }

    return [
      '-sdp_file', $sdpPath,
    ];
  }

  /**
   * Builds low-latency encoding options for real-time RTP streaming.
   *
   * @return array<int, string>
   *   FFmpeg arguments for low-latency encoding.
   */
  #[\NoDiscard]
  protected function buildLowLatencyOptions(): array {
    return [
      ...$this->buildBaseLowLatencyOptions(),
      // Minimize buffer delay.
      '-max_delay', '0',
      '-flush_packets', '1',
    ];
  }

  /**
   * Builds arguments for codec-specific RTP packetization.
   *
   * @param string $codecId
   *   The codec plugin ID.
   *
   * @return array<int, string>
   *   FFmpeg arguments for RTP packetization.
   */
  #[\Override]
  #[\NoDiscard]
  public function buildCodecSpecificArguments(string $codecId): array {
    return match ($codecId) {
      'h264' => [
        // H.264 RTP uses dynamic payload type 96.
        '-payload_type', '96',
      ],
      'hevc' => [
        // HEVC/H.265 RTP specific options.
        '-payload_type', '97',
        '-strict', 'experimental',
      ],
      'vp9' => [
        // VP9 RTP payload configuration.
        '-payload_type', '99',
      ],
      'av1' => [
        // AV1 RTP packetizer (FFmpeg 8.0+).
        '-payload_type', '100',
        '-strict', 'experimental',
      ],
      default => [],
    };
  }

  /**
   * Builds RTP URL with multicast support.
   *
   * @param string $host
   *   Destination host (unicast or multicast address).
   * @param int $port
   *   Destination port.
   * @param int|null $ttl
   *   Time-to-live for multicast (NULL for unicast).
   *
   * @return string
   *   Complete RTP URL.
   */
  #[\NoDiscard]
  public function buildRtpUrl(string $host, int $port, ?int $ttl = NULL): string {
    $url = sprintf('rtp://%s:%d', $host, $port);

    if ($ttl !== NULL && $this->isMulticastAddress($host)) {
      $url .= sprintf('?ttl=%d', $ttl);
    }

    return $url;
  }

  /**
   * Checks if an IP address is a multicast address.
   *
   * @param string $ip
   *   IP address to check.
   *
   * @return bool
   *   TRUE if the address is a multicast address.
   */
  #[\NoDiscard]
  protected function isMulticastAddress(string $ip): bool {
    $parts = explode('.', $ip);
    if (count($parts) !== 4) {
      return FALSE;
    }

    $firstOctet = (int) $parts[0];
    // Multicast addresses range from 224.0.0.0 to 239.255.255.255.
    return $firstOctet >= 224 && $firstOctet <= 239;
  }

  /**
   * Gets the default RTP configuration.
   *
   * @return array<string, int|string>
   *   Default configuration values for RTP streaming.
   */
  #[\NoDiscard]
  public function getDefaultConfiguration(): array {
    return [
      'destination_host' => '127.0.0.1',
      'video_port' => self::DEFAULT_VIDEO_PORT,
      'audio_port' => self::DEFAULT_AUDIO_PORT,
      'ttl' => 64,
      'pkt_size' => 1316,
      'video_bitrate' => '2500k',
      'audio_bitrate' => '128k',
    ];
  }

  /**
   * Generates SDP content for manual configuration.
   *
   * @param string $sessionName
   *   Name of the streaming session.
   * @param string $host
   *   Destination host.
   * @param int $videoPort
   *   Video RTP port.
   * @param int $audioPort
   *   Audio RTP port.
   * @param string $codecId
   *   Video codec identifier.
   *
   * @return string
   *   SDP file content.
   */
  #[\NoDiscard]
  public function generateSdpContent(
    string $sessionName,
    string $host,
    int $videoPort,
    int $audioPort,
    string $codecId,
  ): string {
    $payloadType = match ($codecId) {
      'h264' => 96,
      'hevc' => 97,
      'vp9' => 99,
      'av1' => 100,
      default => 96,
    };

    $codecName = match ($codecId) {
      'h264' => 'H264',
      'hevc' => 'H265',
      'vp9' => 'VP9',
      'av1' => 'AV1',
      default => 'H264',
    };

    $sdp = "v=0\r\n";
    $sdp .= "o=- 0 0 IN IP4 {$host}\r\n";
    $sdp .= "s={$sessionName}\r\n";
    $sdp .= "c=IN IP4 {$host}\r\n";
    $sdp .= "t=0 0\r\n";
    $sdp .= "m=video {$videoPort} RTP/AVP {$payloadType}\r\n";
    $sdp .= "a=rtpmap:{$payloadType} {$codecName}/90000\r\n";
    $sdp .= "m=audio {$audioPort} RTP/AVP 111\r\n";
    $sdp .= "a=rtpmap:111 opus/48000/2\r\n";

    return $sdp;
  }

}
