#include <cstring>
#include <iostream>
#include "accessibility.hh"
#include "SwiftAccessibility.hh"
#include "AXAsyncWorker.hh"
#include "AXErrorAsyncWorker.hh"
#include "AXAppAsyncWorker.hh"

using namespace Napi;

/*
Function launchAppCB;
const CallbackInfo* launchAppCBCallbackInfo;
*/

// CallbackInfo& launchAppCBCallbackInfo = nullptr;
//std::unique_ptr<Env> launchAppCBEnv;

AXAsyncWorker* launchAppWorker;
AXAsyncWorker* createNewWindowWorker;

/*
void accessibility::FocusApp(const char * bundleID) {
  swift_FocusApp(bundleID);
}
*/

Boolean accessibility::IsProcessTrustedWrapper(const CallbackInfo& info) {
  Env env = info.Env();
  Boolean returnVal = Boolean::New(env, swift_IsProcessTrusted());
  return returnVal;
}

/*
Value accessibility::FocusAppWrapper(const CallbackInfo& info) {
  Env env = info.Env();
  if (info.Length() < 1) {
    TypeError::New(env, "Missing argument. Bundle ID is required")
      .ThrowAsJavaScriptException();
    return env.Null();
  }

  if (!info[0].IsString()) {
    TypeError::New(env, "Argument must be string")
      .ThrowAsJavaScriptException();
    return env.Null();
  }

  std::string bundleID = info[0].As<String>().Utf8Value();
  char* c_bundleID = new char [bundleID.length()+1];
  strcpy(c_bundleID, bundleID.c_str());

  accessibility::FocusApp(c_bundleID);
  return env.Null();
}
*/

void LaunchAppCallback(const char* error) {
  launchAppWorker->error = std::string(error);
  launchAppWorker->canFinish = true;
}

void CreateNewWindowCallback(const char* error) {
  std::cout << "Create new window callback" << std::endl;
  createNewWindowWorker->error = std::string(error);
  createNewWindowWorker->canFinish = true;
}

void accessibility::LaunchAppWrapper(const CallbackInfo& info) {
  Env env = info.Env();
  if (info.Length() < 1) {
    TypeError::New(env, "Missing argument. Bundle ID is required").ThrowAsJavaScriptException();
    return;
  }

  if (!info[0].IsString()) {
    TypeError::New(env, "Invalid argument type. Expected string").ThrowAsJavaScriptException();
    return;
  }

  if (info[1] && !info[1].IsFunction()) {
    TypeError::New(env, "Invalid argument type. Expected function").ThrowAsJavaScriptException();
    return;
  }

  Function cb = info[1].As<Function>();
  launchAppWorker = new AXAsyncWorker(cb);
  launchAppWorker->Queue();

  std::string bundleID = info[0].As<String>().Utf8Value();
  char* c_bundleID = new char [bundleID.length()+1];
  strcpy(c_bundleID, bundleID.c_str());
  swift_LaunchApp(c_bundleID, LaunchAppCallback);


  return;
}

void accessibility::CreateNewWindowWrapper(const CallbackInfo& info) {
  Env env = info.Env();
  if (info.Length() < 1) {
    TypeError::New(env, "Missing argument. Bundle ID is required").ThrowAsJavaScriptException();
    return;
  }

  if (!info[0].IsString()) {
    TypeError::New(env, "Invalid argument type. Expected string").ThrowAsJavaScriptException();
    return;
  }

  if (info[1] && !info[1].IsFunction()) {
    TypeError::New(env, "Invalid argument type. Expected function").ThrowAsJavaScriptException();
    return;
  }

  Function cb = info[1].As<Function>();
  createNewWindowWorker = new AXAsyncWorker(cb);
  createNewWindowWorker->Queue();

  std::string bundleID = info[0].As<String>().Utf8Value();
  char* c_bundleID = new char [bundleID.length()+1];
  strcpy(c_bundleID, bundleID.c_str());
  swift_CreateNewWindow(c_bundleID, CreateNewWindowCallback);

  return;
}

void accessibility::RunWrapper(const CallbackInfo& info) {
  Env env = info.Env();
  if (info.Length() < 1) {
    TypeError::New(env, "Missing argument. Bundle ID is required").ThrowAsJavaScriptException();
    return;
  }

  if (!info[0].IsString()) {
    TypeError::New(env, "Invalid argument type. Expected string").ThrowAsJavaScriptException();
    return;
  }

  if (info[1] && !info[1].IsFunction()) {
    TypeError::New(env, "Invalid argument type. Expected function").ThrowAsJavaScriptException();
    return;
  }

  std::string bundleID = info[0].As<String>().Utf8Value();
  char* c_bundleID = new char [bundleID.length()+1];
  strcpy(c_bundleID, bundleID.c_str());

  /*
  Function cb = info[1].As<Function>();
  AXAppAsyncWorker* axAppWorker = new AXAppAsyncWorker(cb, bundleID);
  axAppWorker->Queue();
  */

  swift_Run(c_bundleID);

  std::cout << "AFTER SWIFT RUN" << std::endl;
  return;
}

Value accessibility::IsAppRunningWrapper(const CallbackInfo& info) {
  Env env = info.Env();
  if (info.Length() < 1) {
    TypeError::New(env, "Missing argument. Bundle ID is required").ThrowAsJavaScriptException();
    return env.Undefined();
  }

  if (!info[0].IsString()) {
    TypeError::New(env, "Invalid argument type. Expected string").ThrowAsJavaScriptException();
    return env.Undefined();
  }

  std::string bundleID = info[0].As<String>().Utf8Value();
  char* c_bundleID = new char [bundleID.length()+1];
  strcpy(c_bundleID, bundleID.c_str());

  bool isRunning = swift_IsAppRunning(c_bundleID);
  return Boolean::New(env, isRunning);
}

Object accessibility::Init(Env env, Object exports) {
  exports.Set("isProcessTrusted", Function::New(env, accessibility::IsProcessTrustedWrapper));
  // exports.Set("focusApp", Function::New(env, accessibility::FocusAppWrapper));
  exports.Set("launchApp", Function::New(env, accessibility::LaunchAppWrapper));
  exports.Set("createNewWindow", Function::New(env, accessibility::CreateNewWindowWrapper));
  exports.Set("run", Function::New(env, accessibility::RunWrapper));
  exports.Set("isAppRunning", Function::New(env, accessibility::IsAppRunningWrapper));
  return exports;
}

