// Copyright (c) 2013 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 <string.h>
#include <iterator>
#include <sstream>
#include <string>
#include <vector>

#include "ppapi/c/pp_errors.h"
#include "ppapi/cpp/instance.h"
#include "ppapi/cpp/message_loop.h"
#include "ppapi/cpp/module.h"
#include "ppapi/cpp/private/video_destination_private.h"
#include "ppapi/cpp/private/video_frame_private.h"
#include "ppapi/cpp/private/video_source_private.h"
#include "ppapi/cpp/var.h"
#include "ppapi/utility/completion_callback_factory.h"

// When compiling natively on Windows, PostMessage can be #define-d to
// something else.
#ifdef PostMessage
#undef PostMessage
#endif

namespace {

// Helper functions
std::vector<std::string> SplitStringBySpace(const std::string& str) {
  std::istringstream buf(str);
  std::istream_iterator<std::string> begin(buf), end;
  std::vector<std::string> tokens(begin, end);
  return tokens;
}

// This object is the global object representing this plugin library as long
// as it is loaded.
class VEDemoModule : public pp::Module {
 public:
  VEDemoModule() : pp::Module() {}
  virtual ~VEDemoModule() {}

  virtual pp::Instance* CreateInstance(PP_Instance instance);
};

class VEDemoInstance : public pp::Instance {
 public:
  VEDemoInstance(PP_Instance instance, pp::Module* module);
  virtual ~VEDemoInstance();

  // pp::Instance implementation (see PPP_Instance).
  virtual void HandleMessage(const pp::Var& message_data);

 private:
  void DestinationOpenDone(int32_t result, const std::string& src_url);
  void SourceOpenDone(int32_t result);
  void GetFrameDone(int32_t result, pp::VideoFrame_Private video_frame);
  void KickoffEffect(int32_t result);
  pp::VideoSource_Private video_source_;
  pp::VideoDestination_Private video_destination_;
  bool effect_on_;
  pp::CompletionCallbackFactory<VEDemoInstance> factory_;
  pp::MessageLoop message_loop_;
};

VEDemoInstance::VEDemoInstance(PP_Instance instance, pp::Module* module)
    : pp::Instance(instance),
      video_source_(this),
      video_destination_(this),
      effect_on_(false),
      message_loop_(pp::MessageLoop::GetCurrent()) {
  factory_.Initialize(this);
}

VEDemoInstance::~VEDemoInstance() {
  video_source_.Close();
  video_destination_.Close();
}

void VEDemoInstance::HandleMessage(const pp::Var& message_data) {
  if (message_data.is_string()) {
    std::vector<std::string> messages;
    messages = SplitStringBySpace(message_data.AsString());
    if (messages.empty()) {
      PostMessage(pp::Var("Ignored empty message."));
      return;
    }
    if (messages[0] == "registerStream") {
      if (messages.size() < 3) {
        PostMessage(pp::Var("Got 'registerStream' with incorrect parameters."));
        return;
      }
      // Open destination stream for write.
      video_destination_.Open(
          messages[2],
          factory_.NewCallback(&VEDemoInstance::DestinationOpenDone,
                               messages[1]));
    } else if (messages[0] == "effectOn") {
      effect_on_ = true;
      PostMessage(pp::Var("Effect ON."));
    } else if (messages[0] == "effectOff") {
      effect_on_ = false;
      PostMessage(pp::Var("Effect OFF."));
    }
  }
}

void VEDemoInstance::DestinationOpenDone(int32_t result,
                                         const std::string& src_url) {
  if (result != PP_OK) {
    PostMessage(pp::Var("Failed to open destination stream."));
    return;
  }
  // Open source stream for read.
  video_source_.Open(src_url,
                     factory_.NewCallback(&VEDemoInstance::SourceOpenDone));
}

void VEDemoInstance::SourceOpenDone(int32_t result) {
  if (result != PP_OK) {
    PostMessage(pp::Var("Failed to open source stream."));
    return;
  }
  // Done with the stream register.
  PostMessage(pp::Var("DoneRegistering"));

  // Kick off the processing loop.
  message_loop_.PostWork(factory_.NewCallback(&VEDemoInstance::KickoffEffect));
}

void VEDemoInstance::GetFrameDone(int32_t result,
                                  pp::VideoFrame_Private video_frame) {
  if (result != PP_OK) {
    PostMessage(pp::Var("Failed to get frame."));
    return;
  }

  // Apply the effect to the received frame.
  if (effect_on_) {
    pp::ImageData image_data = video_frame.image_data();
    pp::Size size = image_data.size();
    std::vector<uint8_t> tmp_row(image_data.stride());
    uint8_t* image = static_cast<uint8_t*>(image_data.data());
    for (int i = 0; i < size.height() / 2; ++i) {
      uint8_t* top = image + i * image_data.stride();
      uint8_t* bottom = image + (size.height() - 1 - i) * image_data.stride();
      memcpy(&tmp_row[0], top, image_data.stride());
      memcpy(top, bottom, image_data.stride());
      memcpy(bottom, &tmp_row[0], image_data.stride());
    }
  }

  // Put frame back to destination stream
  video_destination_.PutFrame(video_frame);

  // Trigger for the next frame.
  message_loop_.PostWork(factory_.NewCallback(&VEDemoInstance::KickoffEffect));
}

void VEDemoInstance::KickoffEffect(int32_t /* result */) {
  // Get the frame from the source stream.
  video_source_.GetFrame(
      factory_.NewCallbackWithOutput<pp::VideoFrame_Private>(
          &VEDemoInstance::GetFrameDone));
}

pp::Instance* VEDemoModule::CreateInstance(PP_Instance instance) {
  return new VEDemoInstance(instance, this);
}

}  // anonymous namespace

namespace pp {
// Factory function for your specialization of the Module object.
Module* CreateModule() {
  return new VEDemoModule();
}
}  // namespace pp