/*
 *  Copyright (c) 2011 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.
 */

/*
 * vp8_simulcast.cc
 * WEBRTC VP8 simulcast wrapper interface
 */

#include "vp8_simulcast.h"

#include <string.h>

#include "module_common_types.h"
#include "trace.h"

namespace webrtc {

VP8SimulcastEncoder::VP8SimulcastEncoder() {
  for (int i = 0; i < kMaxSimulcastStreams; i++) {
    encoder_[i] = NULL;
    encode_stream_[i] = false;
    frame_type_[i] = kKeyFrame;
    interpolator_[i] = NULL;
  }
}

VP8SimulcastEncoder::~VP8SimulcastEncoder() {
  for (int i = 0; i < kMaxSimulcastStreams; i++) {
    delete encoder_[i];
    delete interpolator_[i];
    delete [] video_frame_[i]._buffer;
  }
}

WebRtc_Word32 VP8SimulcastEncoder::Release() {
  for (int i = 0; i < kMaxSimulcastStreams; i++) {
    delete encoder_[i];
    encoder_[i] = NULL;
    delete interpolator_[i];
    interpolator_[i] = NULL;
    delete [] video_frame_[i]._buffer;
    video_frame_[i]._buffer = NULL;
    video_frame_[i]._size = 0;
  }
  return 0;
}

WebRtc_Word32 VP8SimulcastEncoder::Reset() {
  for (int i = 0; i < kMaxSimulcastStreams; i++) {
    if (encoder_[i]) {
      encoder_[i]->Reset();
    }
  }
  return 0;
}

WebRtc_Word32 VP8SimulcastEncoder::InitEncode(const VideoCodec* codecSettings,
                                              WebRtc_Word32 numberOfCores,
                                              WebRtc_UWord32 maxPayloadSize) {
  // Store a config copy
  memcpy(&video_codec_, codecSettings, sizeof(VideoCodec));

  // local copy
  VideoCodec video_codec;
  memcpy(&video_codec, codecSettings, sizeof(VideoCodec));
  video_codec.numberOfSimulcastStreams = 0;

  WebRtc_UWord32 bitrate_sum = 0;
  WebRtc_Word32 ret_val = 0;
  for (int i = 0; i < codecSettings->numberOfSimulcastStreams; i++) {
    if (encoder_[i] == NULL) {
      encoder_[i] = new VP8Encoder();
    }
    assert(encoder_[i]);

    if (codecSettings->startBitrate > bitrate_sum) {
      frame_type_[i] = kKeyFrame;
      encode_stream_[i] = true;
    } else {
      // no more bits
      encode_stream_[i] = false;
      continue;
    }
    bitrate_sum += codecSettings->simulcastStream[i].maxBitrate;
    if (codecSettings->startBitrate >= bitrate_sum) {
      video_codec.startBitrate = codecSettings->simulcastStream[i].maxBitrate;
    } else {
      // The last stream will get what ever is left of the budget up to its max
      video_codec.startBitrate =
          codecSettings->startBitrate -
          (bitrate_sum -
          codecSettings->simulcastStream[i].maxBitrate);
    }
    video_codec.maxBitrate = codecSettings->simulcastStream[i].maxBitrate;
    video_codec.qpMax = codecSettings->simulcastStream[i].qpMax;
    video_codec.width = codecSettings->simulcastStream[i].width;
    video_codec.height = codecSettings->simulcastStream[i].height;

    WebRtc_Word32 cores = 1;
    if (video_codec.width > 640 &&
        numberOfCores > codecSettings->numberOfSimulcastStreams) {
      cores = 2;
    }
    ret_val = encoder_[i]->InitEncode(&video_codec,
                                      cores,
                                      maxPayloadSize);
    if (ret_val != 0) {
      WEBRTC_TRACE(webrtc::kTraceError,
                   webrtc::kTraceVideoCoding,
                   -1,
                   "Failed to initialize VP8 simulcast idx: %d.",
                   i);
      return ret_val;
    }
    if (codecSettings->width != video_codec.width ||
        codecSettings->height != video_codec.height) {
      if (interpolator_[i] == NULL) {
        interpolator_[i] = new interpolator();
      }
      interpolator_[i]->Set(
          codecSettings->width,
          codecSettings->height,
          video_codec.width,
          video_codec.height,
          kI420,
          kI420,
          kBilinear);

      if (video_frame_[i]._size <
          (3u * video_codec.width * video_codec.height / 2u)) {
        video_frame_[i]._size = 3 * video_codec.width * video_codec.height / 2;
        delete video_frame_[i]._buffer;
        video_frame_[i]._buffer = new WebRtc_UWord8[video_frame_[i]._size];
        video_frame_[i]._length = 0;
      }
    }
  }
  return ret_val;
}

WebRtc_Word32  VP8SimulcastEncoder::Encode(
    const RawImage& inputImage,
    const CodecSpecificInfo* codecSpecificInfo,
    const VideoFrameType* requestedFrameTypes) {

  WebRtc_Word32 ret_val = -1;
  // we need a local copy since we modify it
  CodecSpecificInfo info = *codecSpecificInfo;

  const int numberOfStreams = video_codec_.numberOfSimulcastStreams;

  for (int i = 0; i < numberOfStreams; i++) {
    if (encode_stream_[i]) {
       video_frame_[i]._timeStamp = inputImage._timeStamp;
    }
    if (requestedFrameTypes[i] == kKeyFrame) {
      // always do a keyframe if asked to
      frame_type_[i] = kKeyFrame;
    } else if (frame_type_[i] == kKeyFrame) {
        // don't write over a previusly requested keyframe
    } else if (frame_type_[i] == kGoldenFrame) {
      if (requestedFrameTypes[i] == kAltRefFrame) {
        // request for both AltRef and Golden upgrade to keyframe
        frame_type_[i] = kKeyFrame;
      }
    } else if (frame_type_[i] == kAltRefFrame) {
      if (requestedFrameTypes[i] == kGoldenFrame) {
        // request for both AltRef and Golden upgrade to keyframe
        frame_type_[i] = kKeyFrame;
      }
    } else if (frame_type_[i] == kDeltaFrame) {
      // if the current is delta set requested
      frame_type_[i] = requestedFrameTypes[i];
    }
  }

  for (int i = 0; i < numberOfStreams; i++) {
    if (encoder_[i] && encode_stream_[i]) {
      // need the simulcastIdx to keep track of which encoder encoded the frame
      info.codecSpecific.VP8.simulcastIdx = i;
      VideoFrameType requested_frame_type = frame_type_[i];
      if (interpolator_[i]) {
        interpolator_[i]->Interpolate(inputImage._buffer,
                                      video_frame_[i]._buffer,
                                      video_frame_[i]._size);
        video_frame_[i]._length =
            3 *
            video_codec_.simulcastStream[i].width *
            video_codec_.simulcastStream[i].height /
            2;
        ret_val = encoder_[i]->Encode(video_frame_[i],
                                      &info,
                                      &requested_frame_type);
      } else {
        ret_val = encoder_[i]->Encode(inputImage,
                                      &info,
                                      &requested_frame_type);
      }
      if (ret_val < 0) {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCoding, -1,
                     "Encode error:%d on stream:%d", ret_val, i);
        return ret_val;
      }
      frame_type_[i] = kDeltaFrame;
    }
  }
  return ret_val;
}

WebRtc_Word32 VP8SimulcastEncoder::RegisterEncodeCompleteCallback(
    EncodedImageCallback* callback) {
  WebRtc_Word32 ret_val = 0;
  for (int i = 0; i < kMaxSimulcastStreams; i++) {
    if (encoder_[i]) {
      ret_val = encoder_[i]->RegisterEncodeCompleteCallback(callback);
      if (ret_val < 0) {
        WEBRTC_TRACE(webrtc::kTraceError,
                     webrtc::kTraceVideoCoding,
                     -1,
                     "RegisterEncodeCompleteCallback error:%d on stream:%d",
                     ret_val,
                     i);
        return ret_val;
      }
    }
  }
  return ret_val;
}

WebRtc_Word32 VP8SimulcastEncoder::SetPacketLoss(WebRtc_UWord32 packetLoss) {
  WebRtc_Word32 ret_val = 0;
  for (int i = 0; i < kMaxSimulcastStreams; i++) {
    if (encoder_[i]) {
      ret_val = encoder_[i]->SetPacketLoss(packetLoss);
      if (ret_val < 0) {
        WEBRTC_TRACE(webrtc::kTraceError,
                     webrtc::kTraceVideoCoding,
                     -1,
                     "SetPacketLoss error:%d on stream:%d",
                     ret_val,
                     i);
        return ret_val;
      }
    }
  }
  return ret_val;
}

WebRtc_Word32 VP8SimulcastEncoder::SetRates(WebRtc_UWord32 new_bitrate,
                                            WebRtc_UWord32 frame_rate) {
  WEBRTC_TRACE(webrtc::kTraceStateInfo, webrtc::kTraceVideoCoding, -1,
               "VP8 simulcast SetRates(%d,%d)", new_bitrate, frame_rate);

  WebRtc_UWord32 bitrate_sum = 0;
  WebRtc_Word32 ret_val = 0;
  for (int i = 0; i < video_codec_.numberOfSimulcastStreams; i++) {
    if (new_bitrate > bitrate_sum) {
      if (!encode_stream_[i]) {
        frame_type_[i] = kKeyFrame;
        encode_stream_[i] = true;
      }
    } else {
      // no more bits
      encode_stream_[i] = false;
      continue;
    }
    WebRtc_UWord32 stream_bitrate = 0;
    bitrate_sum += video_codec_.simulcastStream[i].maxBitrate;
    if (new_bitrate >= bitrate_sum) {
      stream_bitrate = video_codec_.simulcastStream[i].maxBitrate;
    } else {
      stream_bitrate =
          new_bitrate -
          (bitrate_sum -
          video_codec_.simulcastStream[i].maxBitrate);
    }
    ret_val = encoder_[i]->SetRates(stream_bitrate, frame_rate);
    if (ret_val < 0) {
      WEBRTC_TRACE(webrtc::kTraceError,
                   webrtc::kTraceVideoCoding,
                   -1,
                   "VP8 error stream:%d SetRates(%d,%d)",
                   i, stream_bitrate, frame_rate);
    } else {
      WEBRTC_TRACE(webrtc::kTraceStateInfo,
                   webrtc::kTraceVideoCoding,
                   -1,
                   "VP8 stream:%d SetRates(%d,%d)",
                   i, stream_bitrate, frame_rate);
    }
  }
  return ret_val;
}

WebRtc_Word32 VP8SimulcastEncoder::VersionStatic(WebRtc_Word8 *version,
                                                 WebRtc_Word32 length) {
  const WebRtc_Word8* str = "WebM/VP8 simulcast version 1.0.0\n";
  WebRtc_Word32 verLen = (WebRtc_Word32)strlen(str);
  if (verLen > length) {
    return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
  }
  strncpy(version, str, length);
  return verLen;
}

WebRtc_Word32 VP8SimulcastEncoder::Version(WebRtc_Word8 *version,
                                           WebRtc_Word32 length) const {
  return VersionStatic(version, length);
}
}  // namespace webrtc
