// This is a generated file, modify: generate/templates/struct_content.cc.
#include <nan.h>
#include <string.h>
#include <chrono>
#include <thread>

extern "C" {
  #include <git2.h>
 }

#include <iostream>
#include "../include/functions/copy.h"
#include "../include/remote_callbacks.h"

  #include "../include/cred.h"
 
using namespace v8;
using namespace node;
using namespace std;


// generated from struct_content.cc
GitRemoteCallbacks::GitRemoteCallbacks() {
  git_remote_callbacks wrappedValue = GIT_REMOTE_CALLBACKS_INIT;
  this->raw = (git_remote_callbacks*) malloc(sizeof(git_remote_callbacks));
  memcpy(this->raw, &wrappedValue, sizeof(git_remote_callbacks));

  this->ConstructFields();
  this->selfFreeing = true;
}

GitRemoteCallbacks::GitRemoteCallbacks(git_remote_callbacks* raw, bool selfFreeing) {
  this->raw = raw;
  this->ConstructFields();
  this->selfFreeing = selfFreeing;
}

GitRemoteCallbacks::~GitRemoteCallbacks() {
  // This is going to cause memory leaks. We'll have to solve that later
  // TODO: Clean up memory better
  if (this->selfFreeing) {
    free(this->raw);
  }
}

void GitRemoteCallbacks::ConstructFields() {
   
          // Set the static method call and set the payload for this function to be
          // the current instance
          this->raw->credentials = (git_cred_acquire_cb)credentials_cppCallback;
          this->raw->payload = (void *)this;
          this->credentials = new NanCallback();
   
          Local<Value> payload = NanUndefined();
          NanAssignPersistent(this->payload, payload);
    }

void GitRemoteCallbacks::InitializeComponent(Handle<v8::Object> target) {
  NanScope();

  Local<FunctionTemplate> tpl = NanNew<FunctionTemplate>(JSNewFunction);

  tpl->InstanceTemplate()->SetInternalFieldCount(1);
  tpl->SetClassName(NanNew<String>("RemoteCallbacks"));

      tpl->InstanceTemplate()->SetAccessor(NanNew<String>("version"), GetVersion, SetVersion);
       tpl->InstanceTemplate()->SetAccessor(NanNew<String>("credentials"), GetCredentials, SetCredentials);
       tpl->InstanceTemplate()->SetAccessor(NanNew<String>("payload"), GetPayload, SetPayload);
  
  Local<Function> _constructor_template = tpl->GetFunction();
  NanAssignPersistent(constructor_template, _constructor_template);
  target->Set(NanNew<String>("RemoteCallbacks"), _constructor_template);
}

NAN_METHOD(GitRemoteCallbacks::JSNewFunction) {
  NanScope();
  GitRemoteCallbacks* instance;

  if (args.Length() == 0 || !args[0]->IsExternal()) {
    instance = new GitRemoteCallbacks();
  }
  else {
    instance = new GitRemoteCallbacks(static_cast<git_remote_callbacks*>(Handle<External>::Cast(args[0])->Value()), args[1]->BooleanValue());
  }

  instance->Wrap(args.This());

  NanReturnValue(args.This());
}

Handle<Value> GitRemoteCallbacks::New(void* raw, bool selfFreeing) {
  NanEscapableScope();

  Handle<Value> argv[2] = { NanNew<External>((void *)raw), NanNew<Boolean>(selfFreeing) };
  return NanEscapeScope(NanNew<Function>(GitRemoteCallbacks::constructor_template)->NewInstance(2, argv));
}

git_remote_callbacks *GitRemoteCallbacks::GetValue() {
  return this->raw;
}

git_remote_callbacks **GitRemoteCallbacks::GetRefValue() {
  return &this->raw;
}

    NAN_GETTER(GitRemoteCallbacks::GetVersion) {
      NanScope();

      GitRemoteCallbacks *wrapper = ObjectWrap::Unwrap<GitRemoteCallbacks>(args.This());

        NanReturnValue(NanNew<Number>(wrapper->GetValue()->version));
     }

    NAN_SETTER(GitRemoteCallbacks::SetVersion) {
      NanScope();

      GitRemoteCallbacks *wrapper = ObjectWrap::Unwrap<GitRemoteCallbacks>(args.This());

         if (value->IsNumber()) {
          wrapper->GetValue()->version = (unsigned int) value->Int32Value();
        }
     }

      NAN_GETTER(GitRemoteCallbacks::GetCredentials) {
      NanScope();

      GitRemoteCallbacks *wrapper = ObjectWrap::Unwrap<GitRemoteCallbacks>(args.This());

        NanReturnValue(wrapper->credentials->GetFunction());

     }

    NAN_SETTER(GitRemoteCallbacks::SetCredentials) {
      NanScope();

      GitRemoteCallbacks *wrapper = ObjectWrap::Unwrap<GitRemoteCallbacks>(args.This());

        if (value->IsFunction()) {
          wrapper->credentials = new NanCallback(value.As<Function>());
        }

     }

      int GitRemoteCallbacks::credentials_cppCallback (
          git_cred ** cred,           const char * url,           const char * username_from_url,           unsigned int allowed_types,           void * payload        ) {
        CredentialsBaton* baton = new CredentialsBaton();

          baton->cred = cred;
          baton->url = url;
          baton->username_from_url = username_from_url;
          baton->allowed_types = allowed_types;
          baton->payload = payload;
 
        baton->req.data = baton;
        baton->done = false;

        uv_queue_work(uv_default_loop(), &baton->req, credentials_asyncWork, credentials_asyncAfter);

        while(!baton->done) {
          this_thread::sleep_for(chrono::milliseconds(1));
        }

          *cred = *baton->cred;
 
        return baton->result;
      }

      void GitRemoteCallbacks::credentials_asyncWork(uv_work_t* req) {
        // We aren't doing any work on a seperate thread, just need to
        // access the main node thread in the async after method.
        // However, this worker method is still needed
      }

      void GitRemoteCallbacks::credentials_asyncAfter(uv_work_t* req, int status) {
        NanScope();

        CredentialsBaton* baton = static_cast<CredentialsBaton*>(req->data);
        GitRemoteCallbacks* instance = static_cast<GitRemoteCallbacks*>(baton->payload);

        if (instance->credentials->IsEmpty()) {
            baton->result = 1; // no results acquired
 
          baton->done = true;
          return;
        }

        Local<Value> argv[4] = {
                  NanNew(baton->url),
                   NanNew(baton->username_from_url),
                   NanNew(baton->allowed_types),
                NanNew(instance->payload)
          };

        TryCatch tryCatch;
        Handle<Value> result = instance->credentials->Call(4, argv);

        if (result->IsObject() && result->ToObject()->Has(NanNew("then"))) {
          Handle<Value> thenProp = result->ToObject()->Get(NanNew("then"));

          if (thenProp->IsFunction()) {
            // we can be reasonbly certain that the result is a promise
            Local<Object> promise = result->ToObject();

            NanAssignPersistent(baton->promise, promise);

            uv_queue_work(uv_default_loop(), &baton->req, credentials_asyncWork, credentials_asyncPromisePolling);
            return;
          }
        }

        int resultStatus;

          if (result.IsEmpty() || result->IsNativeError()) {
            baton->result = -1;
          }
          else if (!result->IsNull() && !result->IsUndefined()) {
            GitCred* wrapper = ObjectWrap::Unwrap<GitCred>(result->ToObject());
            wrapper->selfFreeing = false;

            baton->cred = wrapper->GetRefValue();
            baton->result = 0;
          }
          else {
            baton->result = 1;
          }
         baton->done = true;
      }

      void GitRemoteCallbacks::credentials_asyncPromisePolling(uv_work_t* req, int status) {
        NanScope();

        CredentialsBaton* baton = static_cast<CredentialsBaton*>(req->data);
        Local<Object> promise = NanNew<Object>(baton->promise);
        NanCallback* isPendingFn = new NanCallback(promise->Get(NanNew("isPending")).As<Function>());
        Local<Value> argv[1]; // MSBUILD won't assign an array of length 0
        Local<Boolean> isPending = isPendingFn->Call(0, argv)->ToBoolean();

        if (isPending->Value()) {
          uv_queue_work(uv_default_loop(), &baton->req, credentials_asyncWork, credentials_asyncPromisePolling);
          return;
        }

        NanCallback* isFulfilledFn = new NanCallback(promise->Get(NanNew("isFulfilled")).As<Function>());
        Local<Boolean> isFulfilled = isFulfilledFn->Call(0, argv)->ToBoolean();

        if (isFulfilled->Value()) {
          NanCallback* resultFn = new NanCallback(promise->Get(NanNew("value")).As<Function>());
          Handle<Value> result = resultFn->Call(0, argv);
          int resultStatus;

            if (result.IsEmpty() || result->IsNativeError()) {
              baton->result = -1;
            }
            else if (!result->IsNull() && !result->IsUndefined()) {
              GitCred* wrapper = ObjectWrap::Unwrap<GitCred>(result->ToObject());
              wrapper->selfFreeing = false;

              baton->cred = wrapper->GetRefValue();
              baton->result = 0;
            }
            else {
              baton->result = 1;
            }
           baton->done = true;
        }
        else {
          // promise was rejected
          baton->result = -1;
          baton->done = false;
        }
      }
      NAN_GETTER(GitRemoteCallbacks::GetPayload) {
      NanScope();

      GitRemoteCallbacks *wrapper = ObjectWrap::Unwrap<GitRemoteCallbacks>(args.This());

        NanReturnValue(wrapper->payload);

     }

    NAN_SETTER(GitRemoteCallbacks::SetPayload) {
      NanScope();

      GitRemoteCallbacks *wrapper = ObjectWrap::Unwrap<GitRemoteCallbacks>(args.This());

        NanAssignPersistent(wrapper->payload, value);

     }

   
Persistent<Function> GitRemoteCallbacks::constructor_template;
