/*
 *  Copyright 2022 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "modules/desktop_capture/linux/wayland/screencast_stream_utils.h"

#include <libdrm/drm_fourcc.h>
#include <pipewire/pipewire.h>
#include <spa/param/format.h>
#include <spa/param/param.h>
#include <spa/param/video/format.h>
#include <spa/pod/builder.h>
#include <spa/pod/iter.h>
#include <spa/pod/pod.h>
#include <spa/pod/vararg.h>
#include <spa/utils/type.h>

#include <cstdint>
#include <optional>
#include <tuple>
#include <vector>

#include "modules/desktop_capture/linux/wayland/egl_dmabuf.h"

#if !PW_CHECK_VERSION(0, 3, 29)
#define SPA_POD_PROP_FLAG_MANDATORY (1u << 3)
#endif
#if !PW_CHECK_VERSION(0, 3, 33)
#define SPA_POD_PROP_FLAG_DONT_FIXATE (1u << 4)
#endif

namespace webrtc {

constexpr uint32_t kSupportedPixelFormats[] = {
    SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_RGBA, SPA_VIDEO_FORMAT_BGRx,
    SPA_VIDEO_FORMAT_RGBx};

void BuildBaseFormatParams(spa_pod_builder* builder,
                           uint32_t format,
                           const struct spa_rectangle* resolution,
                           const struct spa_fraction* frame_rate) {
  spa_pod_builder_add(builder, SPA_FORMAT_mediaType,
                      SPA_POD_Id(SPA_MEDIA_TYPE_video), 0);
  spa_pod_builder_add(builder, SPA_FORMAT_mediaSubtype,
                      SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0);
  spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0);

  if (resolution) {
    spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_size,
                        SPA_POD_Rectangle(resolution), 0);
  } else {
    spa_rectangle pw_min_screen_bounds = spa_rectangle{.width = 1, .height = 1};
    spa_rectangle pw_max_screen_bounds =
        spa_rectangle{.width = UINT32_MAX, .height = UINT32_MAX};
    spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_size,
                        SPA_POD_CHOICE_RANGE_Rectangle(&pw_min_screen_bounds,
                                                       &pw_min_screen_bounds,
                                                       &pw_max_screen_bounds),
                        0);
  }
  if (frame_rate) {
    static const spa_fraction pw_min_frame_rate =
        spa_fraction{.num = 0, .denom = 1};
    spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_framerate,
                        SPA_POD_CHOICE_RANGE_Fraction(
                            frame_rate, &pw_min_frame_rate, frame_rate),
                        0);
    spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_maxFramerate,
                        SPA_POD_CHOICE_RANGE_Fraction(
                            frame_rate, &pw_min_frame_rate, frame_rate),
                        0);
  }
}

void BuildBaseFormat(spa_pod_builder* builder,
                     const struct spa_rectangle* resolution,
                     const struct spa_fraction* frame_rate,
                     std::vector<const spa_pod*>& params) {
  for (uint32_t format : kSupportedPixelFormats) {
    struct spa_pod_frame frame;
    spa_pod_builder_push_object(builder, &frame, SPA_TYPE_OBJECT_Format,
                                SPA_PARAM_EnumFormat);
    BuildBaseFormatParams(builder, format, resolution, frame_rate);
    params.push_back(
        static_cast<spa_pod*>(spa_pod_builder_pop(builder, &frame)));
  }
}

void BuildFullFormat(spa_pod_builder* builder,
                     EglDrmDevice* render_device,
                     const struct spa_rectangle* resolution,
                     const struct spa_fraction* frame_rate,
                     std::vector<const spa_pod*>& params) {
  for (uint32_t format : kSupportedPixelFormats) {
    bool need_fallback_format = false;
    struct spa_pod_frame frame;
    spa_pod_builder_push_object(builder, &frame, SPA_TYPE_OBJECT_Format,
                                SPA_PARAM_EnumFormat);
    BuildBaseFormatParams(builder, format, resolution, frame_rate);
    if (render_device) {
      auto modifiers = render_device->QueryDmaBufModifiers(format);

      if (modifiers.size()) {
        if (modifiers.size() == 1 && modifiers[0] == DRM_FORMAT_MOD_INVALID) {
          spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_modifier,
                               SPA_POD_PROP_FLAG_MANDATORY);
          spa_pod_builder_long(builder, modifiers[0]);
        } else {
          struct spa_pod_frame modifier_frame;
          spa_pod_builder_prop(
              builder, SPA_FORMAT_VIDEO_modifier,
              SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE);
          spa_pod_builder_push_choice(builder, &modifier_frame, SPA_CHOICE_Enum,
                                      0);

          // Add the first modifier twice as the very first value is the
          // default option
          spa_pod_builder_long(builder, modifiers[0]);
          // Add modifiers from the array
          for (int64_t val : modifiers) {
            spa_pod_builder_long(builder, val);
          }
          spa_pod_builder_pop(builder, &modifier_frame);
        }

        need_fallback_format = true;
      }
    }

    params.push_back(
        static_cast<spa_pod*>(spa_pod_builder_pop(builder, &frame)));

    if (need_fallback_format) {
      spa_pod_builder_push_object(builder, &frame, SPA_TYPE_OBJECT_Format,
                                  SPA_PARAM_EnumFormat);
      BuildBaseFormatParams(builder, format, resolution, frame_rate);
      params.push_back(
          static_cast<spa_pod*>(spa_pod_builder_pop(builder, &frame)));
    }
  }
}

}  // namespace webrtc
