#include "Utils.h"

#include <Misc.h>
#include <unordered_map>

namespace sharedjsi
{
Function ditto_tick(Runtime &runtime) {
  return  Function::createFromHostFunction(
                                           runtime,
                                           PropNameID::forAscii(runtime, STRINGIFIED_FUNC_NAME()),
                                           0,
                                           [](Runtime &runtime, const Value &thisValue, const Value *arguments, size_t count) -> Value
                                           {
                                             ditto_tick(&runtime);
                                             return Value();
                                           });
}

Function ditto_set_device_name(Runtime &runtime) {
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    auto *ditto = jsPointerObjectToCPointer<CDitto_t>(runtime, arguments[0]);
    std::string device_name_str = jsTypedArrayToCString(runtime, arguments[1]);
    const char *device_name = device_name_str.c_str();

    const char *res = ::ditto_set_device_name(ditto, device_name);
    return cPointerToJSPointerObject<char>(runtime, res);
  });
}

Function ditto_error_message(Runtime &runtime) {
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    char *str = ::ditto_error_message();
    return cPointerToJSPointerObject<char>(runtime, str);
  });
}

String getErrorCodeString(jsi::Runtime &runtime, dittoffi_error_code_t error_code) {
  const std::string name = [&]() -> std::string {
    switch (error_code) {
      case DITTOFFI_ERROR_CODE_STORE_DOCUMENT_NOT_FOUND: return "StoreDocumentNotFound";
      case DITTOFFI_ERROR_CODE_STORE_DATABASE: return "StoreDatabase";
      case DITTOFFI_ERROR_CODE_STORE_QUERY: return "StoreQuery";
      case DITTOFFI_ERROR_CODE_CRDT: return "Crdt";
      case DITTOFFI_ERROR_CODE_UNKNOWN: return "Unknown";
      case DITTOFFI_ERROR_CODE_BASE64_INVALID: return "Base64Invalid";
      case DITTOFFI_ERROR_CODE_CBOR_UNSUPPORTED: return "CborUnsupported";
      case DITTOFFI_ERROR_CODE_JS_FLOATING_STORE_OPERATION: return "JsFloatingStoreOperation";
      case DITTOFFI_ERROR_CODE_DQL_QUERY_COMPILATION: return "DqlQueryCompilation";
      case DITTOFFI_ERROR_CODE_DQL_INVALID_QUERY_ARGS: return "DqlInvalidQueryArgs";
      case DITTOFFI_ERROR_CODE_DQL_UNSUPPORTED: return "DqlUnsupported";
      case DITTOFFI_ERROR_CODE_DQL_EVALUATION_ERROR: return "DqlEvaluationError";
      case DITTOFFI_ERROR_CODE_PARAMETER_QUERY: return "ParameterQuery";
      case DITTOFFI_ERROR_CODE_VALIDATION_DEPTH_LIMIT_EXCEEDED: return "ValidationDepthLimitExceeded";
      case DITTOFFI_ERROR_CODE_VALIDATION_NOT_A_MAP: return "ValidationNotAMap";
      case DITTOFFI_ERROR_CODE_VALIDATION_SIZE_LIMIT_EXCEEDED: return "ValidationSizeLimitExceeded";
      case DITTOFFI_ERROR_CODE_CBOR_INVALID:
      case DITTOFFI_ERROR_CODE_VALIDATION_INVALID_CBOR: return "ValidationInvalidCbor";
      case DITTOFFI_ERROR_CODE_VALIDATION_INVALID_JSON: return "ValidationInvalidJson";
      case DITTOFFI_ERROR_CODE_ACTIVATION_NOT_ACTIVATED: return "ActivationNotActivated";
      case DITTOFFI_ERROR_CODE_VALIDATION_INVALID_TRANSPORT_CONFIG: return "ValidationInvalidTransportConfig";
      case DITTOFFI_ERROR_CODE_AUTHENTICATION_EXPIRATION_HANDLER_MISSING: return "AuthenticationExpirationHandlerMissing";
      case DITTOFFI_ERROR_CODE_DIFFER_IDENTITY_KEY_PATH_INVALID: return "DifferIdentityKeyPathInvalid";
      case DITTOFFI_ERROR_CODE_VALIDATION_INVALID_DITTO_CONFIG: return "ValidationInvalidDittoConfig";
      case DITTOFFI_ERROR_CODE_ACTIVATION_LICENSE_TOKEN_INVALID: return "ActivationLicenseTokenInvalid";
      case DITTOFFI_ERROR_CODE_ACTIVATION_LICENSE_TOKEN_EXPIRED: return "ActivationLicenseTokenExpired";
      case DITTOFFI_ERROR_CODE_ACTIVATION_LICENSE_UNSUPPORTED_FUTURE_VERSION: return "ActivationLicenseUnsupportedFutureVersion";
      case DITTOFFI_ERROR_CODE_ACTIVATION_UNNECESSARY: return "ActivationUnnecessary";
      case DITTOFFI_ERROR_CODE_IO_NOT_FOUND: return "IoNotFound";
      case DITTOFFI_ERROR_CODE_IO_PERMISSION_DENIED: return "IoPermissionDenied";
      case DITTOFFI_ERROR_CODE_IO_ALREADY_EXISTS: return "IoAlreadyExists";
      case DITTOFFI_ERROR_CODE_IO_OPERATION_FAILED: return "IoOperationFailed";
      case DITTOFFI_ERROR_CODE_UNSUPPORTED: return "Unsupported";
      case DITTOFFI_ERROR_CODE_TRANSPORT: return "Transport";
      case DITTOFFI_ERROR_CODE_LOCKED_DITTO_WORKING_DIRECTORY: return "LockedDittoWorkingDirectory";
      case DITTOFFI_ERROR_CODE_ENCRYPTION_EXTRANEOUS_PASSPHRASE_GIVEN: return "EncryptionExtraneousPassphraseGiven";
      case DITTOFFI_ERROR_CODE_ENCRYPTION_PASSPHRASE_INVALID: return "EncryptionPassphraseInvalid";
      case DITTOFFI_ERROR_CODE_ENCRYPTION_PASSPHRASE_NOT_GIVEN: return "EncryptionPassphraseNotGiven";
      case DITTOFFI_ERROR_CODE_STORE_DOCUMENT_ID: return "StoreDocumentId";
      case DITTOFFI_ERROR_CODE_STORE_TRANSACTION_READ_ONLY: return "TransactionReadOnly";
      case DITTOFFI_ERROR_CODE_INTERNAL: return "Internal";
    }

    throw std::invalid_argument("Unknown error code.");
  }();

  return String::createFromUtf8(runtime, name);
}

Function dittoffi_error_code(Runtime &runtime) {
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    auto *error = jsPointerObjectToCPointer<dittoffi_error_t>(runtime, arguments[0]);
    dittoffi_error_code_t error_code = ::dittoffi_error_code(error);
    return getErrorCodeString(runtime, error_code);
  });
}

Function dittoffi_error_description(Runtime &runtime) {
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    char *res = ::dittoffi_error_description(jsPointerObjectToCPointer<dittoffi_error_t>(runtime, arguments[0]));
    return cPointerToJSPointerObject<char>(runtime, res);
  });
}

Function dittoffi_error_free(Runtime &runtime) {
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    ::dittoffi_error_free(jsPointerObjectToCPointer<dittoffi_error_t>(runtime, arguments[0]));
    return {};
  });
}

Function dittoffi_get_sdk_semver(Runtime &runtime) {
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    return cPointerToJSPointerObject<char>(runtime, ::dittoffi_get_sdk_semver());
  });
}

Function dittoffi_try_verify_license(Runtime &runtime) {
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    CDitto_t *ditto = jsPointerObjectToCPointer<CDitto_t>(runtime, arguments[0]);
    std::string license = jsTypedArrayToCString(runtime, arguments[1]);
    dittoffi_result_void_t res = ::dittoffi_try_verify_license(ditto, license.c_str());

    Object obj(runtime);
    if (res.error != nullptr) {
      obj.setProperty(runtime, "error", cPointerToJSPointerObject(runtime, res.error));
    } else {
      obj.setProperty(runtime, "error", Value::null());
    }
    return obj;
  });
}

Function dittoffi_base64_encode(Runtime &runtime)
{
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    std::vector<uint8_t> bytes_vec = jsTypedArrayToCVector(runtime, arguments[0]);
    slice_ref_uint8_t bytes = borrowVecAsRefSlice(bytes_vec);

    std::string padding_mode_str = arguments[1].toString(runtime).utf8(runtime);
    Base64PaddingMode_t padding_mode = padding_mode_str == "Padded" ? BASE64_PADDING_MODE_PADDED : BASE64_PADDING_MODE_UNPADDED;

    char *res = ::dittoffi_base64_encode(bytes, padding_mode);
    return cPointerToJSPointerObject<char>(runtime, res);
  });
}

Function dittoffi_try_base64_decode(Runtime &runtime)
{
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    std::string base64_str = jsTypedArrayToCString(runtime, arguments[0]);
    const char *base64 = base64_str.c_str();

    std::string padding_mode_str = arguments[1].toString(runtime).utf8(runtime);
    Base64PaddingMode_t padding_mode = padding_mode_str == "Padded" ? BASE64_PADDING_MODE_PADDED : BASE64_PADDING_MODE_UNPADDED;

    dittoffi_result_slice_boxed_uint8_t res = ::dittoffi_try_base64_decode(base64, padding_mode);

    Object obj(runtime);
    obj.setProperty(runtime, "success", cSlicedBoxToJSSlicedBox(runtime, res.success));
    if (res.error != nullptr) {
      obj.setProperty(runtime, "error", cPointerToJSPointerObject(runtime, res.error));
    } else {
      obj.setProperty(runtime, "error", Value::null());
    }
    return obj;
  });
}

Function dittoffi_crypto_generate_secure_random_token(Runtime &runtime)
{
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    char *res = ::dittoffi_crypto_generate_secure_random_token();
    return cPointerToJSPointerObject<char>(runtime, res);
  });
}
}
