// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ppapi/cpp/video_encoder.h"

#include <stddef.h>

#include "ppapi/c/pp_errors.h"
#include "ppapi/c/ppb_video_encoder.h"
#include "ppapi/cpp/completion_callback.h"
#include "ppapi/cpp/instance_handle.h"
#include "ppapi/cpp/module.h"
#include "ppapi/cpp/module_impl.h"

namespace pp {

namespace {

template <>
const char* interface_name<PPB_VideoEncoder_0_1>() {
  return PPB_VIDEOENCODER_INTERFACE_0_1;
}

template <>
const char* interface_name<PPB_VideoEncoder_0_2>() {
  return PPB_VIDEOENCODER_INTERFACE_0_2;
}

// This struct is used to adapt
// CompletionCallbackWithOutput<std::vector<PP_VideoProfileDescription>>
// to the pre-0.2 APIs, which return PP_VideoProfileDescription_0_1.
// This struct is allocated on the heap, and deleted in
// CallbackProfileDescriptionConverter.
struct CallbackProfileDescription_0_1 {
  explicit CallbackProfileDescription_0_1(const CompletionCallbackWithOutput<
      std::vector<PP_VideoProfileDescription> >& cc)
      : output_profiles(&profiles),
        original_output_profiles(cc.output()),
        original_callback(cc.pp_completion_callback()) {}
  std::vector<PP_VideoProfileDescription_0_1> profiles;
  ArrayOutputAdapter<PP_VideoProfileDescription_0_1> output_profiles;
  PP_ArrayOutput original_output_profiles;
  PP_CompletionCallback original_callback;
};

// Converts data from a 0.1 style callback to 0.2 callback.
void CallbackProfileDescriptionConverter(void* user_data, int32_t result) {
  CallbackProfileDescription_0_1* data =
      static_cast<CallbackProfileDescription_0_1*>(user_data);
  if (result >= 0) {
    PP_VideoProfileDescription* original_profiles =
        static_cast<PP_VideoProfileDescription*>(
            data->original_output_profiles.GetDataBuffer(
                data->original_output_profiles.user_data,
                static_cast<uint32_t>(data->profiles.size()),
                static_cast<uint32_t>(sizeof(PP_VideoProfileDescription))));

    for (size_t i = 0; i < data->profiles.size(); i++) {
      const PP_VideoProfileDescription_0_1& profile = data->profiles[i];

      original_profiles[i].profile = profile.profile;
      original_profiles[i].max_resolution = profile.max_resolution;
      original_profiles[i].max_framerate_numerator =
          profile.max_framerate_numerator;
      original_profiles[i].max_framerate_denominator =
          profile.max_framerate_denominator;
      original_profiles[i].hardware_accelerated =
          PP_FromBool(profile.acceleration == PP_HARDWAREACCELERATION_ONLY);
    }
  }

  // Now execute the original callback.
  PP_RunCompletionCallback(&data->original_callback, result);
  delete data;
}

}  // namespace

VideoEncoder::VideoEncoder() {
}

VideoEncoder::VideoEncoder(const InstanceHandle& instance) {
  if (has_interface<PPB_VideoEncoder_0_2>()) {
    PassRefFromConstructor(
        get_interface<PPB_VideoEncoder_0_2>()->Create(instance.pp_instance()));
  } else if (has_interface<PPB_VideoEncoder_0_1>()) {
    PassRefFromConstructor(
        get_interface<PPB_VideoEncoder_0_1>()->Create(instance.pp_instance()));
  }
}

VideoEncoder::VideoEncoder(const VideoEncoder& other) : Resource(other) {
}

int32_t VideoEncoder::GetSupportedProfiles(const CompletionCallbackWithOutput<
    std::vector<PP_VideoProfileDescription> >& cc) {
  if (has_interface<PPB_VideoEncoder_0_2>()) {
    return get_interface<PPB_VideoEncoder_0_2>()->GetSupportedProfiles(
        pp_resource(), cc.output(), cc.pp_completion_callback());
  } else if (has_interface<PPB_VideoEncoder_0_1>()) {
    // Data for our callback wrapper. The callback handler will delete it.
    CallbackProfileDescription_0_1* data =
        new CallbackProfileDescription_0_1(cc);
    return get_interface<PPB_VideoEncoder_0_1>()->GetSupportedProfiles(
        pp_resource(), data->output_profiles.pp_array_output(),
        PP_MakeCompletionCallback(&CallbackProfileDescriptionConverter, data));
  }
  return cc.MayForce(PP_ERROR_NOINTERFACE);
}

int32_t VideoEncoder::Initialize(const PP_VideoFrame_Format& input_format,
                                 const Size& input_visible_size,
                                 const PP_VideoProfile& output_profile,
                                 const uint32_t initial_bitrate,
                                 PP_HardwareAcceleration acceleration,
                                 const CompletionCallback& cc) {
  if (has_interface<PPB_VideoEncoder_0_2>()) {
    return get_interface<PPB_VideoEncoder_0_2>()->Initialize(
        pp_resource(), input_format, &input_visible_size.pp_size(),
        output_profile, initial_bitrate, acceleration,
        cc.pp_completion_callback());
  } else if (has_interface<PPB_VideoEncoder_0_1>()) {
    return get_interface<PPB_VideoEncoder_0_1>()->Initialize(
        pp_resource(), input_format, &input_visible_size.pp_size(),
        output_profile, initial_bitrate, acceleration,
        cc.pp_completion_callback());
  }
  return cc.MayForce(PP_ERROR_NOINTERFACE);
}

int32_t VideoEncoder::GetFramesRequired() {
  if (has_interface<PPB_VideoEncoder_0_2>()) {
    return get_interface<PPB_VideoEncoder_0_2>()->GetFramesRequired(
        pp_resource());
  } else if (has_interface<PPB_VideoEncoder_0_1>()) {
    return get_interface<PPB_VideoEncoder_0_1>()->GetFramesRequired(
        pp_resource());
  }
  return PP_ERROR_NOINTERFACE;
}

int32_t VideoEncoder::GetFrameCodedSize(Size* coded_size) {
  if (has_interface<PPB_VideoEncoder_0_2>()) {
    return get_interface<PPB_VideoEncoder_0_2>()->GetFrameCodedSize(
        pp_resource(), &coded_size->pp_size());
  } else if (has_interface<PPB_VideoEncoder_0_1>()) {
    return get_interface<PPB_VideoEncoder_0_1>()->GetFrameCodedSize(
        pp_resource(), &coded_size->pp_size());
  }
  return PP_ERROR_NOINTERFACE;
}

int32_t VideoEncoder::GetVideoFrame(
    const CompletionCallbackWithOutput<VideoFrame>& cc) {
  if (has_interface<PPB_VideoEncoder_0_2>()) {
    return get_interface<PPB_VideoEncoder_0_2>()->GetVideoFrame(
        pp_resource(), cc.output(), cc.pp_completion_callback());
  } else if (has_interface<PPB_VideoEncoder_0_1>()) {
    return get_interface<PPB_VideoEncoder_0_1>()->GetVideoFrame(
        pp_resource(), cc.output(), cc.pp_completion_callback());
  }
  return cc.MayForce(PP_ERROR_NOINTERFACE);
}

int32_t VideoEncoder::Encode(const VideoFrame& video_frame,
                             bool force_keyframe,
                             const CompletionCallback& cc) {
  if (has_interface<PPB_VideoEncoder_0_2>()) {
    return get_interface<PPB_VideoEncoder_0_2>()->Encode(
        pp_resource(), video_frame.pp_resource(), PP_FromBool(force_keyframe),
        cc.pp_completion_callback());
  } else if (has_interface<PPB_VideoEncoder_0_1>()) {
    return get_interface<PPB_VideoEncoder_0_1>()->Encode(
        pp_resource(), video_frame.pp_resource(), PP_FromBool(force_keyframe),
        cc.pp_completion_callback());
  }
  return cc.MayForce(PP_ERROR_NOINTERFACE);
}

int32_t VideoEncoder::GetBitstreamBuffer(
    const CompletionCallbackWithOutput<PP_BitstreamBuffer>& cc) {
  if (has_interface<PPB_VideoEncoder_0_2>()) {
    return get_interface<PPB_VideoEncoder_0_2>()->GetBitstreamBuffer(
        pp_resource(), cc.output(), cc.pp_completion_callback());
  } else if (has_interface<PPB_VideoEncoder_0_1>()) {
    return get_interface<PPB_VideoEncoder_0_1>()->GetBitstreamBuffer(
        pp_resource(), cc.output(), cc.pp_completion_callback());
  }
  return cc.MayForce(PP_ERROR_NOINTERFACE);
}

void VideoEncoder::RecycleBitstreamBuffer(
    const PP_BitstreamBuffer& bitstream_buffer) {
  if (has_interface<PPB_VideoEncoder_0_2>()) {
    get_interface<PPB_VideoEncoder_0_2>()->RecycleBitstreamBuffer(
        pp_resource(), &bitstream_buffer);
  } else if (has_interface<PPB_VideoEncoder_0_1>()) {
    get_interface<PPB_VideoEncoder_0_1>()->RecycleBitstreamBuffer(
        pp_resource(), &bitstream_buffer);
  }
}

void VideoEncoder::RequestEncodingParametersChange(uint32_t bitrate,
                                                   uint32_t framerate) {
  if (has_interface<PPB_VideoEncoder_0_2>()) {
    get_interface<PPB_VideoEncoder_0_2>()->RequestEncodingParametersChange(
        pp_resource(), bitrate, framerate);
  } else if (has_interface<PPB_VideoEncoder_0_1>()) {
    get_interface<PPB_VideoEncoder_0_1>()->RequestEncodingParametersChange(
        pp_resource(), bitrate, framerate);
  }
}

void VideoEncoder::Close() {
  if (has_interface<PPB_VideoEncoder_0_2>()) {
    get_interface<PPB_VideoEncoder_0_2>()->Close(pp_resource());
  } else if (has_interface<PPB_VideoEncoder_0_1>()) {
    get_interface<PPB_VideoEncoder_0_1>()->Close(pp_resource());
  }
}

}  // namespace pp
