// hello.cc
#include <iostream>
#include <node.h>
#include "./nan/nan.h"
#include <windows.h>
#include <tlhelp32.h>
#include <string>
#include <assert.h>
#include <stdlib.h>



union byteData {
	uintptr_t num;
	char buf[sizeof(uintptr_t)]; //32 or 64 bit compatible
};

class AddonData {
public:
	AddonData(v8::Isolate* isolate, v8::Local<v8::Object> exports) :
		call_count(0) {
		// Link the existence of this object instance to the existence of exports.
		exports_.Reset(isolate, exports);
		exports_.SetWeak(this, DeleteMe, v8::WeakCallbackType::kParameter);
	}

	// Per-addon data.
	int call_count;

private:
	// Method to call when "exports" is about to be garbage-collected.
	static void DeleteMe(const v8::WeakCallbackInfo<AddonData>& info) {
		delete info.GetParameter();
	}

	// Weak handle to the "exports" object. An instance of this class will be
	// destroyed along with the exports object to which it is weakly bound.
	v8::Global<v8::Object> exports_;
};


namespace demo {

	using v8::Exception;
	using v8::FunctionCallbackInfo;
	using v8::Isolate;
	using v8::Local;
	using v8::NewStringType;
	using v8::Number;
	using v8::Object;
	using v8::String;
	using v8::Value;
	using v8::Context;
	using node::AddEnvironmentCleanupHook;


	void Method(const FunctionCallbackInfo<Value>& args) {
		Isolate* isolate = args.GetIsolate();
		args.GetReturnValue().Set(String::NewFromUtf8(isolate, "world", NewStringType::kNormal).ToLocalChecked());
	};



	//Open process
	void OpenProcessJS(const FunctionCallbackInfo<Value>& args) {
		Isolate* isolate = args.GetIsolate();

		// Check the number of arguments passed.
		if (args.Length() < 3) {
			// Throw an Error that is passed back to JavaScript
			isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong number of arguments", NewStringType::kNormal).ToLocalChecked()));
			return;
		}

		// Check the argument types
		if (!args[0]->IsNumber() || !args[1]->IsNumber() || !args[2]->IsNumber()) {
			isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong arguments", NewStringType::kNormal).ToLocalChecked()));
			return;
		};

		DWORD pAccess = args[0].As<Number>()->Value();
		BOOL pInheritHandle = args[1].As<Number>()->Value();
		DWORD pID = args[2].As<Number>()->Value();
		HANDLE processHandle = OpenProcess(pAccess, pInheritHandle, pID);

		if (!processHandle) {
			isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "OpenProcess failed!", NewStringType::kNormal).ToLocalChecked()));
			return;
		};

		
		uint32_t x = static_cast<int>(reinterpret_cast<std::uintptr_t>(processHandle));
		args.GetReturnValue().Set(x);
		
	};



	//Get module base address
	void dwGetModuleBaseAddress(const FunctionCallbackInfo<Value>& args) {
		Isolate* isolate = args.GetIsolate();
		v8::Local<Context> context = (isolate->GetCurrentContext());

		// Check the number of arguments passed.
		if (args.Length() < 3) {
			// Throw an Error that is passed back to JavaScript
			isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong number of arguments", NewStringType::kNormal).ToLocalChecked()));
			return;
		}

		// Check the argument types
		if (!args[0]->IsNumber() || !args[1]->IsString() || !args[2]->IsObject()) {
			isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong arguments", NewStringType::kNormal).ToLocalChecked()));
			return;
		};

		DWORD dwProcessIdentifier = args[0].As<Number>()->Value();
		//v8::String *str = *(args[1].As<String>());
		v8::String::Utf8Value str(isolate, args[1]);
		std::string cppStr(*str);

		const char *lpszModuleName = (const char*)cppStr.c_str();

		//std::cout << cppStr.c_str() << std::endl;
		
		
		
		HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessIdentifier);
		uintptr_t dwModuleBaseAddress = 0;
		if (hSnapshot != INVALID_HANDLE_VALUE)
		{
			MODULEENTRY32 ModuleEntry32 = { 0 };
			ModuleEntry32.dwSize = sizeof(MODULEENTRY32);
			if (Module32First(hSnapshot, &ModuleEntry32))
			{
				do
				{
					if (strcmp(ModuleEntry32.szModule, lpszModuleName) == 0)
					{
						dwModuleBaseAddress = (uintptr_t)ModuleEntry32.modBaseAddr;
						break;
					}
				} while (Module32Next(hSnapshot, &ModuleEntry32));
			}
			CloseHandle(hSnapshot);
		}

		//std::cout << dwModuleBaseAddress << std::endl;

		//Buffer
		char* buffer = (char*)node::Buffer::Data(args[2]->ToObject(context).ToLocalChecked());
		
		byteData bytes;
		bytes.num = dwModuleBaseAddress;

		//Copy
		for (int i = 0; i < 8; i++) {
			buffer[i] = bytes.buf[i];
		};

		//args.GetReturnValue().Set()
	};




	void CloseHandleJS(const FunctionCallbackInfo<Value>& args) {
		Isolate* isolate = args.GetIsolate();
		v8::Local<Context> context = (isolate->GetCurrentContext());

		// Check the number of arguments passed.
		if (args.Length() < 1) {
			// Throw an Error that is passed back to JavaScript
			isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong number of arguments", NewStringType::kNormal).ToLocalChecked()));
			return;
		};

		// Check the argument types
		if (!args[0]->IsNumber()) {
			isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong arguments", NewStringType::kNormal).ToLocalChecked()));
			return;
		};

		//To uint32_t
		v8::Maybe<uint32_t> m32 = (args[0].As<Number>()->Uint32Value(context));
		uint32_t p;
		m32.To(&p);

		HANDLE pHandle = (HANDLE)p;

		BOOL res = CloseHandle(pHandle);

		args.GetReturnValue().Set(res);
	};



	void ReadMemoryJS(const FunctionCallbackInfo<Value>& args) {
		Isolate* isolate = args.GetIsolate();
		v8::Local<Context> context = (isolate->GetCurrentContext());


		// Check the number of arguments passed.
		if (args.Length() < 4) {
			// Throw an Error that is passed back to JavaScript
			isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong number of arguments", NewStringType::kNormal).ToLocalChecked()));
			return;
		};

		// Check the argument types
		if (!args[0]->IsNumber() || !args[1]->IsNumber() || !args[2]->IsObject() || !args[3]->IsNumber()) {
			isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong arguments", NewStringType::kNormal).ToLocalChecked()));
			return;
		};

		//Address - arg 0
		v8::Maybe<int64_t> d32 = args[0]->IntegerValue(context);
		uintptr_t address = d32.ToChecked();


		//Handle - arg 1
		v8::Maybe<uint32_t> handl = args[1]->Uint32Value(context);
		HANDLE pHandle = (HANDLE)handl.ToChecked();
	
		//Buffer
		char* buffer = (char*)node::Buffer::Data(args[2]->ToObject(context).ToLocalChecked());

		uint32_t bufLength = args[3]->Uint32Value(context).ToChecked();

		if (!ReadProcessMemory(pHandle, (LPVOID)address, buffer, bufLength, NULL)) {
			std::cout << GetLastError() << std::endl;
			isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Read failed!", NewStringType::kNormal).ToLocalChecked()));
			return;
		};

	};


	//Cleanup
	static void sanity_check(void*) {
		assert(1 == 1); //Yes...
	};



	//Init - exports
	//void Initialize(Local<Object> exports, Local<Value> module, Local<Context> context) {
	NODE_MODULE_INIT(Local<Object> exports, Local<Value> module, Local<Context> context){
		/*Isolate* isolate = context->GetIsolate();
	    NODE_SET_METHOD(exports, "hello", Method);
	    NODE_SET_METHOD(exports, "OpenProcess", OpenProcessJS);
	    NODE_SET_METHOD(exports, "GetModuleBaseAddress", dwGetModuleBaseAddress);
	    NODE_SET_METHOD(exports, "CloseHandle", CloseHandleJS);
	    NODE_SET_METHOD(exports, "ReadProcessMemory", ReadMemoryJS);
		//AddEnvironmentCleanupHook(isolate, sanity_check, nullptr);*/
		Isolate* isolate = context->GetIsolate();
		// Create a new instance of AddonData for this instance of the addon.
		AddonData* data = new AddonData(isolate, exports);
		// Wrap the data in a v8::External so we can pass it to the method we expose.
		Local<v8::External> external = v8::External::New(isolate, data);

		// Expose the method "Method" to JavaScript, and make sure it receives the
		// per-addon-instance data we created above by passing `external` as the
		// third parameter to the FunctionTemplate constructor.
		exports->Set(context,
			String::NewFromUtf8(isolate, "method", NewStringType::kNormal)
			.ToLocalChecked(),
			v8::FunctionTemplate::New(isolate, Method, external)
			->GetFunction(context).ToLocalChecked()).FromJust();

		exports->Set(context,
			String::NewFromUtf8(isolate, "OpenProcess", NewStringType::kNormal)
			.ToLocalChecked(),
			v8::FunctionTemplate::New(isolate, OpenProcessJS, external)
			->GetFunction(context).ToLocalChecked()).FromJust();

		exports->Set(context,
			String::NewFromUtf8(isolate, "GetModuleBaseAddress", NewStringType::kNormal)
			.ToLocalChecked(),
			v8::FunctionTemplate::New(isolate, dwGetModuleBaseAddress, external)
			->GetFunction(context).ToLocalChecked()).FromJust();

		exports->Set(context,
			String::NewFromUtf8(isolate, "CloseHandle", NewStringType::kNormal)
			.ToLocalChecked(),
			v8::FunctionTemplate::New(isolate, CloseHandleJS, external)
			->GetFunction(context).ToLocalChecked()).FromJust();

		exports->Set(context,
			String::NewFromUtf8(isolate, "ReadProcessMemory", NewStringType::kNormal)
			.ToLocalChecked(),
			v8::FunctionTemplate::New(isolate, ReadMemoryJS, external)
			->GetFunction(context).ToLocalChecked()).FromJust();

		AddEnvironmentCleanupHook(isolate, sanity_check, isolate);
	};


	//NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)

};