#include "Extras/Utils/nutils.h"

#include <QApplication>
#include <QMetaType>
#include <QWidget>
#include <string>

#include "core/Component/component_wrap.h"
#include "core/FlexLayout/flexutils.h"
#include "core/YogaWidget/yogawidget.h"

bool extrautils::isNapiValueInt(Napi::Env& env, Napi::Value& num) {
  return env.Global()
      .Get("Number")
      .ToObject()
      .Get("isInteger")
      .As<Napi::Function>()
      .Call({num})
      .ToBoolean()
      .Value();
}

std::string extrautils::getNapiObjectClassName(Napi::Object& object) {
  return object.Get("constructor")
      .As<Napi::Object>()
      .Get("name")
      .As<Napi::String>()
      .Utf8Value();
}

QVariant* extrautils::convertToQVariant(Napi::Env& env, Napi::Value& value) {
  // Warning: Make sure you delete the QVariant fron this function upon use.
  if (value.IsBoolean()) {
    return new QVariant(value.As<Napi::Boolean>().Value());

  } else if (value.IsNumber()) {
    if (isNapiValueInt(env, value)) {
      return new QVariant(value.As<Napi::Number>().Int32Value());
    } else {
      return new QVariant(value.As<Napi::Number>().DoubleValue());
    }

  } else if (value.IsString()) {
    std::string stringValue = value.As<Napi::String>().Utf8Value();
    return new QVariant(stringValue.c_str());

  } else if (value.IsSymbol()) {
    return new QVariant();

  } else if (value.IsArray()) {
    // Note: This assumes an array of strings.
    Napi::Array array = value.As<Napi::Array>();
    QStringList value;
    uint32_t len = array.Length();
    for (uint32_t i = 0; i < len; i++) {
      if (array.Get(i).IsString()) {
        std::string stringValue = array.Get(i).As<Napi::String>().Utf8Value();
        value.append(QString::fromUtf8(stringValue.c_str()));
      }
    }
    return new QVariant(value);

  } else if (value.IsArrayBuffer()) {
    // TODO: fix this
    return new QVariant();

  } else if (value.IsTypedArray()) {
    // TODO: fix this
    return new QVariant();

  } else if (value.IsObject()) {
    Napi::Object object = value.As<Napi::Object>();
    std::string className = getNapiObjectClassName(object);
    QMetaType type = QMetaType::fromName(className.c_str());
    ComponentWrap* componentWrap =
        Napi::ObjectWrap<ComponentWrap>::Unwrap(object);
    return new QVariant(type, componentWrap->rawData);

  } else if (value.IsFunction()) {
    return new QVariant();

  } else if (value.IsPromise()) {
    return new QVariant();

  } else if (value.IsUndefined()) {
    return new QVariant();

  } else if (value.IsNull()) {
    return new QVariant();

  } else if (value.IsBuffer()) {
    // TODO: fix this
    return new QVariant();

  } else if (value.IsExternal()) {
    QVariant* variant = value.As<Napi::External<QVariant>>().Data();
    return variant;

  } else {
    return new QVariant();
  }
}

void* extrautils::configureComponent(void* component) { return component; }

void* extrautils::configureQObject(QObject* object) {
  return configureComponent(object);
}

void* extrautils::configureQWidget(QWidget* widget, bool isLeafNode) {
  YogaWidget* yogaWidget = dynamic_cast<YogaWidget*>(widget);
  if (yogaWidget) {
    flexutils::configureFlexNode(widget, yogaWidget->getFlexNode(), isLeafNode);
  }
  return configureQObject(widget);
}

uint64_t extrautils::hashPointerTo53bit(const void* input) {
  // Hash the address of the object down to something which will
  // fit into the JS 53bit safe integer space.
  uint64_t address = reinterpret_cast<uint64_t>(input);
  uint64_t top8Bits = address & 0xff00000000000000u;
  uint64_t foldedBits = (top8Bits >> 11) ^ address;

  // Clear the top 8bits which we folded, now shift out the last 3 bits
  // Pointers are aligned on 64bit architectures to at least 8bytes
  // boundaries.
  uint64_t result = (foldedBits & ~0xff00000000000000u) >> 3;
  return result;
}

Napi::FunctionReference NUtilsWrap::constructor;

Napi::Object NUtilsWrap::init(Napi::Env env, Napi::Object exports) {
  Napi::HandleScope scope(env);
  char CLASSNAME[] = "NUtils";
  Napi::Function func =
      DefineClass(env, CLASSNAME,
                  {
                      StaticMethod("isNapiExternal",
                                   &StaticNUtilsWrapMethods::isNapiExternal),
                  });
  constructor = Napi::Persistent(func);
  exports.Set(CLASSNAME, func);
  return exports;
}

NUtilsWrap::NUtilsWrap(const Napi::CallbackInfo& info)
    : Napi::ObjectWrap<NUtilsWrap>(info) {}

Napi::Value StaticNUtilsWrapMethods::isNapiExternal(
    const Napi::CallbackInfo& info) {
  Napi::Env env = info.Env();
  if (info.Length() > 0 && info[0].IsExternal()) {
    return Napi::Boolean::New(env, true);
  }
  return Napi::Boolean::New(env, false);
}
