#include "llvm/Config/config.h"

#include "../RemoteTargetMessage.h"
#include <assert.h>
#include <map>
#include <stdint.h>
#include <string>
#include <vector>

using namespace llvm;

class LLIChildTarget {
public:
  ~LLIChildTarget(); // OS-specific destructor
  void initialize();
  LLIMessageType waitForIncomingMessage();
  void handleMessage(LLIMessageType messageType);

private:
  // Incoming message handlers
  void handleAllocateSpace();
  void handleLoadSection(bool IsCode);
  void handleExecute();
  void handleTerminate();

  // Outgoing message handlers
  void sendChildActive();
  void sendAllocationResult(uint64_t Addr);
  void sendLoadComplete();
  void sendExecutionComplete(uint64_t Result);

  // OS-specific functions
  void initializeConnection();
  int WriteBytes(const void *Data, size_t Size);
  int ReadBytes(void *Data, size_t Size);
  uint64_t allocate(uint32_t Alignment, uint32_t Size);
  void makeSectionExecutable(uint64_t Addr, uint32_t Size);
  void InvalidateInstructionCache(const void *Addr, size_t Len);
  void releaseMemory(uint64_t Addr, uint32_t Size);

  // Store a map of allocated buffers to sizes.
  typedef std::map<uint64_t, uint32_t> AllocMapType;
  AllocMapType m_AllocatedBufferMap;

  // Communication handles (OS-specific)
  void *ConnectionData;
};

int main() {
  LLIChildTarget  ThisChild;
  ThisChild.initialize();
  LLIMessageType MsgType;
  do {
    MsgType = ThisChild.waitForIncomingMessage();
    ThisChild.handleMessage(MsgType);
  } while (MsgType != LLI_Terminate &&
           MsgType != LLI_Error);
  return 0;
}

// Public methods
void LLIChildTarget::initialize() {
  initializeConnection();
  sendChildActive();
}

LLIMessageType LLIChildTarget::waitForIncomingMessage() {
  int32_t MsgType = -1;
  if (ReadBytes(&MsgType, 4) > 0)
    return (LLIMessageType)MsgType;
  return LLI_Error;
}

void LLIChildTarget::handleMessage(LLIMessageType messageType) {
  switch (messageType) {
    case LLI_AllocateSpace:
      handleAllocateSpace();
      break;
    case LLI_LoadCodeSection:
      handleLoadSection(true);
      break;
    case LLI_LoadDataSection:
      handleLoadSection(false);
      break;
    case LLI_Execute:
      handleExecute();
      break;
    case LLI_Terminate:
      handleTerminate();
      break;
    default:
      // FIXME: Handle error!
      break;
  }
}

// Incoming message handlers
void LLIChildTarget::handleAllocateSpace() {
  // Read and verify the message data size.
  uint32_t DataSize;
  int rc = ReadBytes(&DataSize, 4);
  assert(rc == 4);
  assert(DataSize == 8);

  // Read the message arguments.
  uint32_t Alignment;
  uint32_t AllocSize;
  rc = ReadBytes(&Alignment, 4);
  assert(rc == 4);
  rc = ReadBytes(&AllocSize, 4);
  assert(rc == 4);

  // Allocate the memory.
  uint64_t Addr = allocate(Alignment, AllocSize);

  // Send AllocationResult message.
  sendAllocationResult(Addr);
}

void LLIChildTarget::handleLoadSection(bool IsCode) {
  // Read the message data size.
  uint32_t DataSize;
  int rc = ReadBytes(&DataSize, 4);
  assert(rc == 4);

  // Read the target load address.
  uint64_t Addr;
  rc = ReadBytes(&Addr, 8);
  assert(rc == 8);

  size_t BufferSize = DataSize - 8;

  // FIXME: Verify that this is in allocated space

  // Read section data into previously allocated buffer
  rc = ReadBytes((void*)Addr, DataSize - 8);
  assert(rc == (int)(BufferSize));

  // If IsCode, mark memory executable
  if (IsCode)
    makeSectionExecutable(Addr, BufferSize);

  // Send MarkLoadComplete message.
  sendLoadComplete();
}

void LLIChildTarget::handleExecute() {
  // Read the message data size.
  uint32_t DataSize;
  int rc = ReadBytes(&DataSize, 4);
  assert(rc == 4);
  assert(DataSize == 8);

  // Read the target address.
  uint64_t Addr;
  rc = ReadBytes(&Addr, 8);
  assert(rc == 8);

  // Call function
  int Result;
  int (*fn)(void) = (int(*)(void))Addr;
  Result = fn();

  // Send ExecutionResult message.
  sendExecutionComplete((int64_t)Result);
}

void LLIChildTarget::handleTerminate() {
  // Release all allocated memory
  AllocMapType::iterator Begin = m_AllocatedBufferMap.begin();
  AllocMapType::iterator End = m_AllocatedBufferMap.end();
  for (AllocMapType::iterator It = Begin; It != End; ++It) {
    releaseMemory(It->first, It->second);
  }
  m_AllocatedBufferMap.clear();
}

// Outgoing message handlers
void LLIChildTarget::sendChildActive() {
  // Write the message type.
  uint32_t MsgType = (uint32_t)LLI_ChildActive;
  int rc = WriteBytes(&MsgType, 4);
  assert(rc == 4);

  // Write the data size.
  uint32_t DataSize = 0;
  rc = WriteBytes(&DataSize, 4);
  assert(rc == 4);
}

void LLIChildTarget::sendAllocationResult(uint64_t Addr) {
  // Write the message type.
  uint32_t MsgType = (uint32_t)LLI_AllocationResult;
  int rc = WriteBytes(&MsgType, 4);
  assert(rc == 4);

  // Write the data size.
  uint32_t DataSize = 8;
  rc = WriteBytes(&DataSize, 4);
  assert(rc == 4);

  // Write the allocated address.
  rc = WriteBytes(&Addr, 8);
  assert(rc == 8);
}

void LLIChildTarget::sendLoadComplete() {
  // Write the message type.
  uint32_t MsgType = (uint32_t)LLI_LoadComplete;
  int rc = WriteBytes(&MsgType, 4);
  assert(rc == 4);

  // Write the data size.
  uint32_t DataSize = 0;
  rc = WriteBytes(&DataSize, 4);
  assert(rc == 4);
}

void LLIChildTarget::sendExecutionComplete(uint64_t Result) {
  // Write the message type.
  uint32_t MsgType = (uint32_t)LLI_ExecutionResult;
  int rc = WriteBytes(&MsgType, 4);
  assert(rc == 4);


  // Write the data size.
  uint32_t DataSize = 8;
  rc = WriteBytes(&DataSize, 4);
  assert(rc == 4);

  // Write the result.
  rc = WriteBytes(&Result, 8);
  assert(rc == 8);
}

#ifdef LLVM_ON_UNIX
#include "Unix/ChildTarget.inc"
#endif

#ifdef LLVM_ON_WIN32
#include "Windows/ChildTarget.inc"
#endif
