//-------------------------------------------------------------------------------------------------------
// Project: NodeActiveX
// Author: Yuri Dursin
// Description: DispObject class implementations
//-------------------------------------------------------------------------------------------------------

#include "stdafx.h"
#include "disp.h"
#include "utils.h"

Persistent<ObjectTemplate> DispObject::inst_template;
Persistent<FunctionTemplate> DispObject::clazz_template;
NodeMethods DispObject::clazz_methods;

Persistent<ObjectTemplate> VariantObject::inst_template;
Persistent<FunctionTemplate> VariantObject::clazz_template;
NodeMethods VariantObject::clazz_methods;

Persistent<ObjectTemplate> ConnectionPointObject::inst_template;
Persistent<FunctionTemplate> ConnectionPointObject::clazz_template;

//-------------------------------------------------------------------------------------------------------
// DispObject implemetation
DispObject::DispObject(const DispInfoPtr &ptr, const std::wstring &nm, DISPID id, LONG indx, int opt)
    : disp(ptr), options((ptr->options & option_mask) | opt), name(nm), dispid(id), index(indx) {
  if (dispid == DISPID_UNKNOWN) {
    dispid = DISPID_VALUE;
    options |= option_prepared;
  } else
    options |= option_owned;
  NODE_DEBUG_FMT("DispObject '%S' constructor", name.c_str());
}

DispObject::~DispObject() {
  items.Reset();
  methods.Reset();
  vars.Reset();
  NODE_DEBUG_FMT("DispObject '%S' destructor", name.c_str());
}

HRESULT DispObject::prepare() {
  CComVariant value;
  HRESULT hrcode = disp ? disp->GetProperty(dispid, index, &value) : E_UNEXPECTED;

  // Init dispatch interface
  options |= option_prepared;
  CComPtr<IDispatch> ptr;
  if (VariantDispGet(&value, &ptr)) {
    disp.reset(new DispInfo(ptr, name, options, &disp));
    dispid = DISPID_VALUE;
  } else if ((value.vt & VT_ARRAY) != 0) {
  }
  return hrcode;
}

bool DispObject::release() {
  if (!disp)
    return false;
  NODE_DEBUG_FMT("DispObject '%S' release", name.c_str());
  disp.reset();
  items.Reset();
  methods.Reset();
  vars.Reset();
  return true;
}

bool DispObject::get(LPOLESTR tag, LONG index, const PropertyCallbackInfoGetter &args) {
  Isolate *isolate = args.GetIsolate();
  if (!is_prepared())
    prepare();
  if (!disp) {
    isolate->ThrowException(DispErrorNull(isolate));
    return false;
  }

  // Search dispid
  HRESULT hrcode;
  DISPID propid;
  bool prop_by_key = false;
  bool this_prop = false;
  if (!tag || !*tag) {
    tag = (LPOLESTR)name.c_str();
    propid = dispid;
    this_prop = true;
  } else {
    hrcode = disp->FindProperty(tag, &propid);
    if (SUCCEEDED(hrcode) && propid == DISPID_UNKNOWN)
      hrcode = E_INVALIDARG;
    if FAILED (hrcode) {
      prop_by_key = (options & option_property) != 0;
      if (!prop_by_key) {
        // isolate->ThrowException(DispError(isolate, hrcode, L"DispPropertyFind", tag));
        args.GetReturnValue().SetUndefined();
        return false;
      }
      propid = dispid;
    }
  }

  // Check type info
  int opt = 0;
  bool is_property_simple = false;
  if (prop_by_key) {
    is_property_simple = true;
    opt |= option_property;
  } else {
    DispInfo::type_ptr disp_info;
    if (disp->GetTypeInfo(propid, disp_info)) {
      if (disp_info->is_function_simple())
        opt |= option_function_simple;
      else {
        if (disp_info->is_property())
          opt |= option_property;
        is_property_simple = disp_info->is_property_simple();

        if (disp->bManaged && tag && *tag && wcscmp(tag, L"ToString") == 0) {
          // .NET ToString is reported as a property while we normally use it via .ToString() - i.e. as a method.
          is_property_simple = false;
        }
      }
    } else if (disp->bManaged && tag && *tag && wcscmp(tag, L"length") == 0) {
      DISPID lenprop;
      if SUCCEEDED (disp->FindProperty((LPOLESTR)L"length", &lenprop)) {

        // If we have 'IReflect' and '.length' - assume it is .NET JS Array or JS Object
        is_property_simple = true;
      }
    } else if (disp->bManaged && tag && *tag && index >= 0) {
      // jsarray[x]
      is_property_simple = true;
    }
  }

  // Return as property value
  if (is_property_simple) {
    CComException except;
    CComVariant value;
    VarArguments vargs;
    if (prop_by_key)
      vargs.items.push_back(CComVariant(tag));
    if (index >= 0)
      vargs.items.push_back(CComVariant(index));
    LONG argcnt = (LONG)vargs.items.size();
    VARIANT *pargs = (argcnt > 0) ? &vargs.items.front() : 0;
    hrcode = disp->GetProperty(propid, argcnt, pargs, &value, &except);
    if (FAILED(hrcode) && dispid != DISPID_VALUE) {
      isolate->ThrowException(DispError(isolate, hrcode, L"DispPropertyGet", tag, &except));
      return false;
    }
    CComPtr<IDispatch> ptr;
    if (VariantDispGet(&value, &ptr)) {
      DispInfoPtr disp_result(new DispInfo(ptr, tag, options, &disp));
      Local<Object> result = DispObject::NodeCreate(isolate, args.This(), disp_result, tag, DISPID_UNKNOWN, -1, opt);
      args.GetReturnValue().Set(result);
    } else {
      args.GetReturnValue().Set(Variant2Value(isolate, value));
    }
  }

  // Return as dispatch object
  else {
    Local<Object> result = DispObject::NodeCreate(isolate, args.This(), disp, tag, propid, index, opt);
    args.GetReturnValue().Set(result);
  }
  return true;
}

bool DispObject::set(LPOLESTR tag, LONG index, const Local<Value> &value, const PropertyCallbackInfoSetter &args) {
  Isolate *isolate = args.GetIsolate();
  if (!is_prepared())
    prepare();
  if (!disp) {
    isolate->ThrowException(DispErrorNull(isolate));
    return false;
  }

  // Search dispid
  HRESULT hrcode;
  DISPID propid;
  if (!tag || !*tag) {
    tag = (LPOLESTR)name.c_str();
    propid = dispid;
  } else {
    hrcode = disp->FindProperty(tag, &propid);
    if (SUCCEEDED(hrcode) && propid == DISPID_UNKNOWN)
      hrcode = E_INVALIDARG;
    if FAILED (hrcode) {
      isolate->ThrowException(DispError(isolate, hrcode, L"DispPropertyFind", tag));
      return false;
    }
  }

  // Set value using dispatch
  CComException except;
  CComVariant ret;
  VarArguments vargs(isolate, value);
  if (index >= 0)
    vargs.items.push_back(CComVariant(index));
  LONG argcnt = (LONG)vargs.items.size();
  VARIANT *pargs = (argcnt > 0) ? &vargs.items.front() : 0;
  hrcode = disp->SetProperty(propid, argcnt, pargs, &ret, &except);
  if FAILED (hrcode) {
    isolate->ThrowException(DispError(isolate, hrcode, L"DispPropertyPut", tag, &except));
    return false;
  }

  // Send result
  CComPtr<IDispatch> ptr;
  if (VariantDispGet(&ret, &ptr)) {
    std::wstring rtag;
    rtag.reserve(32);
    rtag += L"@";
    rtag += tag;
    DispInfoPtr disp_result(new DispInfo(ptr, tag, options, &disp));
    Local<Object> result = DispObject::NodeCreate(isolate, args.This(), disp_result, rtag);
    args.GetReturnValue().Set(result);
  } else {
    args.GetReturnValue().Set(Variant2Value(isolate, ret));
  }
  return true;
}

void DispObject::call(Isolate *isolate, const FunctionCallbackInfo<Value> &args) {
  if (!disp) {
    isolate->ThrowException(DispErrorNull(isolate));
    return;
  }

  CComException except;
  CComVariant ret;
  VarArguments vargs(isolate, args);
  LONG argcnt = (LONG)vargs.items.size();
  VARIANT *pargs = (argcnt > 0) ? &vargs.items.front() : 0;
  HRESULT hrcode;

  if (vargs.IsDefault()) {
    hrcode = valueOf(isolate, ret, true);
  } else if ((options & option_property) == 0) {
    hrcode = disp->ExecuteMethod(dispid, argcnt, pargs, &ret, &except);
  } else {
    DispInfo::type_ptr disp_info;
    disp->GetTypeInfo(dispid, disp_info);

    if (disp_info->is_property_advanced() && argcnt > 1) {
      hrcode = disp->SetProperty(dispid, argcnt, pargs, &ret, &except);
    } else {
      hrcode = disp->GetProperty(dispid, argcnt, pargs, &ret, &except);
    }
  }
  if FAILED (hrcode) {
    isolate->ThrowException(DispError(isolate, hrcode, L"DispInvoke", name.c_str(), &except));
    return;
  }

  // Prepare result
  Local<Value> result;
  CComPtr<IDispatch> ptr;
  if (VariantDispGet(&ret, &ptr)) {
    std::wstring tag;
    tag.reserve(32);
    tag += L"@";
    tag += name;
    DispInfoPtr disp_result(new DispInfo(ptr, tag, options, &disp));
    result = DispObject::NodeCreate(isolate, args.This(), disp_result, tag);
  } else {
    result = Variant2Value(isolate, ret, true);
  }
  args.GetReturnValue().Set(result);
}

HRESULT DispObject::valueOf(Isolate *isolate, VARIANT &value, bool simple) {
  if (!is_prepared())
    prepare();
  HRESULT hrcode;
  if (!disp)
    hrcode = E_UNEXPECTED;

  // simple function without arguments
  else if ((options & option_function_simple) != 0) {
    hrcode = disp->ExecuteMethod(dispid, 0, 0, &value);
  }

  // property or array element
  else if (dispid != DISPID_VALUE || index >= 0 || simple) {
    hrcode = disp->GetProperty(dispid, index, &value);
  }

  // self dispatch object
  else /*if (is_object())*/ {
    value.vt = VT_DISPATCH;
    value.pdispVal = (IDispatch *)disp->ptr;
    if (value.pdispVal)
      value.pdispVal->AddRef();
    hrcode = S_OK;
  }
  return hrcode;
}

HRESULT DispObject::valueOf(Isolate *isolate, const Local<Object> &self, Local<Value> &value) {
  if (!is_prepared())
    prepare();
  HRESULT hrcode;
  if (!disp)
    hrcode = E_UNEXPECTED;
  else {
    CComVariant val;

    // simple function without arguments

    if ((options & option_function_simple) != 0) {
      hrcode = disp->ExecuteMethod(dispid, 0, 0, &val);
    }

    // self value, property or array element
    else {
      hrcode = disp->GetProperty(dispid, index, &val);
      // Try to get some primitive value
      if FAILED (hrcode) {
        hrcode = disp->ExecuteMethod(dispid, 0, 0, &val);
      }
    }

    // convert result to v8 value
    if SUCCEEDED (hrcode) {
      value = Variant2Value(isolate, val);
    }

    // or return self as object
    else {
      value = self;
      hrcode = S_OK;
    }
  }
  return hrcode;
}

void DispObject::toString(const FunctionCallbackInfo<Value> &args) {
  Isolate *isolate = args.GetIsolate();
  CComVariant val;
  HRESULT hrcode = valueOf(isolate, val, true);
  if FAILED (hrcode) {
    isolate->ThrowException(Win32Error(isolate, hrcode, L"DispToString"));
    return;
  }
  args.GetReturnValue().Set(Variant2String(isolate, val));
}

Local<Value> DispObject::getIdentity(Isolate *isolate) {
  // wchar_t buf[64];
  std::wstring id;
  id.reserve(128);
  id += name;
  DispInfoPtr ptr = disp;
  if (ptr && ptr->name == id)
    ptr = ptr->parent.lock();
  while (ptr) {
    id.insert(0, L".");
    id.insert(0, ptr->name);
    /*
    if (ptr->index >= 0) {
        swprintf_s(buf, L"[%ld]", index);
        id += buf;
    }
    */
    ptr = ptr->parent.lock();
  }
  return v8str(isolate, id.c_str());
}

void DispObject::initTypeInfo(Isolate *isolate) {
  if ((options & option_type) == 0 || !disp) {
    return;
  }
  uint32_t index = 0;
  Local<v8::Object> _items = v8::Array::New(isolate);
  Local<v8::Object> _methods = v8::Object::New(isolate);
  Local<v8::Object> _vars = v8::Object::New(isolate);
  disp->Enumerate(1 + 2 /*functions and variables*/, nullptr,
                  [isolate, &_items, &_vars, &_methods, &index](ITypeInfo *info, FUNCDESC *func, VARDESC *var) {
                    Local<Context> ctx = isolate->GetCurrentContext();
                    CComBSTR name;
                    MEMBERID memid = func != nullptr ? func->memid : var->memid;
                    TypeInfoGetName(info, memid, &name);
                    Local<Object> item(Object::New(isolate));
                    Local<String> vname;
                    if (name) {
                      vname = v8str(isolate, (BSTR)name);
                      item->Set(ctx, v8str(isolate, "name"), vname);
                    }
                    item->Set(ctx, v8str(isolate, "dispid"), Int32::New(isolate, memid));
                    if (func != nullptr) {
                      item->Set(ctx, v8str(isolate, "invkind"), Int32::New(isolate, (int32_t)func->invkind));
                      item->Set(ctx, v8str(isolate, "flags"), Int32::New(isolate, (int32_t)func->wFuncFlags));
                      item->Set(ctx, v8str(isolate, "argcnt"), Int32::New(isolate, (int32_t)func->cParams));
                      _methods->Set(ctx, vname, item);
                    } else {
                      item->Set(ctx, v8str(isolate, "varkind"), Int32::New(isolate, (int32_t)var->varkind));
                      item->Set(ctx, v8str(isolate, "flags"), Int32::New(isolate, (int32_t)var->wVarFlags));
                      if (var->varkind == VAR_CONST && var->lpvarValue != nullptr) {
                        v8::Local<v8::Value> value = Variant2Value(isolate, *var->lpvarValue, false);
                        item->Set(ctx, v8str(isolate, "value"), value);
                      }
                      _vars->Set(ctx, vname, item);
                    }
                    _items->Set(ctx, index++, item);
                  });
  items.Reset(isolate, _items);
  methods.Reset(isolate, _methods);
  vars.Reset(isolate, _vars);
}

//-----------------------------------------------------------------------------------
// Static Node JS callbacks

void DispObject::NodeInit(const Local<Object> &target, Isolate *isolate, Local<Context> &ctx) {

  // Prepare constructor template
  Local<FunctionTemplate> clazz = FunctionTemplate::New(isolate, NodeCreate);
  clazz->SetClassName(v8str(isolate, "Dispatch"));

  clazz_methods.add(isolate, clazz, "toString", NodeToString);
  clazz_methods.add(isolate, clazz, "valueOf", NodeValueOf);

  Local<ObjectTemplate> inst = clazz->InstanceTemplate();
  inst->SetInternalFieldCount(1);

#ifdef NODE_INTERCEPTED
  inst->SetHandler(NamedPropertyHandlerConfiguration(InterceptedNodeGet, InterceptedNodeSet));
  inst->SetHandler(IndexedPropertyHandlerConfiguration(InterceptedNodeGetByIndex, InterceptedNodeSetByIndex));
#else
  inst->SetHandler(NamedPropertyHandlerConfiguration(NodeGet, NodeSet));
  inst->SetHandler(IndexedPropertyHandlerConfiguration(NodeGetByIndex, NodeSetByIndex));
#endif

  inst->SetCallAsFunctionHandler(NodeCall);
  inst->SetNativeDataProperty(v8str(isolate, "__id"), NodeGet);
  inst->SetNativeDataProperty(v8str(isolate, "__value"), NodeGet);
  // inst->SetLazyDataProperty(v8str(isolate, "__type"), NodeGet, Local<Value>(), ReadOnly);
  // inst->SetLazyDataProperty(v8str(isolate, "__methods"), NodeGet, Local<Value>(), ReadOnly);
  // inst->SetLazyDataProperty(v8str(isolate, "__vars"), NodeGet, Local<Value>(), ReadOnly);
  inst->SetNativeDataProperty(v8str(isolate, "__type"), NodeGet);
  inst->SetNativeDataProperty(v8str(isolate, "__methods"), NodeGet);
  inst->SetNativeDataProperty(v8str(isolate, "__vars"), NodeGet);

  inst_template.Reset(isolate, inst);
  clazz_template.Reset(isolate, clazz);
  target->Set(ctx, v8str(isolate, "Object"), clazz->GetFunction(ctx).ToLocalChecked());
  target->Set(ctx, v8str(isolate, "cast"), FunctionTemplate::New(isolate, NodeCast)->GetFunction(ctx).ToLocalChecked());
  target->Set(ctx, v8str(isolate, "release"),
              FunctionTemplate::New(isolate, NodeRelease)->GetFunction(ctx).ToLocalChecked());

  target->Set(ctx, v8str(isolate, "getConnectionPoints"),
              FunctionTemplate::New(isolate, NodeConnectionPoints)->GetFunction(ctx).ToLocalChecked());
  target->Set(ctx, v8str(isolate, "peekAndDispatchMessages"),
              FunctionTemplate::New(isolate, PeakAndDispatchMessages)->GetFunction(ctx).ToLocalChecked());

  // Context::GetCurrent()->Global()->Set(v8str(isolate, "ActiveXObject"), t->GetFunction());
  NODE_DEBUG_MSG("DispObject initialized");
}

Local<Object> DispObject::NodeCreate(Isolate *isolate, const Local<Object> &parent, const DispInfoPtr &ptr,
                                     const std::wstring &name, DISPID id, LONG index, int opt) {
  Local<Object> self;
  if (!inst_template.IsEmpty()) {
    if (inst_template.Get(isolate)->NewInstance(isolate->GetCurrentContext()).ToLocal(&self)) {
      (new DispObject(ptr, name, id, index, opt))->Wrap(self);
      // Local<String> prop_id(v8str(isolate, "_identity"));
      // self->Set(prop_id, v8str(isolate, name.c_str()));
    }
  }
  return self;
}

void DispObject::NodeCreate(const FunctionCallbackInfo<Value> &args) {
  Isolate *isolate = args.GetIsolate();
  Local<Context> ctx = isolate->GetCurrentContext();
  bool isGetObject = false;
  bool isGetAccessibleObject = false;
  int argcnt = args.Length();
  if (argcnt < 1) {
    isolate->ThrowException(InvalidArgumentsError(isolate));
    return;
  }
  int options = (option_async | option_type);
  if (argcnt > 1) {
    Local<Value> val, argopt = args[1];
    bool isEmpty = argopt.IsEmpty();
    bool isObject = argopt->IsObject();
    if (!isEmpty && isObject) {
      auto opt = Local<Object>::Cast(argopt);
      if (opt->Get(ctx, v8str(isolate, "async")).ToLocal(&val)) {
        if (!v8val2bool(isolate, val, true))
          options &= ~option_async;
      }
      if (opt->Get(ctx, v8str(isolate, "type")).ToLocal(&val)) {
        if (!v8val2bool(isolate, val, true))
          options &= ~option_type;
      }
      if (opt->Get(ctx, v8str(isolate, "activate")).ToLocal(&val)) {
        if (v8val2bool(isolate, val, false))
          options |= option_activate;
      }
      if (opt->Get(ctx, v8str(isolate, "getobject")).ToLocal(&val)) {
        if (v8val2bool(isolate, val, false))
          isGetObject = true;
      }
      if (opt->Get(ctx, v8str(isolate, "getaccessibleobject")).ToLocal(&val)) {
        if (v8val2bool(isolate, val, false))
          isGetAccessibleObject = true;
      }
    }
  }

  // Invoked as plain function
  if (!args.IsConstructCall()) {
    Local<FunctionTemplate> clazz = clazz_template.Get(isolate);
    if (clazz.IsEmpty()) {
      isolate->ThrowException(TypeError(isolate, "FunctionTemplateIsEmpty"));
      return;
    }
    const int argc = 1;
    Local<Value> argv[argc] = {args[0]};
    Local<Function> cons;
    Local<Context> context = isolate->GetCurrentContext();
    if (clazz->GetFunction(context).ToLocal(&cons)) {
      Local<Object> self;
      if (cons->NewInstance(context, argc, argv).ToLocal(&self)) {
        args.GetReturnValue().Set(self);
      }
    }
    return;
  }

  // Create dispatch object from ProgId
  HRESULT hrcode;
  std::wstring name;
  CComPtr<IDispatch> disp;
  if (args[0]->IsString()) {

    // Prepare arguments
    String::Value vname(isolate, args[0]);
    if (vname.length() <= 0)
      hrcode = E_INVALIDARG;
    else {
      name.assign((LPOLESTR)*vname, vname.length());

      CComPtr<IUnknown> unk;
      if (isGetObject) {
        hrcode = CoGetObject(name.c_str(), NULL, IID_IUnknown, (void **)&unk);
        if SUCCEEDED (hrcode)
          hrcode = unk->QueryInterface(&disp);
      } else {
        if (isGetAccessibleObject) {
          hrcode = GetAccessibleObject(name.c_str(), unk);
          if SUCCEEDED (hrcode)
            hrcode = unk->QueryInterface(&disp);
        } else {
          CLSID clsid;
          hrcode = CLSIDFromProgID(name.c_str(), &clsid);
          if SUCCEEDED (hrcode) {
            if ((options & option_activate) == 0)
              hrcode = E_FAIL;
            else {
              hrcode = GetActiveObject(clsid, NULL, &unk);
              if SUCCEEDED (hrcode)
                hrcode = unk->QueryInterface(&disp);
            }
            if FAILED (hrcode) {
              hrcode = disp.CoCreateInstance(clsid, nullptr, CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER);
            }
          }
        }
      }
    }
  }

  // Use supplied dispatch pointer
  else if (args[0]->IsUint8Array()) {
    size_t len = node::Buffer::Length(args[0]);
    void *data = node::Buffer::Data(args[0]);
    IDispatch *p = (len == sizeof(INT_PTR)) ? (IDispatch *)*(static_cast<INT_PTR *>(data)) : nullptr;
    if (!p) {
      isolate->ThrowException(InvalidArgumentsError(isolate));
      return;
    }
    disp.Attach(p);
    hrcode = S_OK;
  }

  // Create dispatch object from javascript object
  else if (args[0]->IsObject()) {
    name = L"#";
    disp = new DispObjectImpl(Local<Object>::Cast(args[0]));
    hrcode = S_OK;
  }

  // Other
  else {
    hrcode = E_INVALIDARG;
  }

  // Prepare result
  if FAILED (hrcode) {
    isolate->ThrowException(DispError(isolate, hrcode, L"CreateInstance", name.c_str()));
  } else {
    Local<Object> self = args.This();
    DispInfoPtr ptr(new DispInfo(disp, name, options));
    (new DispObject(ptr, name))->Wrap(self);
    args.GetReturnValue().Set(self);
  }
}

void DispObject::NodeGet(Local<Name> name, const PropertyCallbackInfoGetter &args) {
  Isolate *isolate = args.GetIsolate();
  DispObject *self = DispObject::Unwrap<DispObject>(args.This());
  if (!self) {
    isolate->ThrowException(DispErrorInvalid(isolate));
    return;
  }
  String::Value vname(isolate, name);
  LPOLESTR id = (vname.length() > 0) ? (LPOLESTR)*vname : (LPOLESTR)L"valueOf";
  NODE_DEBUG_FMT2("DispObject '%S.%S' get", self->name.c_str(), id);
  if (_wcsicmp(id, L"__value") == 0) {
    Local<Value> result;
    HRESULT hrcode = self->valueOf(isolate, args.This(), result);
    if FAILED (hrcode)
      isolate->ThrowException(Win32Error(isolate, hrcode, L"DispValueOf"));
    else
      args.GetReturnValue().Set(result);
  } else if (_wcsicmp(id, L"__id") == 0) {
    args.GetReturnValue().Set(self->getIdentity(isolate));
  } else if (_wcsicmp(id, L"__type") == 0) {
    if (self->items.IsEmpty()) {
      self->initTypeInfo(isolate);
    }
    Local<Value> result = self->items.Get(isolate);
    args.GetReturnValue().Set(result);
  } else if (_wcsicmp(id, L"__methods") == 0) {
    if (self->methods.IsEmpty()) {
      self->initTypeInfo(isolate);
    }
    Local<Value> result = self->methods.Get(isolate);
    args.GetReturnValue().Set(result);
  } else if (_wcsicmp(id, L"__vars") == 0) {
    if (self->vars.IsEmpty()) {
      self->initTypeInfo(isolate);
    }
    Local<Value> result = self->vars.Get(isolate);
    args.GetReturnValue().Set(result);
  } else if (_wcsicmp(id, L"__proto__") == 0) {
    Local<Function> func;
    Local<FunctionTemplate> clazz = clazz_template.Get(isolate);
    Local<Context> ctx = isolate->GetCurrentContext();
    if (!clazz.IsEmpty() && clazz->GetFunction(ctx).ToLocal(&func)) {
      args.GetReturnValue().Set(func);
    } else {
      args.GetReturnValue().SetNull();
    }
  } else {
    Local<Function> func;
    if (clazz_methods.get(isolate, id, &func)) {
      args.GetReturnValue().Set(func);
    }

    else if (!self->get(id, -1, args)) {
      Local<Value> result;
      HRESULT hrcode = self->valueOf(isolate, args.This(), result);
      if FAILED (hrcode)
        isolate->ThrowException(Win32Error(isolate, hrcode, L"Unable to Get Value"));

      Local<Context> ctx = isolate->GetCurrentContext();
      MaybeLocal<Object> localObj = result->ToObject(ctx);
      if (localObj.IsEmpty()) {
        args.GetReturnValue().SetUndefined();
        return;
      }

      Local<Object> obj = localObj.ToLocalChecked();
      MaybeLocal<Value> realProp = obj->GetRealNamedPropertyInPrototypeChain(ctx, v8str(isolate, id));
      if (realProp.IsEmpty()) {
        // We may call non-existing property for an object to check its existence
        // So we should return undefined in this case
        args.GetReturnValue().SetUndefined();
      } else {
        Local<Value> ownProp = realProp.ToLocalChecked();
        if (ownProp->IsFunction()) {
          Local<Function> func = Local<Function>::Cast(ownProp);
          if (func.IsEmpty())
            return;
          args.GetReturnValue().Set(func);
          return;
        }
      }
    }
  }
}

void DispObject::NodeGetByIndex(uint32_t index, const PropertyCallbackInfoGetter &args) {
  Isolate *isolate = args.GetIsolate();
  DispObject *self = DispObject::Unwrap<DispObject>(args.This());
  if (!self) {
    isolate->ThrowException(DispErrorInvalid(isolate));
    return;
  }
  NODE_DEBUG_FMT2("DispObject '%S[%u]' get", self->name.c_str(), index);
  self->get(0, index, args);
}

void DispObject::NodeSet(Local<Name> name, Local<Value> value, const PropertyCallbackInfoSetter &args) {
  Isolate *isolate = args.GetIsolate();
  DispObject *self = DispObject::Unwrap<DispObject>(args.This());
  if (!self) {
    isolate->ThrowException(DispErrorInvalid(isolate));
    return;
  }
  String::Value vname(isolate, name);
  LPOLESTR id = (vname.length() > 0) ? (LPOLESTR)*vname : (LPOLESTR)L"";
  NODE_DEBUG_FMT2("DispObject '%S.%S' set", self->name.c_str(), id);
  self->set(id, -1, value, args);
}

void DispObject::NodeSetByIndex(uint32_t index, Local<Value> value, const PropertyCallbackInfoSetter &args) {
  Isolate *isolate = args.GetIsolate();
  DispObject *self = DispObject::Unwrap<DispObject>(args.This());
  if (!self) {
    isolate->ThrowException(DispErrorInvalid(isolate));
    return;
  }
  NODE_DEBUG_FMT2("DispObject '%S[%u]' set", self->name.c_str(), index);
  self->set(0, index, value, args);
}

void DispObject::NodeCall(const FunctionCallbackInfo<Value> &args) {
  Isolate *isolate = args.GetIsolate();
  DispObject *self = DispObject::Unwrap<DispObject>(args.This());
  if (!self) {
    isolate->ThrowException(DispErrorInvalid(isolate));
    return;
  }
  NODE_DEBUG_FMT("DispObject '%S' call", self->name.c_str());
  self->call(isolate, args);
}

void DispObject::NodeValueOf(const FunctionCallbackInfo<Value> &args) {
  Isolate *isolate = args.GetIsolate();
  DispObject *self = DispObject::Unwrap<DispObject>(args.This());
  if (!self) {
    isolate->ThrowException(DispErrorInvalid(isolate));
    return;
  }
  Local<Value> result;
  HRESULT hrcode = self->valueOf(isolate, args.This(), result);
  if FAILED (hrcode) {
    isolate->ThrowException(Win32Error(isolate, hrcode, L"DispValueOf"));
    return;
  }
  args.GetReturnValue().Set(result);
}

void DispObject::NodeToString(const FunctionCallbackInfo<Value> &args) {
  Isolate *isolate = args.GetIsolate();
  DispObject *self = DispObject::Unwrap<DispObject>(args.This());
  if (!self) {
    isolate->ThrowException(DispErrorInvalid(isolate));
    return;
  }
  self->toString(args);
}

void DispObject::NodeRelease(const FunctionCallbackInfo<Value> &args) {
  Isolate *isolate = args.GetIsolate();
  int rcnt = 0, argcnt = args.Length();
  for (int argi = 0; argi < argcnt; argi++) {
    auto obj = args[argi];
    if (obj->IsObject()) {
      auto disp_obj = Local<Object>::Cast(obj);
      DispObject *disp = DispObject::Unwrap<DispObject>(disp_obj);
      if (disp && disp->release())
        rcnt++;
    }
  }
  args.GetReturnValue().Set(rcnt);
}

void DispObject::NodeCast(const FunctionCallbackInfo<Value> &args) {
  Local<Object> inst = VariantObject::NodeCreateInstance(args);
  args.GetReturnValue().Set(inst);
}

void DispObject::NodeConnectionPoints(const FunctionCallbackInfo<Value> &args) {
  Isolate *isolate = args.GetIsolate();
  Local<Context> ctx = isolate->GetCurrentContext();
  Local<Array> items = Array::New(isolate);
  CComPtr<IDispatch> ptr;
  CComPtr<IConnectionPointContainer> cp_cont;
  CComPtr<IEnumConnectionPoints> cp_enum;

  // prepare connecton points from arguments
  int argcnt = args.Length();
  if (argcnt >= 1) {
    auto arg = args[0];
    if (Value2Unknown(isolate, arg, (IUnknown **)&ptr)) {
      if SUCCEEDED (ptr->QueryInterface(&cp_cont)) {
        cp_cont->EnumConnectionPoints(&cp_enum);
      }
    }
  }

  // enumerate connection points
  if (cp_enum) {
    ULONG cnt_fetched;
    CComPtr<IConnectionPoint> cp_ptr;
    uint32_t cnt = 0;
    while (SUCCEEDED(cp_enum->Next(1, &cp_ptr, &cnt_fetched)) && cnt_fetched == 1) {
      items->Set(ctx, cnt++, ConnectionPointObject::NodeCreateInstance(isolate, cp_ptr, ptr));
      cp_ptr.Release();
    }
  }

  // return array of connection points
  args.GetReturnValue().Set(items);
}
void DispObject::PeakAndDispatchMessages(const FunctionCallbackInfo<Value> &args) {
  MSG msg;
  while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
    DispatchMessage(&msg);
  }
}

//-------------------------------------------------------------------------------------------------------

class vtypes_t {
public:
  inline vtypes_t(std::initializer_list<std::pair<std::wstring, VARTYPE>> recs) {
    for (auto &rec : recs) {
      str2vt.emplace(rec.first, rec.second);
      vt2str.emplace(rec.second, rec.first);
    }
  }
  inline bool find(VARTYPE vt, std::wstring &name) {
    auto it = vt2str.find(vt);
    if (it == vt2str.end())
      return false;
    name = it->second;
    return true;
  }
  inline VARTYPE find(const std::wstring &name) {
    auto it = str2vt.find(name);
    if (it == str2vt.end())
      return VT_EMPTY;
    return it->second;
  }

private:
  std::map<std::wstring, VARTYPE> str2vt;
  std::map<VARTYPE, std::wstring> vt2str;
};

static vtypes_t vtypes({{L"char", VT_I1},     {L"uchar", VT_UI1},   {L"byte", VT_UI1},        {L"short", VT_I2},
                        {L"ushort", VT_UI2},  {L"int", VT_INT},     {L"uint", VT_UINT},       {L"long", VT_I8},
                        {L"ulong", VT_UI8},

                        {L"int8", VT_I1},     {L"uint8", VT_UI1},   {L"int16", VT_I2},        {L"uint16", VT_UI2},
                        {L"int32", VT_I4},    {L"uint32", VT_UI4},  {L"int64", VT_I8},        {L"uint64", VT_UI8},
                        {L"currency", VT_CY},

                        {L"float", VT_R4},    {L"double", VT_R8},   {L"date", VT_DATE},       {L"decimal", VT_DECIMAL},

                        {L"string", VT_BSTR}, {L"empty", VT_EMPTY}, {L"variant", VT_VARIANT}, {L"null", VT_NULL},
                        {L"byref", VT_BYREF}});

bool VariantObject::assign(Isolate *isolate, Local<Value> &val, Local<Value> &type) {
  VARTYPE vt = VT_EMPTY;
  if (!type.IsEmpty()) {
    if (type->IsString()) {
      String::Value vtstr(isolate, type);
      const wchar_t *pvtstr = (const wchar_t *)*vtstr;
      int vtstr_len = vtstr.length();
      if (vtstr_len > 1) {
        if (pvtstr[0] == 'p') {
          vt |= VT_BYREF;
          vtstr_len--;
          pvtstr++;
        } else if (pvtstr[vtstr_len - 1] == '*') {
          vt |= VT_BYREF;
          vtstr_len--;
        } else if (pvtstr[vtstr_len - 2] == '[' || pvtstr[vtstr_len - 1] == ']') {
          vt |= VT_ARRAY;
          vtstr_len -= 2;
        }
      }
      if (vtstr_len > 0) {
        std::wstring type(pvtstr, vtstr_len);
        vt |= vtypes.find(type);
      }
    } else if (type->IsInt32()) {
      vt |= type->Int32Value(isolate->GetCurrentContext()).FromMaybe(0);
    }
  }

  if (val.IsEmpty()) {
    if FAILED (value.ChangeType(vt))
      return false;
    if ((value.vt & VT_BYREF) == 0)
      pvalue.Clear();
    return true;
  }

  value.Clear();
  pvalue.Clear();
  if ((vt & VT_ARRAY) != 0) {
    Value2SafeArray(isolate, val, value, vt & ~VT_ARRAY);
  } else if ((vt & VT_BYREF) == 0) {
    Value2Variant(isolate, val, value, vt);
  } else {
    VARIANT *refvalue = nullptr;
    VARTYPE vt_noref = vt & ~VT_BYREF;
    VariantObject *ref =
        (!val.IsEmpty() && val->IsObject()) ? GetInstanceOf(isolate, Local<Object>::Cast(val)) : nullptr;
    if (ref) {
      if ((ref->value.vt & VT_BYREF) != 0)
        value = ref->value;
      else
        refvalue = &ref->value;
    } else {
      Value2Variant(isolate, val, pvalue, vt_noref);
      refvalue = &pvalue;
    }
    if (refvalue) {
      if (vt_noref == 0 || vt_noref == VT_VARIANT || refvalue->vt == VT_EMPTY) {
        value.vt = VT_VARIANT | VT_BYREF;
        value.pvarVal = refvalue;
      } else {
        value.vt = refvalue->vt | VT_BYREF;
        value.byref = &refvalue->intVal;
      }
    }
  }
  return true;
}

VariantObject::VariantObject(const FunctionCallbackInfo<Value> &args) {
  Local<Value> val, type;
  int argcnt = args.Length();
  if (argcnt > 0)
    val = args[0];
  if (argcnt > 1)
    type = args[1];
  assign(args.GetIsolate(), val, type);
}

void VariantObject::NodeInit(const Local<Object> &target, Isolate *isolate, Local<Context> &ctx) {

  // Prepare constructor template
  Local<FunctionTemplate> clazz = FunctionTemplate::New(isolate, NodeCreate);
  clazz->SetClassName(v8str(isolate, "Variant"));
  clazz_methods.add(isolate, clazz, "clear", NodeClear);
  clazz_methods.add(isolate, clazz, "assign", NodeAssign);
  clazz_methods.add(isolate, clazz, "cast", NodeCast);
  clazz_methods.add(isolate, clazz, "toString", NodeToString);
  clazz_methods.add(isolate, clazz, "valueOf", NodeValueOf);

  Local<ObjectTemplate> inst = clazz->InstanceTemplate();
  inst->SetInternalFieldCount(1);

#ifdef NODE_INTERCEPTED
  inst->SetHandler(NamedPropertyHandlerConfiguration(InterceptedNodeGet, InterceptedNodeSet));
  inst->SetHandler(IndexedPropertyHandlerConfiguration(InterceptedNodeGetByIndex, InterceptedNodeSetByIndex));
#else
  inst->SetHandler(NamedPropertyHandlerConfiguration(NodeGet, NodeSet));
  inst->SetHandler(IndexedPropertyHandlerConfiguration(NodeGetByIndex, NodeSetByIndex));
#endif
  // inst->SetCallAsFunctionHandler(NodeCall);
  // inst->SetNativeDataProperty(v8str(isolate, "__id"), NodeGet);

  inst->SetNativeDataProperty(v8str(isolate, "__value"), NodeGet);
  // inst->SetLazyDataProperty(v8str(isolate, "__type"), NodeGet, Local<Value>(), ReadOnly);
  inst->SetNativeDataProperty(v8str(isolate, "__type"), NodeGet);

  inst_template.Reset(isolate, inst);
  clazz_template.Reset(isolate, clazz);
  Local<Function> func;
  if (clazz->GetFunction(ctx).ToLocal(&func)) {
    target->Set(ctx, v8str(isolate, "Variant"), func);
  }
  NODE_DEBUG_MSG("VariantObject initialized");
}

Local<Object> VariantObject::NodeCreateInstance(const FunctionCallbackInfo<Value> &args) {
  Local<Object> self;
  Isolate *isolate = args.GetIsolate();
  if (!inst_template.IsEmpty()) {
    if (inst_template.Get(isolate)->NewInstance(isolate->GetCurrentContext()).ToLocal(&self)) {
      (new VariantObject(args))->Wrap(self);
    }
  }
  return self;
}

void VariantObject::NodeCreate(const FunctionCallbackInfo<Value> &args) {
  Isolate *isolate = args.GetIsolate();
  Local<Object> self = args.This();
  (new VariantObject(args))->Wrap(self);
  args.GetReturnValue().Set(self);
}

void VariantObject::NodeClear(const FunctionCallbackInfo<Value> &args) {
  Isolate *isolate = args.GetIsolate();
  VariantObject *self = VariantObject::Unwrap<VariantObject>(args.This());
  if (!self) {
    isolate->ThrowException(DispErrorInvalid(isolate));
    return;
  }
  self->value.Clear();
  self->pvalue.Clear();
}

void VariantObject::NodeAssign(const FunctionCallbackInfo<Value> &args) {
  Isolate *isolate = args.GetIsolate();
  VariantObject *self = VariantObject::Unwrap<VariantObject>(args.This());
  if (!self) {
    isolate->ThrowException(DispErrorInvalid(isolate));
    return;
  }
  Local<Value> val, type;
  int argcnt = args.Length();
  if (argcnt > 0)
    val = args[0];
  if (argcnt > 1)
    type = args[1];
  self->assign(isolate, val, type);
}

void VariantObject::NodeCast(const FunctionCallbackInfo<Value> &args) {
  Isolate *isolate = args.GetIsolate();
  VariantObject *self = VariantObject::Unwrap<VariantObject>(args.This());
  if (!self) {
    isolate->ThrowException(DispErrorInvalid(isolate));
    return;
  }
  Local<Value> val, type;
  int argcnt = args.Length();
  if (argcnt > 0)
    type = args[0];
  self->assign(isolate, val, type);
}

void VariantObject::NodeValueOf(const FunctionCallbackInfo<Value> &args) {
  Isolate *isolate = args.GetIsolate();
  VariantObject *self = VariantObject::Unwrap<VariantObject>(args.This());
  if (!self) {
    isolate->ThrowException(DispErrorInvalid(isolate));
    return;
  }
  // Last parameter false because valueOf should return primitive value
  Local<Value> result = Variant2Value(isolate, self->value, false);
  args.GetReturnValue().Set(result);
}

void VariantObject::NodeToString(const FunctionCallbackInfo<Value> &args) {
  Isolate *isolate = args.GetIsolate();
  VariantObject *self = VariantObject::Unwrap<VariantObject>(args.This());
  if (!self) {
    isolate->ThrowException(DispErrorInvalid(isolate));
    return;
  }
  Local<Value> result = Variant2String(isolate, self->value);
  args.GetReturnValue().Set(result);
}

void VariantObject::NodeGet(Local<Name> name, const PropertyCallbackInfoGetter &args) {
  Isolate *isolate = args.GetIsolate();
  VariantObject *self = VariantObject::Unwrap<VariantObject>(args.This());
  if (!self) {
    isolate->ThrowException(DispErrorInvalid(isolate));
    return;
  }

  String::Value vname(isolate, name);
  LPOLESTR id = (vname.length() > 0) ? (LPOLESTR)*vname : (LPOLESTR)L"valueOf";
  if (_wcsicmp(id, L"__value") == 0) {
    Local<Value> result = Variant2Value(isolate, self->value);
    args.GetReturnValue().Set(result);
  } else if (_wcsicmp(id, L"__type") == 0) {
    std::wstring type, name;
    if (self->value.vt & VT_BYREF)
      type += L"byref:";
    if (self->value.vt & VT_ARRAY)
      type = L"array:";
    if (vtypes.find(self->value.vt & VT_TYPEMASK, name))
      type += name;
    else
      type += std::to_wstring(self->value.vt & VT_TYPEMASK);
    Local<String> text = v8str(isolate, type.c_str());
  } else if (_wcsicmp(id, L"__proto__") == 0) {
    Local<Function> func;
    Local<FunctionTemplate> clazz = clazz_template.Get(isolate);
    Local<Context> ctx = isolate->GetCurrentContext();
    if (!clazz.IsEmpty() && clazz_template.Get(isolate)->GetFunction(ctx).ToLocal(&func)) {
      args.GetReturnValue().Set(func);
    } else {
      args.GetReturnValue().SetNull();
    }
  } else if (_wcsicmp(id, L"length") == 0) {
    if ((self->value.vt & VT_ARRAY) != 0) {
      args.GetReturnValue().Set((uint32_t)self->value.ArrayLength());
    }
  } else {
    Local<Function> func;
    if (clazz_methods.get(isolate, id, &func)) {
      args.GetReturnValue().Set(func);
    }
  }
}

void VariantObject::NodeGetByIndex(uint32_t index, const PropertyCallbackInfoGetter &args) {
  Isolate *isolate = args.GetIsolate();
  VariantObject *self = VariantObject::Unwrap<VariantObject>(args.This());
  if (!self) {
    isolate->ThrowException(DispErrorInvalid(isolate));
    return;
  }
  Local<Value> result;
  if ((self->value.vt & VT_ARRAY) == 0) {
    result = Variant2Value(isolate, self->value);
  } else {
    CComVariant value;
    if SUCCEEDED (self->value.ArrayGet((LONG)index, value)) {
      result = Variant2Value(isolate, value);
    }
  }
  args.GetReturnValue().Set(result);
}

void VariantObject::NodeSet(Local<Name> name, Local<Value> val, const PropertyCallbackInfoSetter &args) {
  Isolate *isolate = args.GetIsolate();
  VariantObject *self = VariantObject::Unwrap<VariantObject>(args.This());
  if (!self) {
    isolate->ThrowException(DispErrorInvalid(isolate));
    return;
  }
  isolate->ThrowException(DispError(isolate, E_NOTIMPL));
}

void VariantObject::NodeSetByIndex(uint32_t index, Local<Value> value, const PropertyCallbackInfoSetter &args) {
  Isolate *isolate = args.GetIsolate();
  VariantObject *self = VariantObject::Unwrap<VariantObject>(args.This());
  if (!self) {
    isolate->ThrowException(DispErrorInvalid(isolate));
    return;
  }
  isolate->ThrowException(DispError(isolate, E_NOTIMPL));
}

Local<Object> VariantObject::NodeCreate(Isolate *isolate, const VARIANT &var) {
  Local<Object> self;
  if (!inst_template.IsEmpty()) {
    if (inst_template.Get(isolate)->NewInstance(isolate->GetCurrentContext()).ToLocal(&self)) {
      (new VariantObject(var))->Wrap(self);
    }
  }
  return self;
}

//-------------------------------------------------------------------------------------------------------

ConnectionPointObject::ConnectionPointObject(IConnectionPoint *p, IDispatch *d) : ptr(p), disp(d) { InitIndex(); }

bool ConnectionPointObject::InitIndex() {
  if (!ptr || !disp) {
    return false;
  }
  UINT typeindex = 0;
  CComPtr<ITypeInfo> typeinfo;
  if FAILED (disp->GetTypeInfo(typeindex, LOCALE_USER_DEFAULT, &typeinfo)) {
    return false;
  }

  CComPtr<ITypeLib> typelib;
  if FAILED (typeinfo->GetContainingTypeLib(&typelib, &typeindex)) {
    return false;
  }

  IID conniid;
  if FAILED (ptr->GetConnectionInterface(&conniid)) {
    return false;
  }

  CComPtr<ITypeInfo> conninfo;
  if FAILED (typelib->GetTypeInfoOfGuid(conniid, &conninfo)) {
    return false;
  }

  TYPEATTR *typeattr = nullptr;
  if FAILED (conninfo->GetTypeAttr(&typeattr)) {
    return false;
  }

  if (typeattr->typekind != TKIND_DISPATCH) {
    conninfo->ReleaseTypeAttr(typeattr);
    return false;
  }

  for (UINT fd = 0; fd < typeattr->cFuncs; ++fd) {
    FUNCDESC *funcdesc;
    if FAILED (conninfo->GetFuncDesc(fd, &funcdesc)) {
      continue;
    }
    if (!funcdesc) {
      break;
    }

    if (funcdesc->invkind != INVOKE_FUNC || funcdesc->funckind != FUNC_DISPATCH) {
      conninfo->ReleaseFuncDesc(funcdesc);
      continue;
    }

    // const size_t nameSize = 256;
    const size_t nameSize = 1; // only event function name required
    BSTR bstrNames[nameSize];
    UINT maxNames = nameSize;
    UINT maxNamesOut = 0;
    if SUCCEEDED (conninfo->GetNames(funcdesc->memid, reinterpret_cast<BSTR *>(&bstrNames), maxNames, &maxNamesOut)) {
      DISPID id = funcdesc->memid;
      std::wstring funcname(bstrNames[0]);
      index.insert(std::pair<DISPID, DispObjectImpl::name_ptr>(id, new DispObjectImpl::name_t(id, funcname)));

      for (size_t i = 0; i < maxNamesOut; i++) {
        SysFreeString(bstrNames[i]);
      }
    }

    conninfo->ReleaseFuncDesc(funcdesc);
  }

  conninfo->ReleaseTypeAttr(typeattr);

  return true;
}

Local<Object> ConnectionPointObject::NodeCreateInstance(Isolate *isolate, IConnectionPoint *p, IDispatch *d) {
  Local<Object> self;
  if (!inst_template.IsEmpty()) {
    if (inst_template.Get(isolate)->NewInstance(isolate->GetCurrentContext()).ToLocal(&self)) {
      (new ConnectionPointObject(p, d))->Wrap(self);
    }
  }
  return self;
}

void ConnectionPointObject::NodeInit(const Local<Object> &target, Isolate *isolate, Local<Context> &ctx) {

  // Prepare constructor template
  Local<FunctionTemplate> clazz = FunctionTemplate::New(isolate, NodeCreate);
  clazz->SetClassName(v8str(isolate, "ConnectionPoint"));

  NODE_SET_PROTOTYPE_METHOD(clazz, "advise", NodeAdvise);
  NODE_SET_PROTOTYPE_METHOD(clazz, "unadvise", NodeUnadvise);
  NODE_SET_PROTOTYPE_METHOD(clazz, "getMethods", NodeConnectionPointMethods);

  Local<ObjectTemplate> inst = clazz->InstanceTemplate();
  inst->SetInternalFieldCount(1);

  inst_template.Reset(isolate, inst);
  clazz_template.Reset(isolate, clazz);
  // target->Set(v8str(isolate, "ConnectionPoint"), clazz->GetFunction());
  NODE_DEBUG_MSG("ConnectionPointObject initialized");
}

void ConnectionPointObject::NodeCreate(const FunctionCallbackInfo<Value> &args) {
  Isolate *isolate = args.GetIsolate();
  Local<Object> self = args.This();
  (new ConnectionPointObject(args))->Wrap(self);
  args.GetReturnValue().Set(self);
}

void ConnectionPointObject::NodeAdvise(const FunctionCallbackInfo<Value> &args) {
  Isolate *isolate = args.GetIsolate();
  ConnectionPointObject *self = ConnectionPointObject::Unwrap<ConnectionPointObject>(args.This());
  if (!self || !self->ptr) {
    isolate->ThrowException(DispErrorInvalid(isolate));
    return;
  }
  CComPtr<IUnknown> unk;
  int argcnt = args.Length();
  if (argcnt > 0) {
    Local<Value> val = args[0];
    if (!Value2Unknown(isolate, val, &unk)) {
      Local<Object> obj;
      if (!val.IsEmpty() && val->IsObject() && val->ToObject(isolate->GetCurrentContext()).ToLocal(&obj)) {

        // .NET Connection Points require to implement specific interface
        // So we need to remember its IID for the case when Container does QueryInterface for it
        IID connif;
        self->ptr->GetConnectionInterface(&connif);
        DispObjectImpl *impl = new DispObjectImpl(obj, false, connif);
        // It requires reversed arguments
        impl->reverse_arguments = true;
        impl->index = self->index;
        if (self->index.size()) {
          impl->dispid_next = self->index.rbegin()->first + 1;
        }
        unk.Attach(impl);
      }
    }
  }
  if (!unk) {
    isolate->ThrowException(InvalidArgumentsError(isolate));
    return;
  }
  DWORD dwCookie;
  HRESULT hrcode = self->ptr->Advise(unk, &dwCookie);
  if FAILED (hrcode) {
    isolate->ThrowException(DispError(isolate, hrcode));
    return;
  }
  self->cookies.insert(dwCookie);
  args.GetReturnValue().Set(v8::Integer::New(isolate, (uint32_t)dwCookie));
}

void ConnectionPointObject::NodeUnadvise(const FunctionCallbackInfo<Value> &args) {
  Isolate *isolate = args.GetIsolate();
  Local<Context> ctx = isolate->GetCurrentContext();
  ConnectionPointObject *self = ConnectionPointObject::Unwrap<ConnectionPointObject>(args.This());
  if (!self || !self->ptr) {
    isolate->ThrowException(DispErrorInvalid(isolate));
    return;
  }

  if (args.Length() == 0 || !args[0]->IsUint32()) {
    isolate->ThrowException(InvalidArgumentsError(isolate));
    return;
  }
  DWORD dwCookie = (args[0]->Uint32Value(ctx)).FromMaybe(0);
  if (dwCookie == 0 || self->cookies.find(dwCookie) == self->cookies.end()) {
    isolate->ThrowException(InvalidArgumentsError(isolate));
    return;
  }
  self->cookies.erase(dwCookie);
  HRESULT hrcode = self->ptr->Unadvise(dwCookie);
  if FAILED (hrcode) {
    isolate->ThrowException(DispError(isolate, hrcode));
    return;
  }
}

void ConnectionPointObject::NodeConnectionPointMethods(const FunctionCallbackInfo<Value> &args) {
  Isolate *isolate = args.GetIsolate();
  Local<Context> ctx = isolate->GetCurrentContext();
  Local<Array> items = Array::New(isolate);

  ConnectionPointObject *self = ConnectionPointObject::Unwrap<ConnectionPointObject>(args.This());

  DispObjectImpl::index_t::iterator it;
  uint32_t cnt = 0;

  for (it = self->index.begin(); it != self->index.end(); it++) {
    items->Set(ctx, cnt++, v8str(isolate, it->second->name.c_str()));
  }

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

//-------------------------------------------------------------------------------------------------------
