// Copyright (c) The NodeRT Contributors
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the ""License""); you may
// not use this file except in compliance with the License. You may obtain a
// copy of the License at http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED ON AN  *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
//
// See the Apache Version 2.0 License for specific language governing permissions
// and limitations under the License.

// TODO: Verify that this is is still needed..
#define NTDDI_VERSION 0x06010000

#include <v8.h>
#include "nan.h"
#include <string>
#include <ppltasks.h>
#include "CollectionsConverter.h"
#include "CollectionsWrap.h"
#include "node-async.h"
#include "NodeRtUtils.h"
#include "OpaqueWrapper.h"
#include "WrapperBase.h"

#using <Windows.WinMD>

// this undefs fixes the issues of compiling Windows.Data.Json, Windows.Storag.FileProperties, and Windows.Stroage.Search
// Some of the node header files brings windows definitions with the same names as some of the WinRT methods
#undef DocumentProperties
#undef GetObject
#undef CreateEvent
#undef FindText
#undef SendMessage

const char* REGISTRATION_TOKEN_MAP_PROPERTY_NAME = "__registrationTokenMap__";

using v8::Array;
using v8::String;
using v8::Value;
using v8::Boolean;
using v8::Integer;
using v8::FunctionTemplate;
using v8::Object;
using v8::Local;
using v8::Function;
using v8::Date;
using v8::Number;
using v8::PropertyAttribute;
using v8::Primitive;
using Nan::HandleScope;
using Nan::Persistent;
using Nan::Undefined;
using Nan::True;
using Nan::False;
using Nan::Null;
using Nan::MaybeLocal;
using Nan::EscapableHandleScope;
using Nan::HandleScope;
using Nan::TryCatch;
using namespace concurrency;

namespace NodeRT { namespace Windows { namespace Management { 
  v8::Local<v8::Value> WrapMdmAlert(::Windows::Management::MdmAlert^ wintRtInstance);
  ::Windows::Management::MdmAlert^ UnwrapMdmAlert(Local<Value> value);
  
  v8::Local<v8::Value> WrapMdmSession(::Windows::Management::MdmSession^ wintRtInstance);
  ::Windows::Management::MdmSession^ UnwrapMdmSession(Local<Value> value);
  
  v8::Local<v8::Value> WrapMdmSessionManager(::Windows::Management::MdmSessionManager^ wintRtInstance);
  ::Windows::Management::MdmSessionManager^ UnwrapMdmSessionManager(Local<Value> value);
  



  static void InitMdmAlertMarkEnum(const Local<Object> exports) {
    HandleScope scope;

    Local<Object> enumObject = Nan::New<Object>();

    Nan::Set(exports, Nan::New<String>("MdmAlertMark").ToLocalChecked(), enumObject);
    Nan::Set(enumObject, Nan::New<String>("none").ToLocalChecked(), Nan::New<Integer>(static_cast<int>(::Windows::Management::MdmAlertMark::None)));
    Nan::Set(enumObject, Nan::New<String>("fatal").ToLocalChecked(), Nan::New<Integer>(static_cast<int>(::Windows::Management::MdmAlertMark::Fatal)));
    Nan::Set(enumObject, Nan::New<String>("critical").ToLocalChecked(), Nan::New<Integer>(static_cast<int>(::Windows::Management::MdmAlertMark::Critical)));
    Nan::Set(enumObject, Nan::New<String>("warning").ToLocalChecked(), Nan::New<Integer>(static_cast<int>(::Windows::Management::MdmAlertMark::Warning)));
    Nan::Set(enumObject, Nan::New<String>("informational").ToLocalChecked(), Nan::New<Integer>(static_cast<int>(::Windows::Management::MdmAlertMark::Informational)));
  }

  static void InitMdmAlertDataTypeEnum(const Local<Object> exports) {
    HandleScope scope;

    Local<Object> enumObject = Nan::New<Object>();

    Nan::Set(exports, Nan::New<String>("MdmAlertDataType").ToLocalChecked(), enumObject);
    Nan::Set(enumObject, Nan::New<String>("string").ToLocalChecked(), Nan::New<Integer>(static_cast<int>(::Windows::Management::MdmAlertDataType::String)));
    Nan::Set(enumObject, Nan::New<String>("base64").ToLocalChecked(), Nan::New<Integer>(static_cast<int>(::Windows::Management::MdmAlertDataType::Base64)));
    Nan::Set(enumObject, Nan::New<String>("boolean").ToLocalChecked(), Nan::New<Integer>(static_cast<int>(::Windows::Management::MdmAlertDataType::Boolean)));
    Nan::Set(enumObject, Nan::New<String>("integer").ToLocalChecked(), Nan::New<Integer>(static_cast<int>(::Windows::Management::MdmAlertDataType::Integer)));
  }

  static void InitMdmSessionStateEnum(const Local<Object> exports) {
    HandleScope scope;

    Local<Object> enumObject = Nan::New<Object>();

    Nan::Set(exports, Nan::New<String>("MdmSessionState").ToLocalChecked(), enumObject);
    Nan::Set(enumObject, Nan::New<String>("notStarted").ToLocalChecked(), Nan::New<Integer>(static_cast<int>(::Windows::Management::MdmSessionState::NotStarted)));
    Nan::Set(enumObject, Nan::New<String>("starting").ToLocalChecked(), Nan::New<Integer>(static_cast<int>(::Windows::Management::MdmSessionState::Starting)));
    Nan::Set(enumObject, Nan::New<String>("connecting").ToLocalChecked(), Nan::New<Integer>(static_cast<int>(::Windows::Management::MdmSessionState::Connecting)));
    Nan::Set(enumObject, Nan::New<String>("communicating").ToLocalChecked(), Nan::New<Integer>(static_cast<int>(::Windows::Management::MdmSessionState::Communicating)));
    Nan::Set(enumObject, Nan::New<String>("alertStatusAvailable").ToLocalChecked(), Nan::New<Integer>(static_cast<int>(::Windows::Management::MdmSessionState::AlertStatusAvailable)));
    Nan::Set(enumObject, Nan::New<String>("retrying").ToLocalChecked(), Nan::New<Integer>(static_cast<int>(::Windows::Management::MdmSessionState::Retrying)));
    Nan::Set(enumObject, Nan::New<String>("completed").ToLocalChecked(), Nan::New<Integer>(static_cast<int>(::Windows::Management::MdmSessionState::Completed)));
  }



  class MdmAlert : public WrapperBase {
    public:
      
      static void Init(const Local<Object> exports) {
        HandleScope scope;

        Local<FunctionTemplate> localRef = Nan::New<FunctionTemplate>(New);
        s_constructorTemplate.Reset(localRef);
        localRef->SetClassName(Nan::New<String>("MdmAlert").ToLocalChecked());
        localRef->InstanceTemplate()->SetInternalFieldCount(1);





          
            Nan::SetAccessor(localRef->PrototypeTemplate(), Nan::New<String>("type").ToLocalChecked(), TypeGetter, TypeSetter);
            Nan::SetAccessor(localRef->PrototypeTemplate(), Nan::New<String>("target").ToLocalChecked(), TargetGetter, TargetSetter);
            Nan::SetAccessor(localRef->PrototypeTemplate(), Nan::New<String>("source").ToLocalChecked(), SourceGetter, SourceSetter);
            Nan::SetAccessor(localRef->PrototypeTemplate(), Nan::New<String>("mark").ToLocalChecked(), MarkGetter, MarkSetter);
            Nan::SetAccessor(localRef->PrototypeTemplate(), Nan::New<String>("format").ToLocalChecked(), FormatGetter, FormatSetter);
            Nan::SetAccessor(localRef->PrototypeTemplate(), Nan::New<String>("data").ToLocalChecked(), DataGetter, DataSetter);
            Nan::SetAccessor(localRef->PrototypeTemplate(), Nan::New<String>("status").ToLocalChecked(), StatusGetter);

        Local<Object> constructor = Nan::To<Object>(Nan::GetFunction(localRef).ToLocalChecked()).ToLocalChecked();
        Nan::SetMethod(constructor, "castFrom", CastFrom);



        Nan::Set(exports, Nan::New<String>("MdmAlert").ToLocalChecked(), constructor);
      }

      virtual ::Platform::Object^ GetObjectInstance() const override {
        return _instance;
      }

    private:

      MdmAlert(::Windows::Management::MdmAlert^ instance) {
        _instance = instance;
      }

      
    static void New(Nan::NAN_METHOD_ARGS_TYPE info) {
      HandleScope scope;

      Local<FunctionTemplate> localRef = Nan::New<FunctionTemplate>(s_constructorTemplate);

      // in case the constructor was called without the new operator
      if (!localRef->HasInstance(info.This())) {
        if (info.Length() > 0) {
          std::unique_ptr<Local<Value> []> constructorArgs(new Local<Value>[info.Length()]);

          Local<Value> *argsPtr = constructorArgs.get();
          for (int i = 0; i < info.Length(); i++) {
            argsPtr[i] = info[i];
          }

          MaybeLocal<Object> res = Nan::NewInstance(Nan::GetFunction(localRef).ToLocalChecked(), info.Length(), constructorArgs.get());
          if (res.IsEmpty()) {
            return;
          }

          info.GetReturnValue().Set(res.ToLocalChecked());
          return;
        } else {
          MaybeLocal<Object> res = Nan::NewInstance(Nan::GetFunction(localRef).ToLocalChecked(), info.Length(), nullptr);

          if (res.IsEmpty()) {
            return;
          }

          info.GetReturnValue().Set(res.ToLocalChecked());
          return;
        }
      }

      ::Windows::Management::MdmAlert^ winRtInstance;


      if (info.Length() == 1 && OpaqueWrapper::IsOpaqueWrapper(info[0]) &&
        NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmAlert^>(info[0])) {
        try {
          winRtInstance = (::Windows::Management::MdmAlert^) NodeRT::Utils::GetObjectInstance(info[0]);
        } catch (Platform::Exception ^exception) {
          NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
          return;
        }
      }
      else if (info.Length() == 0)
      {
        try {
          winRtInstance = ref new ::Windows::Management::MdmAlert();
        } catch (Platform::Exception ^exception) {
          NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
          return;
        }
      }
 else {
        Nan::ThrowError(Nan::Error(NodeRT::Utils::NewString(L"Invalid arguments, no suitable constructor found")));
        return;
      }

      NodeRT::Utils::SetHiddenValue(info.This(), Nan::New<String>("__winRtInstance__").ToLocalChecked(), True());

      MdmAlert *wrapperInstance = new MdmAlert(winRtInstance);
      wrapperInstance->Wrap(info.This());

      info.GetReturnValue().Set(info.This());
    }


      
    static void CastFrom(Nan::NAN_METHOD_ARGS_TYPE info) {
      HandleScope scope;
      if (info.Length() < 1 || !NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmAlert^>(info[0])) {
        Nan::ThrowError(Nan::Error(NodeRT::Utils::NewString(L"Invalid arguments, no object provided, or given object could not be casted to requested type")));
        return;
      }

      ::Windows::Management::MdmAlert^ winRtInstance;
      try {
        winRtInstance = (::Windows::Management::MdmAlert^) NodeRT::Utils::GetObjectInstance(info[0]);
      } catch (Platform::Exception ^exception) {
        NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
        return;
      }

      info.GetReturnValue().Set(WrapMdmAlert(winRtInstance));
    }





    static void TypeGetter(Local<String> property, const Nan::PropertyCallbackInfo<v8::Value> &info) {
      HandleScope scope;

      if (!NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmAlert^>(info.This())) {
        return;
      }

      MdmAlert *wrapper = MdmAlert::Unwrap<MdmAlert>(info.This());

      try  {
        Platform::String^ result = wrapper->_instance->Type;
        info.GetReturnValue().Set(NodeRT::Utils::NewString(result->Data()));
        return;
      } catch (Platform::Exception ^exception) {
        NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
        return;
      }
    }
      
    static void TypeSetter(Local<String> property, Local<Value> value, const Nan::PropertyCallbackInfo<void> &info) {
      HandleScope scope;

      if (!value->IsString()) {
        Nan::ThrowError(Nan::Error(NodeRT::Utils::NewString(L"Value to set is of unexpected type")));
        return;
      }

      if (!NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmAlert^>(info.This())) {
        return;
      }

      MdmAlert *wrapper = MdmAlert::Unwrap<MdmAlert>(info.This());

      try {

        Platform::String^ winRtValue = ref new Platform::String(NodeRT::Utils::StringToWchar(v8::String::Value(v8::Isolate::GetCurrent(), value)));

        wrapper->_instance->Type = winRtValue;
      } catch (Platform::Exception ^exception) {
        NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
      }
    }
      
    static void TargetGetter(Local<String> property, const Nan::PropertyCallbackInfo<v8::Value> &info) {
      HandleScope scope;

      if (!NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmAlert^>(info.This())) {
        return;
      }

      MdmAlert *wrapper = MdmAlert::Unwrap<MdmAlert>(info.This());

      try  {
        Platform::String^ result = wrapper->_instance->Target;
        info.GetReturnValue().Set(NodeRT::Utils::NewString(result->Data()));
        return;
      } catch (Platform::Exception ^exception) {
        NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
        return;
      }
    }
      
    static void TargetSetter(Local<String> property, Local<Value> value, const Nan::PropertyCallbackInfo<void> &info) {
      HandleScope scope;

      if (!value->IsString()) {
        Nan::ThrowError(Nan::Error(NodeRT::Utils::NewString(L"Value to set is of unexpected type")));
        return;
      }

      if (!NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmAlert^>(info.This())) {
        return;
      }

      MdmAlert *wrapper = MdmAlert::Unwrap<MdmAlert>(info.This());

      try {

        Platform::String^ winRtValue = ref new Platform::String(NodeRT::Utils::StringToWchar(v8::String::Value(v8::Isolate::GetCurrent(), value)));

        wrapper->_instance->Target = winRtValue;
      } catch (Platform::Exception ^exception) {
        NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
      }
    }
      
    static void SourceGetter(Local<String> property, const Nan::PropertyCallbackInfo<v8::Value> &info) {
      HandleScope scope;

      if (!NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmAlert^>(info.This())) {
        return;
      }

      MdmAlert *wrapper = MdmAlert::Unwrap<MdmAlert>(info.This());

      try  {
        Platform::String^ result = wrapper->_instance->Source;
        info.GetReturnValue().Set(NodeRT::Utils::NewString(result->Data()));
        return;
      } catch (Platform::Exception ^exception) {
        NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
        return;
      }
    }
      
    static void SourceSetter(Local<String> property, Local<Value> value, const Nan::PropertyCallbackInfo<void> &info) {
      HandleScope scope;

      if (!value->IsString()) {
        Nan::ThrowError(Nan::Error(NodeRT::Utils::NewString(L"Value to set is of unexpected type")));
        return;
      }

      if (!NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmAlert^>(info.This())) {
        return;
      }

      MdmAlert *wrapper = MdmAlert::Unwrap<MdmAlert>(info.This());

      try {

        Platform::String^ winRtValue = ref new Platform::String(NodeRT::Utils::StringToWchar(v8::String::Value(v8::Isolate::GetCurrent(), value)));

        wrapper->_instance->Source = winRtValue;
      } catch (Platform::Exception ^exception) {
        NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
      }
    }
      
    static void MarkGetter(Local<String> property, const Nan::PropertyCallbackInfo<v8::Value> &info) {
      HandleScope scope;

      if (!NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmAlert^>(info.This())) {
        return;
      }

      MdmAlert *wrapper = MdmAlert::Unwrap<MdmAlert>(info.This());

      try  {
        ::Windows::Management::MdmAlertMark result = wrapper->_instance->Mark;
        info.GetReturnValue().Set(Nan::New<Integer>(static_cast<int>(result)));
        return;
      } catch (Platform::Exception ^exception) {
        NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
        return;
      }
    }
      
    static void MarkSetter(Local<String> property, Local<Value> value, const Nan::PropertyCallbackInfo<void> &info) {
      HandleScope scope;

      if (!value->IsInt32()) {
        Nan::ThrowError(Nan::Error(NodeRT::Utils::NewString(L"Value to set is of unexpected type")));
        return;
      }

      if (!NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmAlert^>(info.This())) {
        return;
      }

      MdmAlert *wrapper = MdmAlert::Unwrap<MdmAlert>(info.This());

      try {

        ::Windows::Management::MdmAlertMark winRtValue = static_cast<::Windows::Management::MdmAlertMark>(Nan::To<int32_t>(value).FromMaybe(0));

        wrapper->_instance->Mark = winRtValue;
      } catch (Platform::Exception ^exception) {
        NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
      }
    }
      
    static void FormatGetter(Local<String> property, const Nan::PropertyCallbackInfo<v8::Value> &info) {
      HandleScope scope;

      if (!NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmAlert^>(info.This())) {
        return;
      }

      MdmAlert *wrapper = MdmAlert::Unwrap<MdmAlert>(info.This());

      try  {
        ::Windows::Management::MdmAlertDataType result = wrapper->_instance->Format;
        info.GetReturnValue().Set(Nan::New<Integer>(static_cast<int>(result)));
        return;
      } catch (Platform::Exception ^exception) {
        NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
        return;
      }
    }
      
    static void FormatSetter(Local<String> property, Local<Value> value, const Nan::PropertyCallbackInfo<void> &info) {
      HandleScope scope;

      if (!value->IsInt32()) {
        Nan::ThrowError(Nan::Error(NodeRT::Utils::NewString(L"Value to set is of unexpected type")));
        return;
      }

      if (!NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmAlert^>(info.This())) {
        return;
      }

      MdmAlert *wrapper = MdmAlert::Unwrap<MdmAlert>(info.This());

      try {

        ::Windows::Management::MdmAlertDataType winRtValue = static_cast<::Windows::Management::MdmAlertDataType>(Nan::To<int32_t>(value).FromMaybe(0));

        wrapper->_instance->Format = winRtValue;
      } catch (Platform::Exception ^exception) {
        NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
      }
    }
      
    static void DataGetter(Local<String> property, const Nan::PropertyCallbackInfo<v8::Value> &info) {
      HandleScope scope;

      if (!NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmAlert^>(info.This())) {
        return;
      }

      MdmAlert *wrapper = MdmAlert::Unwrap<MdmAlert>(info.This());

      try  {
        Platform::String^ result = wrapper->_instance->Data;
        info.GetReturnValue().Set(NodeRT::Utils::NewString(result->Data()));
        return;
      } catch (Platform::Exception ^exception) {
        NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
        return;
      }
    }
      
    static void DataSetter(Local<String> property, Local<Value> value, const Nan::PropertyCallbackInfo<void> &info) {
      HandleScope scope;

      if (!value->IsString()) {
        Nan::ThrowError(Nan::Error(NodeRT::Utils::NewString(L"Value to set is of unexpected type")));
        return;
      }

      if (!NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmAlert^>(info.This())) {
        return;
      }

      MdmAlert *wrapper = MdmAlert::Unwrap<MdmAlert>(info.This());

      try {

        Platform::String^ winRtValue = ref new Platform::String(NodeRT::Utils::StringToWchar(v8::String::Value(v8::Isolate::GetCurrent(), value)));

        wrapper->_instance->Data = winRtValue;
      } catch (Platform::Exception ^exception) {
        NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
      }
    }
      
    static void StatusGetter(Local<String> property, const Nan::PropertyCallbackInfo<v8::Value> &info) {
      HandleScope scope;

      if (!NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmAlert^>(info.This())) {
        return;
      }

      MdmAlert *wrapper = MdmAlert::Unwrap<MdmAlert>(info.This());

      try  {
        unsigned int result = wrapper->_instance->Status;
        info.GetReturnValue().Set(Nan::New<Integer>(result));
        return;
      } catch (Platform::Exception ^exception) {
        NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
        return;
      }
    }
      


    private:
      ::Windows::Management::MdmAlert^ _instance;
      static Persistent<FunctionTemplate> s_constructorTemplate;

      friend v8::Local<v8::Value> WrapMdmAlert(::Windows::Management::MdmAlert^ wintRtInstance);
      friend ::Windows::Management::MdmAlert^ UnwrapMdmAlert(Local<Value> value);
  };

  Persistent<FunctionTemplate> MdmAlert::s_constructorTemplate;

  v8::Local<v8::Value> WrapMdmAlert(::Windows::Management::MdmAlert^ winRtInstance) {
    EscapableHandleScope scope;

    if (winRtInstance == nullptr) {
      return scope.Escape(Undefined());
    }

    Local<Value> opaqueWrapper = CreateOpaqueWrapper(winRtInstance);
    Local<Value> args[] = {opaqueWrapper};
    Local<FunctionTemplate> localRef = Nan::New<FunctionTemplate>(MdmAlert::s_constructorTemplate);
    return scope.Escape(Nan::NewInstance(Nan::GetFunction(localRef).ToLocalChecked(),_countof(args), args).ToLocalChecked());
  }

  ::Windows::Management::MdmAlert^ UnwrapMdmAlert(Local<Value> value) {
     return MdmAlert::Unwrap<MdmAlert>(Nan::To<Object>(value).ToLocalChecked())->_instance;
  }

  void InitMdmAlert(Local<Object> exports) {
    MdmAlert::Init(exports);
  }

  class MdmSession : public WrapperBase {
    public:
      
      static void Init(const Local<Object> exports) {
        HandleScope scope;

        Local<FunctionTemplate> localRef = Nan::New<FunctionTemplate>(New);
        s_constructorTemplate.Reset(localRef);
        localRef->SetClassName(Nan::New<String>("MdmSession").ToLocalChecked());
        localRef->InstanceTemplate()->SetInternalFieldCount(1);

        Local<Function> func;
        Local<FunctionTemplate> funcTemplate;

          
            Nan::SetPrototypeMethod(localRef, "delete", Delete);
          

          
            Nan::SetPrototypeMethod(localRef, "attachAsync", AttachAsync);
            Nan::SetPrototypeMethod(localRef, "startAsync", StartAsync);
          


          
            Nan::SetAccessor(localRef->PrototypeTemplate(), Nan::New<String>("alerts").ToLocalChecked(), AlertsGetter);
            Nan::SetAccessor(localRef->PrototypeTemplate(), Nan::New<String>("extendedError").ToLocalChecked(), ExtendedErrorGetter);
            Nan::SetAccessor(localRef->PrototypeTemplate(), Nan::New<String>("id").ToLocalChecked(), IdGetter);
            Nan::SetAccessor(localRef->PrototypeTemplate(), Nan::New<String>("state").ToLocalChecked(), StateGetter);

        Local<Object> constructor = Nan::To<Object>(Nan::GetFunction(localRef).ToLocalChecked()).ToLocalChecked();
        Nan::SetMethod(constructor, "castFrom", CastFrom);



        Nan::Set(exports, Nan::New<String>("MdmSession").ToLocalChecked(), constructor);
      }

      virtual ::Platform::Object^ GetObjectInstance() const override {
        return _instance;
      }

    private:

      MdmSession(::Windows::Management::MdmSession^ instance) {
        _instance = instance;
      }

      
    static void New(Nan::NAN_METHOD_ARGS_TYPE info) {
      HandleScope scope;

      Local<FunctionTemplate> localRef = Nan::New<FunctionTemplate>(s_constructorTemplate);

      // in case the constructor was called without the new operator
      if (!localRef->HasInstance(info.This())) {
        if (info.Length() > 0) {
          std::unique_ptr<Local<Value> []> constructorArgs(new Local<Value>[info.Length()]);

          Local<Value> *argsPtr = constructorArgs.get();
          for (int i = 0; i < info.Length(); i++) {
            argsPtr[i] = info[i];
          }

          MaybeLocal<Object> res = Nan::NewInstance(Nan::GetFunction(localRef).ToLocalChecked(), info.Length(), constructorArgs.get());
          if (res.IsEmpty()) {
            return;
          }

          info.GetReturnValue().Set(res.ToLocalChecked());
          return;
        } else {
          MaybeLocal<Object> res = Nan::NewInstance(Nan::GetFunction(localRef).ToLocalChecked(), info.Length(), nullptr);

          if (res.IsEmpty()) {
            return;
          }

          info.GetReturnValue().Set(res.ToLocalChecked());
          return;
        }
      }

      ::Windows::Management::MdmSession^ winRtInstance;


      if (info.Length() == 1 && OpaqueWrapper::IsOpaqueWrapper(info[0]) &&
        NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmSession^>(info[0])) {
        try {
          winRtInstance = (::Windows::Management::MdmSession^) NodeRT::Utils::GetObjectInstance(info[0]);
        } catch (Platform::Exception ^exception) {
          NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
          return;
        }
      }
 else {
        Nan::ThrowError(Nan::Error(NodeRT::Utils::NewString(L"Invalid arguments, no suitable constructor found")));
        return;
      }

      NodeRT::Utils::SetHiddenValue(info.This(), Nan::New<String>("__winRtInstance__").ToLocalChecked(), True());

      MdmSession *wrapperInstance = new MdmSession(winRtInstance);
      wrapperInstance->Wrap(info.This());

      info.GetReturnValue().Set(info.This());
    }


      
    static void CastFrom(Nan::NAN_METHOD_ARGS_TYPE info) {
      HandleScope scope;
      if (info.Length() < 1 || !NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmSession^>(info[0])) {
        Nan::ThrowError(Nan::Error(NodeRT::Utils::NewString(L"Invalid arguments, no object provided, or given object could not be casted to requested type")));
        return;
      }

      ::Windows::Management::MdmSession^ winRtInstance;
      try {
        winRtInstance = (::Windows::Management::MdmSession^) NodeRT::Utils::GetObjectInstance(info[0]);
      } catch (Platform::Exception ^exception) {
        NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
        return;
      }

      info.GetReturnValue().Set(WrapMdmSession(winRtInstance));
    }

    static void AttachAsync(Nan::NAN_METHOD_ARGS_TYPE info) {
      HandleScope scope;

      if (!NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmSession^>(info.This())) {
        return;
      }

      if (info.Length() == 0 || !info[info.Length() -1]->IsFunction()) {
        Nan::ThrowError(Nan::Error(NodeRT::Utils::NewString(L"Bad arguments: No callback was given")));
        return;
      }

      MdmSession *wrapper = MdmSession::Unwrap<MdmSession>(info.This());

      ::Windows::Foundation::IAsyncAction^ op;


      if (info.Length() == 1)
      {
        try
        {
          op = wrapper->_instance->AttachAsync();
        }
        catch (Platform::Exception ^exception)
        {
          NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
          return;
        }
      }
 else {
        Nan::ThrowError(Nan::Error(NodeRT::Utils::NewString(L"Bad arguments: no suitable overload found")));
        return;
      }

      auto opTask = create_task(op);
      uv_async_t* asyncToken = NodeUtils::Async::GetAsyncToken(info[info.Length() -1].As<Function>());

      opTask.then( [asyncToken] (task<void> t) {
        try {
          t.get();
          NodeUtils::Async::RunCallbackOnMain(asyncToken, [](NodeUtils::InvokeCallbackDelegate invokeCallback) {


            Local<Value> args[] = {Undefined()};


            invokeCallback(_countof(args), args);
          });
        } catch (Platform::Exception^ exception) {
          NodeUtils::Async::RunCallbackOnMain(asyncToken, [exception](NodeUtils::InvokeCallbackDelegate invokeCallback) {
            Local<Value> error = NodeRT::Utils::WinRtExceptionToJsError(exception);

            Local<Value> args[] = {error};
            invokeCallback(_countof(args), args);
          });
        }
      });
    }
    static void StartAsync(Nan::NAN_METHOD_ARGS_TYPE info) {
      HandleScope scope;

      if (!NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmSession^>(info.This())) {
        return;
      }

      if (info.Length() == 0 || !info[info.Length() -1]->IsFunction()) {
        Nan::ThrowError(Nan::Error(NodeRT::Utils::NewString(L"Bad arguments: No callback was given")));
        return;
      }

      MdmSession *wrapper = MdmSession::Unwrap<MdmSession>(info.This());

      ::Windows::Foundation::IAsyncAction^ op;


      if (info.Length() == 1)
      {
        try
        {
          op = wrapper->_instance->StartAsync();
        }
        catch (Platform::Exception ^exception)
        {
          NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
          return;
        }
      }
      else if (info.Length() == 2
        && (NodeRT::Utils::IsWinRtWrapperOf<::Windows::Foundation::Collections::IIterable<::Windows::Management::MdmAlert^>^>(info[0]) || info[0]->IsArray()))
      {
        try
        {
          ::Windows::Foundation::Collections::IIterable<::Windows::Management::MdmAlert^>^ arg0 = 
            [] (v8::Local<v8::Value> value) -> ::Windows::Foundation::Collections::IIterable<::Windows::Management::MdmAlert^>^
            {
              if (value->IsArray())
              {
                return NodeRT::Collections::JsArrayToWinrtVector<::Windows::Management::MdmAlert^>(value.As<Array>(), 
                 [](Local<Value> value) -> bool {
                   return NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmAlert^>(value);
                 },
                 [](Local<Value> value) -> ::Windows::Management::MdmAlert^ {
                   return UnwrapMdmAlert(value);
                 }
                );
              }
              else
              {
                return dynamic_cast<::Windows::Foundation::Collections::IIterable<::Windows::Management::MdmAlert^>^>(NodeRT::Utils::GetObjectInstance(value));
              }
            } (info[0]);
          
          op = wrapper->_instance->StartAsync(arg0);
        }
        catch (Platform::Exception ^exception)
        {
          NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
          return;
        }
      }
 else {
        Nan::ThrowError(Nan::Error(NodeRT::Utils::NewString(L"Bad arguments: no suitable overload found")));
        return;
      }

      auto opTask = create_task(op);
      uv_async_t* asyncToken = NodeUtils::Async::GetAsyncToken(info[info.Length() -1].As<Function>());

      opTask.then( [asyncToken] (task<void> t) {
        try {
          t.get();
          NodeUtils::Async::RunCallbackOnMain(asyncToken, [](NodeUtils::InvokeCallbackDelegate invokeCallback) {


            Local<Value> args[] = {Undefined()};


            invokeCallback(_countof(args), args);
          });
        } catch (Platform::Exception^ exception) {
          NodeUtils::Async::RunCallbackOnMain(asyncToken, [exception](NodeUtils::InvokeCallbackDelegate invokeCallback) {
            Local<Value> error = NodeRT::Utils::WinRtExceptionToJsError(exception);

            Local<Value> args[] = {error};
            invokeCallback(_countof(args), args);
          });
        }
      });
    }

    static void Delete(Nan::NAN_METHOD_ARGS_TYPE info) {
      HandleScope scope;

      if (!NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmSession^>(info.This())) {
        return;
      }

      MdmSession *wrapper = MdmSession::Unwrap<MdmSession>(info.This());

      if (info.Length() == 0)
      {
        try
        {
          wrapper->_instance->Delete();
          return;
        } catch (Platform::Exception ^exception) {
          NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
          return;
        }
      }
 else {
        Nan::ThrowError(Nan::Error(NodeRT::Utils::NewString(L"Bad arguments: no suitable overload found")));
        return;
      }
    }



    static void AlertsGetter(Local<String> property, const Nan::PropertyCallbackInfo<v8::Value> &info) {
      HandleScope scope;

      if (!NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmSession^>(info.This())) {
        return;
      }

      MdmSession *wrapper = MdmSession::Unwrap<MdmSession>(info.This());

      try  {
        ::Windows::Foundation::Collections::IVectorView<::Windows::Management::MdmAlert^>^ result = wrapper->_instance->Alerts;
        info.GetReturnValue().Set(NodeRT::Collections::VectorViewWrapper<::Windows::Management::MdmAlert^>::CreateVectorViewWrapper(result, 
            [](::Windows::Management::MdmAlert^ val) -> Local<Value> {
              return WrapMdmAlert(val);
            },
            [](Local<Value> value) -> bool {
              return NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmAlert^>(value);
            },
            [](Local<Value> value) -> ::Windows::Management::MdmAlert^ {
              return UnwrapMdmAlert(value);
            }
          ));
        return;
      } catch (Platform::Exception ^exception) {
        NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
        return;
      }
    }
      
    static void ExtendedErrorGetter(Local<String> property, const Nan::PropertyCallbackInfo<v8::Value> &info) {
      HandleScope scope;

      if (!NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmSession^>(info.This())) {
        return;
      }

      MdmSession *wrapper = MdmSession::Unwrap<MdmSession>(info.This());

      try  {
        ::Windows::Foundation::HResult result = wrapper->_instance->ExtendedError;
        info.GetReturnValue().Set(Nan::New<Integer>(result.Value));
        return;
      } catch (Platform::Exception ^exception) {
        NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
        return;
      }
    }
      
    static void IdGetter(Local<String> property, const Nan::PropertyCallbackInfo<v8::Value> &info) {
      HandleScope scope;

      if (!NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmSession^>(info.This())) {
        return;
      }

      MdmSession *wrapper = MdmSession::Unwrap<MdmSession>(info.This());

      try  {
        Platform::String^ result = wrapper->_instance->Id;
        info.GetReturnValue().Set(NodeRT::Utils::NewString(result->Data()));
        return;
      } catch (Platform::Exception ^exception) {
        NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
        return;
      }
    }
      
    static void StateGetter(Local<String> property, const Nan::PropertyCallbackInfo<v8::Value> &info) {
      HandleScope scope;

      if (!NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmSession^>(info.This())) {
        return;
      }

      MdmSession *wrapper = MdmSession::Unwrap<MdmSession>(info.This());

      try  {
        ::Windows::Management::MdmSessionState result = wrapper->_instance->State;
        info.GetReturnValue().Set(Nan::New<Integer>(static_cast<int>(result)));
        return;
      } catch (Platform::Exception ^exception) {
        NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
        return;
      }
    }
      


    private:
      ::Windows::Management::MdmSession^ _instance;
      static Persistent<FunctionTemplate> s_constructorTemplate;

      friend v8::Local<v8::Value> WrapMdmSession(::Windows::Management::MdmSession^ wintRtInstance);
      friend ::Windows::Management::MdmSession^ UnwrapMdmSession(Local<Value> value);
  };

  Persistent<FunctionTemplate> MdmSession::s_constructorTemplate;

  v8::Local<v8::Value> WrapMdmSession(::Windows::Management::MdmSession^ winRtInstance) {
    EscapableHandleScope scope;

    if (winRtInstance == nullptr) {
      return scope.Escape(Undefined());
    }

    Local<Value> opaqueWrapper = CreateOpaqueWrapper(winRtInstance);
    Local<Value> args[] = {opaqueWrapper};
    Local<FunctionTemplate> localRef = Nan::New<FunctionTemplate>(MdmSession::s_constructorTemplate);
    return scope.Escape(Nan::NewInstance(Nan::GetFunction(localRef).ToLocalChecked(),_countof(args), args).ToLocalChecked());
  }

  ::Windows::Management::MdmSession^ UnwrapMdmSession(Local<Value> value) {
     return MdmSession::Unwrap<MdmSession>(Nan::To<Object>(value).ToLocalChecked())->_instance;
  }

  void InitMdmSession(Local<Object> exports) {
    MdmSession::Init(exports);
  }

  class MdmSessionManager : public WrapperBase {
    public:
      
      static void Init(const Local<Object> exports) {
        HandleScope scope;

        Local<FunctionTemplate> localRef = Nan::New<FunctionTemplate>(New);
        s_constructorTemplate.Reset(localRef);
        localRef->SetClassName(Nan::New<String>("MdmSessionManager").ToLocalChecked());
        localRef->InstanceTemplate()->SetInternalFieldCount(1);






        Local<Object> constructor = Nan::To<Object>(Nan::GetFunction(localRef).ToLocalChecked()).ToLocalChecked();
        Nan::SetMethod(constructor, "castFrom", CastFrom);

        Nan::SetMethod(constructor, "tryCreateSession", TryCreateSession);
        Nan::SetMethod(constructor, "deleteSessionById", DeleteSessionById);
        Nan::SetMethod(constructor, "getSessionById", GetSessionById);
        Nan::SetAccessor(constructor, Nan::New<String>("sessionIds").ToLocalChecked(), SessionIdsGetter);


        Nan::Set(exports, Nan::New<String>("MdmSessionManager").ToLocalChecked(), constructor);
      }

      virtual ::Platform::Object^ GetObjectInstance() const override {
        return _instance;
      }

    private:

      MdmSessionManager(::Windows::Management::MdmSessionManager^ instance) {
        _instance = instance;
      }

      
    static void New(Nan::NAN_METHOD_ARGS_TYPE info) {
      HandleScope scope;

      Local<FunctionTemplate> localRef = Nan::New<FunctionTemplate>(s_constructorTemplate);

      // in case the constructor was called without the new operator
      if (!localRef->HasInstance(info.This())) {
        if (info.Length() > 0) {
          std::unique_ptr<Local<Value> []> constructorArgs(new Local<Value>[info.Length()]);

          Local<Value> *argsPtr = constructorArgs.get();
          for (int i = 0; i < info.Length(); i++) {
            argsPtr[i] = info[i];
          }

          MaybeLocal<Object> res = Nan::NewInstance(Nan::GetFunction(localRef).ToLocalChecked(), info.Length(), constructorArgs.get());
          if (res.IsEmpty()) {
            return;
          }

          info.GetReturnValue().Set(res.ToLocalChecked());
          return;
        } else {
          MaybeLocal<Object> res = Nan::NewInstance(Nan::GetFunction(localRef).ToLocalChecked(), info.Length(), nullptr);

          if (res.IsEmpty()) {
            return;
          }

          info.GetReturnValue().Set(res.ToLocalChecked());
          return;
        }
      }

      ::Windows::Management::MdmSessionManager^ winRtInstance;


      if (info.Length() == 1 && OpaqueWrapper::IsOpaqueWrapper(info[0]) &&
        NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmSessionManager^>(info[0])) {
        try {
          winRtInstance = (::Windows::Management::MdmSessionManager^) NodeRT::Utils::GetObjectInstance(info[0]);
        } catch (Platform::Exception ^exception) {
          NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
          return;
        }
      }
 else {
        Nan::ThrowError(Nan::Error(NodeRT::Utils::NewString(L"Invalid arguments, no suitable constructor found")));
        return;
      }

      NodeRT::Utils::SetHiddenValue(info.This(), Nan::New<String>("__winRtInstance__").ToLocalChecked(), True());

      MdmSessionManager *wrapperInstance = new MdmSessionManager(winRtInstance);
      wrapperInstance->Wrap(info.This());

      info.GetReturnValue().Set(info.This());
    }


      
    static void CastFrom(Nan::NAN_METHOD_ARGS_TYPE info) {
      HandleScope scope;
      if (info.Length() < 1 || !NodeRT::Utils::IsWinRtWrapperOf<::Windows::Management::MdmSessionManager^>(info[0])) {
        Nan::ThrowError(Nan::Error(NodeRT::Utils::NewString(L"Invalid arguments, no object provided, or given object could not be casted to requested type")));
        return;
      }

      ::Windows::Management::MdmSessionManager^ winRtInstance;
      try {
        winRtInstance = (::Windows::Management::MdmSessionManager^) NodeRT::Utils::GetObjectInstance(info[0]);
      } catch (Platform::Exception ^exception) {
        NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
        return;
      }

      info.GetReturnValue().Set(WrapMdmSessionManager(winRtInstance));
    }





    static void TryCreateSession(Nan::NAN_METHOD_ARGS_TYPE info) {
      HandleScope scope;

      if (info.Length() == 0)
      {
        try
        {
          ::Windows::Management::MdmSession^ result;
          result = ::Windows::Management::MdmSessionManager::TryCreateSession();
          info.GetReturnValue().Set(WrapMdmSession(result));
          return;
        }
        catch (Platform::Exception ^exception)
        {
          NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
          return;
        }
      }
 else  {
        Nan::ThrowError(Nan::Error(NodeRT::Utils::NewString(L"Bad arguments: no suitable overload found")));
        return;
      }
    }

    static void DeleteSessionById(Nan::NAN_METHOD_ARGS_TYPE info) {
      HandleScope scope;

      if (info.Length() == 1
        && info[0]->IsString())
      {
        try
        {
          Platform::String^ arg0 = ref new Platform::String(NodeRT::Utils::StringToWchar(v8::String::Value(v8::Isolate::GetCurrent(), info[0])));
          
          ::Windows::Management::MdmSessionManager::DeleteSessionById(arg0);
          return;
        }
        catch (Platform::Exception ^exception)
        {
          NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
          return;
        }
      }
 else  {
        Nan::ThrowError(Nan::Error(NodeRT::Utils::NewString(L"Bad arguments: no suitable overload found")));
        return;
      }
    }

    static void GetSessionById(Nan::NAN_METHOD_ARGS_TYPE info) {
      HandleScope scope;

      if (info.Length() == 1
        && info[0]->IsString())
      {
        try
        {
          Platform::String^ arg0 = ref new Platform::String(NodeRT::Utils::StringToWchar(v8::String::Value(v8::Isolate::GetCurrent(), info[0])));
          
          ::Windows::Management::MdmSession^ result;
          result = ::Windows::Management::MdmSessionManager::GetSessionById(arg0);
          info.GetReturnValue().Set(WrapMdmSession(result));
          return;
        }
        catch (Platform::Exception ^exception)
        {
          NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
          return;
        }
      }
 else  {
        Nan::ThrowError(Nan::Error(NodeRT::Utils::NewString(L"Bad arguments: no suitable overload found")));
        return;
      }
    }



    static void SessionIdsGetter(Local<String> property, const Nan::PropertyCallbackInfo<v8::Value> &info) {
      HandleScope scope;

      try
      {
        ::Windows::Foundation::Collections::IVectorView<::Platform::String^>^ result = ::Windows::Management::MdmSessionManager::SessionIds;
        info.GetReturnValue().Set(NodeRT::Collections::VectorViewWrapper<::Platform::String^>::CreateVectorViewWrapper(result, 
            [](::Platform::String^ val) -> Local<Value> {
              return NodeRT::Utils::NewString(val->Data());
            },
            [](Local<Value> value) -> bool {
              return value->IsString();
            },
            [](Local<Value> value) -> ::Platform::String^ {
              return ref new Platform::String(NodeRT::Utils::StringToWchar(v8::String::Value(v8::Isolate::GetCurrent(), value)));
            }
          ));
        return;
      } catch (Platform::Exception ^exception) {
        NodeRT::Utils::ThrowWinRtExceptionInJs(exception);
        return;
      }
    }
      

    private:
      ::Windows::Management::MdmSessionManager^ _instance;
      static Persistent<FunctionTemplate> s_constructorTemplate;

      friend v8::Local<v8::Value> WrapMdmSessionManager(::Windows::Management::MdmSessionManager^ wintRtInstance);
      friend ::Windows::Management::MdmSessionManager^ UnwrapMdmSessionManager(Local<Value> value);
  };

  Persistent<FunctionTemplate> MdmSessionManager::s_constructorTemplate;

  v8::Local<v8::Value> WrapMdmSessionManager(::Windows::Management::MdmSessionManager^ winRtInstance) {
    EscapableHandleScope scope;

    if (winRtInstance == nullptr) {
      return scope.Escape(Undefined());
    }

    Local<Value> opaqueWrapper = CreateOpaqueWrapper(winRtInstance);
    Local<Value> args[] = {opaqueWrapper};
    Local<FunctionTemplate> localRef = Nan::New<FunctionTemplate>(MdmSessionManager::s_constructorTemplate);
    return scope.Escape(Nan::NewInstance(Nan::GetFunction(localRef).ToLocalChecked(),_countof(args), args).ToLocalChecked());
  }

  ::Windows::Management::MdmSessionManager^ UnwrapMdmSessionManager(Local<Value> value) {
     return MdmSessionManager::Unwrap<MdmSessionManager>(Nan::To<Object>(value).ToLocalChecked())->_instance;
  }

  void InitMdmSessionManager(Local<Object> exports) {
    MdmSessionManager::Init(exports);
  }


} } } 

NAN_MODULE_INIT(init) {
  // We ignore failures for now since it probably means that
  // the initialization already happened for STA, and that's cool

  CoInitializeEx(nullptr, COINIT_MULTITHREADED);

  /*
  if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED))) {
    Nan::ThrowError(Nan::Error(NodeRT::Utils::NewString(L"error in CoInitializeEx()")));
    return;
  }
  */

      NodeRT::Windows::Management::InitMdmAlertMarkEnum(target);
      NodeRT::Windows::Management::InitMdmAlertDataTypeEnum(target);
      NodeRT::Windows::Management::InitMdmSessionStateEnum(target);
      NodeRT::Windows::Management::InitMdmAlert(target);
      NodeRT::Windows::Management::InitMdmSession(target);
      NodeRT::Windows::Management::InitMdmSessionManager(target);


  NodeRT::Utils::RegisterNameSpace("Windows.Management", target);
}



NODE_MODULE(binding, init)
