/*
 *  Copyright (c) 2024 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 "media/base/codec_list.h"

#include <cstddef>
#include <map>
#include <vector>

#include "api/payload_type.h"
#include "api/rtc_error.h"
#include "media/base/codec.h"
#include "media/base/media_constants.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/string_encode.h"

namespace webrtc {


namespace {

RTCError CheckInputConsistency(const std::vector<Codec>& codecs) {
  std::map<int, int> pt_to_index;
  // Create a map of payload type to index, and ensure
  // that there are no duplicates.
  for (size_t i = 0; i < codecs.size(); i++) {
    const Codec& codec = codecs[i];
    if (codec.id != PayloadType::NotSet()) {
      auto [it, success] = pt_to_index.insert({codec.id, static_cast<int>(i)});
      if (!success) {
        RTC_LOG(LS_ERROR) << "Duplicate payload type in codec list, " << codec
                          << " and " << codecs[it->second]
                          << " have the same ID";
        return RTC_LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                             << "Duplicate payload type in codec list");
      }
    }
  }
  for (const Codec& codec : codecs) {
    switch (codec.GetResiliencyType()) {
      case Codec::ResiliencyType::kRed:
        // Check that the target codec exists
        break;
      case Codec::ResiliencyType::kRtx: {
        // Check that the target codec exists
        const auto apt_it = codec.params.find(kCodecParamAssociatedPayloadType);
        // Not true - there's a test that deliberately injects a wrong
        // RTX codec (MediaSessionDescriptionFactoryTest.RtxWithoutApt)
        // TODO: https://issues.webrtc.org/384756622 - reject codec earlier and
        // enable check. RTC_DCHECK(apt_it != codec.params.end()); Until that is
        // fixed:
        if (codec.id == PayloadType::NotSet()) {
          // Should not have an apt parameter.
          if (apt_it != codec.params.end()) {
            RTC_LOG(LS_WARNING) << "Surprising condition: RTX codec without "
                                << "PT has an apt parameter";
          }
          // Stop checking the associated PT.
          break;
        }
        if (apt_it == codec.params.end()) {
          RTC_LOG(LS_WARNING) << "Surprising condition: RTX codec without"
                              << " apt parameter: " << codec;
          break;
        }
        int associated_pt;
        if (!(FromString(apt_it->second, &associated_pt))) {
          RTC_LOG(LS_ERROR) << "Non-numeric argument to rtx apt: " << codec
                            << " apt=" << apt_it->second;
          return RTC_LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                               << "Non-numeric argument to rtx apt parameter");
        }
        if (codec.id != PayloadType::NotSet() &&
            pt_to_index.count(associated_pt) != 1) {
          RTC_LOG(LS_WARNING)
              << "Surprising condition: RTX codec APT not found: " << codec
              << " points to a PT that occurs "
              << pt_to_index.count(associated_pt) << " times";
          return RTC_LOG_ERROR(
              RTCError(RTCErrorType::INVALID_PARAMETER)
              << "PT pointed to by rtx apt parameter does not exist");
        }
        // const Codec& referred_codec = codecs[pt_to_index[associated_pt]];
        // Not true:
        // RTC_DCHECK(referred_codec.type == Codec::Type::kVideo);
        // Not true:
        // RTC_DCHECK(referred_codec.GetResiliencyType() ==
        // Codec::ResiliencyType::kNone);
        // TODO: https://issues.webrtc.org/384756623 - figure out if this is
        // expected or not.
        break;
      }
      case Codec::ResiliencyType::kNone:
        break;  // nothing to see here
      default:
        break;  // don't know what to check yet
    }
  }
  return RTCError::OK();
}

}  // namespace

// static
RTCErrorOr<CodecList> CodecList::Create(const std::vector<Codec>& codecs) {
  RTCError error = CheckInputConsistency(codecs);
  if (!error.ok()) {
    return error;
  }
  return CodecList(codecs);
}

bool CodecList::PushIfNotPresent(const Codec& codec) {
  for (const Codec& present_codec : codecs_) {
    if (present_codec.id == codec.id) {
      RTC_DCHECK(present_codec == codec);
      return false;
    }
  }
  push_back(codec);
  return true;
}

void CodecList::CheckConsistency() {
  RTC_DCHECK(CheckInputConsistency(codecs_).ok());
}

}  // namespace webrtc
