#pragma once
#ifndef MEMORY_H
#define MEMORY_H
#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <TlHelp32.h>

class memory {
public:
  memory();
  ~memory();
  std::vector<MEMORY_BASIC_INFORMATION> getRegions(HANDLE hProcess);

  template <class dataType>
  dataType readMemory(HANDLE hProcess, DWORD64 address) {
    dataType cRead;
    ReadProcessMemory(hProcess, (LPVOID)address, &cRead, sizeof(dataType), NULL);
    return cRead;
  }

  BOOL readBuffer(HANDLE hProcess, DWORD64 address, SIZE_T size, char* dstBuffer) {
    return ReadProcessMemory(hProcess, (LPVOID)address, dstBuffer, size, NULL);
  }

  char readChar(HANDLE hProcess, DWORD64 address) {
    char value;
    ReadProcessMemory(hProcess, (LPVOID)address, &value, sizeof(char), NULL);
    return value;
	}

  BOOL readString(HANDLE hProcess, DWORD64 address, std::string* pString) {
    int length = 0;
    int BATCH_SIZE = 256;
    char* data = (char*) malloc(sizeof(char) * BATCH_SIZE);
    while (length <= BATCH_SIZE * 4096) {
      BOOL success = readBuffer(hProcess, address + length, BATCH_SIZE, data);

      if (success == 0) {
        free(data);
        break;
      }

      for (const char* ptr = data; ptr - data < BATCH_SIZE; ++ptr) {
        if (*ptr == '\0') {
          length += ptr - data + 1;

          char* buffer = (char*) malloc(length);
          readBuffer(hProcess, address, length, buffer);

          *pString = std::string(buffer);

          free(data);
          free(buffer);

          return TRUE;
        }
      }

      length += BATCH_SIZE;
    }

    return FALSE;
  }

  template <class dataType>
  void writeMemory(HANDLE hProcess, DWORD64 address, dataType value) {
    WriteProcessMemory(hProcess, (LPVOID)address, &value, sizeof(dataType), NULL);
  }

  template <class dataType>
  void writeMemory(HANDLE hProcess, DWORD64 address, dataType value, SIZE_T size) {
	  LPVOID buffer = value;

	  if (typeid(dataType) != typeid(char*)) {
		  buffer = &value;
	  }

	  WriteProcessMemory(hProcess, (LPVOID)address, buffer, size, NULL);
  }

  // Write String, Method 1: Utf8Value is converted to string, get pointer and length from string
  // template <>
  // void writeMemory<std::string>(HANDLE hProcess, DWORD address, std::string value) {
  //  WriteProcessMemory(hProcess, (LPVOID)address, value.c_str(), value.length(), NULL);
  // }

  // Write String, Method 2: get pointer and length from Utf8Value directly
  void writeMemory(HANDLE hProcess, DWORD64 address, char* value, SIZE_T size) {
    WriteProcessMemory(hProcess, (LPVOID)address, value, size, NULL);
  }
};
#endif
#pragma once