/*
 * Copyright (c) 2011, Ben Noordhuis <info@bnoordhuis.nl>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <stdlib.h>
#include "v8.h"
#include "node.h"

namespace {

using node::FatalException;
using v8::Array;
using v8::Boolean;
using v8::Exception;
using v8::Function;
using v8::FunctionTemplate;
using v8::FunctionCallbackInfo;
using v8::HandleScope;
using v8::Integer;
using v8::Isolate;
using v8::Local;
using v8::None;
using v8::Object;
using v8::ObjectTemplate;
using v8::Persistent;
using v8::PropertyCallbackInfo;
using v8::Value;
using v8::String;
using v8::TryCatch;

typedef struct proxy_container {
  Persistent<Object> proxy;
  Persistent<Object> target;
  Persistent<Array>  callbacks;
} proxy_container;


Persistent<ObjectTemplate> proxyClass;


bool IsDead(Local<Object> proxy) {
  assert(proxy->InternalFieldCount() == 1);
  proxy_container *cont = reinterpret_cast<proxy_container*>(
      proxy->GetAlignedPointerFromInternalField(0));
  return cont == NULL || cont->target.IsEmpty();
}


Local<Object> Unwrap(Local<Object> proxy) {
  assert(!IsDead(proxy));
  proxy_container *cont = reinterpret_cast<proxy_container*>(
      proxy->GetAlignedPointerFromInternalField(0));
  Isolate* isolate = Isolate::GetCurrent();
  return Local<Object>::New(isolate, cont->target);
}

Local<Array> GetCallbacks(Local<Object> proxy) {
  proxy_container *cont = reinterpret_cast<proxy_container*>(
      proxy->GetAlignedPointerFromInternalField(0));
  assert(cont != NULL);
  Isolate* isolate = Isolate::GetCurrent();
  return Local<Array>::New(isolate, cont->callbacks);
}


#define UNWRAP                            \
  HandleScope scope(info.GetIsolate());   \
  Local<Object> obj;                     \
  const bool dead = IsDead(info.This());  \
  if (!dead) obj = Unwrap(info.This());   \


void ThrowTypeError(Isolate* isolate, const char* message) {
  HandleScope scope(isolate);
  Local<String> emessage = String::NewFromUtf8(isolate, message);
  isolate->ThrowException(v8::Exception::TypeError(emessage));
}


void WeakNamedPropertyGetter(Local<String> property,
                             const PropertyCallbackInfo<Value>& info) {
  UNWRAP
  if (!dead) info.GetReturnValue().Set(obj->Get(property));
}


void WeakNamedPropertySetter(Local<String> property,
                             Local<Value> value,
                             const PropertyCallbackInfo<Value>& info) {
  UNWRAP
  if (!dead) obj->Set(property, value);
}


void WeakNamedPropertyQuery(Local<String> property,
                            const PropertyCallbackInfo<Integer>& info) {
  info.GetReturnValue().Set(None);
}


void WeakNamedPropertyDeleter(Local<String> property,
                              const PropertyCallbackInfo<Boolean>& info) {
  UNWRAP
  info.GetReturnValue().Set(!dead && obj->Delete(property));
}


void WeakIndexedPropertyGetter(uint32_t index,
                               const PropertyCallbackInfo<Value>& info) {
  UNWRAP
  if (!dead) info.GetReturnValue().Set(obj->Get(index));
}


void WeakIndexedPropertySetter(uint32_t index,
                               Local<Value> value,
                               const PropertyCallbackInfo<Value>& info) {
  UNWRAP
  if (!dead) obj->Set(index, value);
}


void WeakIndexedPropertyQuery(uint32_t index,
                              const PropertyCallbackInfo<Integer>& info) {
  info.GetReturnValue().Set(None);
}


void WeakIndexedPropertyDeleter(uint32_t index,
                                const PropertyCallbackInfo<Boolean>& info) {
  UNWRAP
  info.GetReturnValue().Set(!dead && obj->Delete(index));
}


void WeakPropertyEnumerator(const PropertyCallbackInfo<Array>& info) {
  UNWRAP
  info.GetReturnValue().Set(dead ? Array::New(0) : obj->GetPropertyNames());
}


void AddCallback(Isolate* isolate, Local<Object> proxy, Local<Function> callback) {
  Local<Array> callbacks = GetCallbacks(proxy);
  callbacks->Set(Integer::New(isolate, callbacks->Length()), callback);
}


static void TargetCallback(const v8::WeakCallbackData<v8::Object, proxy_container>& data) {
  Isolate* isolate = data.GetIsolate();
  HandleScope scope(isolate);

  Local<Object> target = data.GetValue();
  proxy_container* cont = data.GetParameter();

  // invoke any listening callbacks
  Local<Array> callbacks = Local<Array>::New(isolate, cont->callbacks);
  uint32_t len = callbacks->Length();
  Local<Value> argv[1];
  argv[0] = target;
  for (uint32_t i=0; i<len; i++) {

    Local<Function> cb = Local<Function>::Cast(
        callbacks->Get(Integer::New(isolate, i)));

    TryCatch try_catch;

    cb->Call(target, 1, argv);

    if (try_catch.HasCaught()) {
      FatalException(try_catch);
    }
  }

  cont->proxy.Reset();
  cont->target.Reset();
  cont->callbacks.Reset();
  delete cont;
}


void Create(const FunctionCallbackInfo<Value>& args) {
  HandleScope scope(args.GetIsolate());

  if (!args[0]->IsObject()) {
    ThrowTypeError(args.GetIsolate(), "Object expected");
    return;
  }

  proxy_container *cont = new proxy_container;

  Isolate* isolate = args.GetIsolate();
  Local<ObjectTemplate> tmpl = Local<ObjectTemplate>::New(isolate, proxyClass);
  Local<Object> proxy = tmpl->NewInstance();
  proxy->SetAlignedPointerInInternalField(0, cont);

  cont->proxy.Reset(Isolate::GetCurrent(), proxy);
  cont->target.Reset(isolate, args[0].As<Object>());
  cont->callbacks.Reset(isolate, Array::New(args.GetIsolate()));
  cont->target.SetWeak(cont, TargetCallback);

  if (args.Length() >= 2) {
    AddCallback(args.GetIsolate(), proxy, Local<Function>::Cast(args[1]));
  }

  args.GetReturnValue().Set(proxy);
}

/**
 * TODO: Make this better.
 */

bool isWeakRef (Local<Value> val) {
  return val->IsObject() && val->ToObject()->InternalFieldCount() == 1;
}

void IsWeakRef (const FunctionCallbackInfo<Value>& args) {
  args.GetReturnValue().Set(isWeakRef(args[0]));
}

void Get(const FunctionCallbackInfo<Value>& args) {
  HandleScope scope(args.GetIsolate());

  if (!isWeakRef(args[0])) {
    ThrowTypeError(args.GetIsolate(), "Weakref instance expected");
    return;
  }

  Local<Object> proxy = args[0]->ToObject();
  if (IsDead(proxy)) return;

  Local<Object> obj = Unwrap(proxy);
  args.GetReturnValue().Set(obj);
}

void IsNearDeath(const FunctionCallbackInfo<Value>& args) {
  HandleScope scope(args.GetIsolate());

  if (!isWeakRef(args[0])) {
    ThrowTypeError(args.GetIsolate(), "Weakref instance expected");
    return;
  }

  Local<Object> proxy = args[0]->ToObject();
  proxy_container *cont = static_cast<proxy_container*>(
      proxy->GetAlignedPointerFromInternalField(0));
  assert(cont != NULL);

  args.GetReturnValue().Set(cont->target.IsNearDeath());
}

void IsDead(const FunctionCallbackInfo<Value>& args) {
  HandleScope scope(args.GetIsolate());

  if (!isWeakRef(args[0])) {
    ThrowTypeError(args.GetIsolate(), "Weakref instance expected");
    return;
  }

  Local<Object> proxy = args[0]->ToObject();
  args.GetReturnValue().Set(IsDead(proxy));
}


void AddCallback(const FunctionCallbackInfo<Value>& args) {
  HandleScope scope(args.GetIsolate());

  if (!isWeakRef(args[0])) {
    ThrowTypeError(args.GetIsolate(), "Weakref instance expected");
    return;
  }

  Local<Object> proxy = args[0]->ToObject();
  AddCallback(args.GetIsolate(), proxy, Local<Function>::Cast(args[1]));
}

void Callbacks(const FunctionCallbackInfo<Value>& args) {
  HandleScope scope(args.GetIsolate());

  if (!isWeakRef(args[0])) {
    ThrowTypeError(args.GetIsolate(), "Weakref instance expected");
    return;
  }

  Local<Object> proxy = args[0]->ToObject();
  args.GetReturnValue().Set(GetCallbacks(proxy));
}


void Initialize(Local<Object> target) {
  HandleScope scope(target->CreationContext()->GetIsolate());

  Local<ObjectTemplate> tmpl = ObjectTemplate::New();
  tmpl->SetNamedPropertyHandler(WeakNamedPropertyGetter,
                                WeakNamedPropertySetter,
                                WeakNamedPropertyQuery,
                                WeakNamedPropertyDeleter,
                                WeakPropertyEnumerator);
  tmpl->SetIndexedPropertyHandler(WeakIndexedPropertyGetter,
                                  WeakIndexedPropertySetter,
                                  WeakIndexedPropertyQuery,
                                  WeakIndexedPropertyDeleter,
                                  WeakPropertyEnumerator);
  tmpl->SetInternalFieldCount(1);
  proxyClass.Reset(Isolate::GetCurrent(), tmpl);

  NODE_SET_METHOD(target, "get", Get);
  NODE_SET_METHOD(target, "create", Create);
  NODE_SET_METHOD(target, "isWeakRef", IsWeakRef);
  NODE_SET_METHOD(target, "isNearDeath", IsNearDeath);
  NODE_SET_METHOD(target, "isDead", IsDead);
  NODE_SET_METHOD(target, "callbacks", Callbacks);
  NODE_SET_METHOD(target, "addCallback", AddCallback);

}

} // anonymous namespace

NODE_MODULE(weakref, Initialize);
