#include "stream_encode.h"
#include "stream_encode_worker.h"

using namespace v8;

StreamEncode::StreamEncode(bool isAsync, Local<Object> params): isAsync(isAsync) {
  state = BrotliEncoderCreateInstance(Allocator::Alloc, Allocator::Free, &alloc);

  Local<String> key;
  Local<Value> localVal;
  uint32_t val;

  key = Nan::New<String>("mode").ToLocalChecked();
  if (Nan::Has(params, key).FromJust()) {
    localVal = Nan::Get(params, key).ToLocalChecked();
    val = Nan::To<uint32_t>(localVal).FromJust();
    BrotliEncoderSetParameter(state, BROTLI_PARAM_MODE, val);
  }

  key = Nan::New<String>("quality").ToLocalChecked();
  if (Nan::Has(params, key).FromJust()) {
    localVal = Nan::Get(params, key).ToLocalChecked();
    val = Nan::To<uint32_t>(localVal).FromJust();
    BrotliEncoderSetParameter(state, BROTLI_PARAM_QUALITY, val);
  }

  key = Nan::New<String>("lgwin").ToLocalChecked();
  if (Nan::Has(params, key).FromJust()) {
    localVal = Nan::Get(params, key).ToLocalChecked();
    val = Nan::To<uint32_t>(localVal).FromJust();
    BrotliEncoderSetParameter(state, BROTLI_PARAM_LGWIN, val);
  }

  key = Nan::New<String>("lgblock").ToLocalChecked();
  if (Nan::Has(params, key).FromJust()) {
    localVal = Nan::Get(params, key).ToLocalChecked();
    val = Nan::To<uint32_t>(localVal).FromJust();
    BrotliEncoderSetParameter(state, BROTLI_PARAM_LGBLOCK, val);
  }

  key = Nan::New<String>("disable_literal_context_modeling").ToLocalChecked();
  if (Nan::Has(params, key).FromJust()) {
    localVal = Nan::Get(params, key).ToLocalChecked();
    val = Nan::To<uint32_t>(localVal).FromJust();
    BrotliEncoderSetParameter(state, BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING, val);
  }

  key = Nan::New<String>("size_hint").ToLocalChecked();
  if (Nan::Has(params, key).FromJust()) {
    localVal = Nan::Get(params, key).ToLocalChecked();
    val = Nan::To<uint32_t>(localVal).FromJust();
    BrotliEncoderSetParameter(state, BROTLI_PARAM_SIZE_HINT, val);
  }

  key = Nan::New<String>("large_window").ToLocalChecked();
  if (Nan::Has(params, key).FromJust()) {
    localVal = Nan::Get(params, key).ToLocalChecked();
    val = Nan::To<uint32_t>(localVal).FromJust();
    BrotliEncoderSetParameter(state, BROTLI_PARAM_LARGE_WINDOW, val);
  }

  key = Nan::New<String>("npostfix").ToLocalChecked();
  if (Nan::Has(params, key).FromJust()) {
    localVal = Nan::Get(params, key).ToLocalChecked();
    val = Nan::To<uint32_t>(localVal).FromJust();
    BrotliEncoderSetParameter(state, BROTLI_PARAM_NPOSTFIX, val);
  }

  key = Nan::New<String>("ndirect").ToLocalChecked();
  if (Nan::Has(params, key).FromJust()) {
    localVal = Nan::Get(params, key).ToLocalChecked();
    val = Nan::To<uint32_t>(localVal).FromJust();
    BrotliEncoderSetParameter(state, BROTLI_PARAM_NDIRECT, val);
  }
}

StreamEncode::~StreamEncode() {
  BrotliEncoderDestroyInstance(state);
}

void StreamEncode::Init(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
  Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
  tpl->SetClassName(Nan::New("StreamEncode").ToLocalChecked());
  tpl->InstanceTemplate()->SetInternalFieldCount(1);

  Nan::SetPrototypeMethod(tpl, "transform", Transform);
  Nan::SetPrototypeMethod(tpl, "flush", Flush);

  constructor.Reset(Nan::GetFunction(tpl).ToLocalChecked());
  Nan::Set(target, Nan::New("StreamEncode").ToLocalChecked(),
    Nan::GetFunction(tpl).ToLocalChecked());
}

NAN_METHOD(StreamEncode::New) {
  StreamEncode* obj = new StreamEncode(
    Nan::To<bool>(info[0]).FromJust(),
    Nan::To<Object>(info[1]).ToLocalChecked()
  );
  obj->Wrap(info.This());
  info.GetReturnValue().Set(info.This());
}

NAN_METHOD(StreamEncode::Transform) {
  StreamEncode* obj = ObjectWrap::Unwrap<StreamEncode>(info.Holder());

  Local<Object> buffer = Nan::To<Object>(info[0]).ToLocalChecked();
  obj->next_in = (const uint8_t*) node::Buffer::Data(buffer);
  obj->available_in = node::Buffer::Length(buffer);

  Nan::Callback *callback = new Nan::Callback(info[1].As<Function>());
  StreamEncodeWorker *worker = new StreamEncodeWorker(callback, obj, BROTLI_OPERATION_PROCESS);
  if (obj->isAsync) {
    Nan::AsyncQueueWorker(worker);
  } else {
    worker->Execute();
    worker->WorkComplete();
    worker->Destroy();
  }
}

NAN_METHOD(StreamEncode::Flush) {
  StreamEncode* obj = ObjectWrap::Unwrap<StreamEncode>(info.Holder());

  Nan::Callback *callback = new Nan::Callback(info[1].As<Function>());
  BrotliEncoderOperation op = Nan::To<bool>(info[0]).FromJust()
    ? BROTLI_OPERATION_FINISH
    : BROTLI_OPERATION_FLUSH;
  obj->next_in = nullptr;
  obj->available_in = 0;
  StreamEncodeWorker *worker = new StreamEncodeWorker(callback, obj, op);
  if (obj->isAsync) {
    Nan::AsyncQueueWorker(worker);
  } else {
    worker->Execute();
    worker->WorkComplete();
    worker->Destroy();
  }
}

Nan::Persistent<Function> StreamEncode::constructor;