/* Memory mapped files */

#include <stdio.h>
#include <sys/mman.h>
#include <string.h>

#include <caml/mlvalues.h>
#include <caml/fail.h>
#include <caml/memory.h>
#include <caml/alloc.h>
#include <caml/custom.h>

typedef struct {
  void *mmd_start;
  size_t mmd_length;
  off_t mmd_offset;
} mmd;

#define mmd_val(v) (*((mmd *) Data_custom_val(v)))

static void mmd_finalize(value mmdv)
{
  if(munmap(mmd_val(mmdv).mmd_start, mmd_val(mmdv).mmd_length)) {
    caml_failwith("munmap");
  }
}

static struct custom_operations mmd_ops = {
  "memory_mapped_string",
  mmd_finalize,
  custom_compare_default,
  custom_hash_default,
  custom_serialize_default,
  custom_deserialize_default
};

value caml_mmap_unmap(value mmdv)
{
  CAMLparam1(mmdv);

  if(mmd_val(mmdv).mmd_start == MAP_FAILED) caml_failwith("not mapped");
  mmd_finalize(mmdv);
  CAMLreturn(Val_unit);
}

value caml_mmap_length(value mmdv)
{
  CAMLparam1(mmdv);
  CAMLlocal1(lengthv);

  if(mmd_val(mmdv).mmd_start == MAP_FAILED) caml_failwith("not mapped");
  lengthv = copy_int64(mmd_val(mmdv).mmd_length);
  CAMLreturn(lengthv);
}

value caml_mmap_offset(value mmdv)
{
  CAMLparam1(mmdv);
  CAMLlocal1(offsetv);

  if(mmd_val(mmdv).mmd_start == MAP_FAILED) caml_failwith("not mapped");
  offsetv = copy_int64(mmd_val(mmdv).mmd_offset);
  CAMLreturn(offsetv);
}

value caml_mmap_map(value writev, value copyv, value offsetv, value lengthv, value fdv)
{
  CAMLparam5(writev,copyv,offsetv,lengthv,fdv);
  CAMLlocal1(mmdv);
  size_t length;
  off_t offset;
  void *start;
  int write, copy;
  int prot, flags;
  int fd;

  fd = Int_val(fdv);
  write = Int_val(writev);
  copy = Int_val(copyv);
  length = Int64_val(lengthv);
  offset = Int64_val(offsetv);

  if(length < 0) caml_failwith("mmap: bad length");
  if(offset < 0) caml_failwith("mmap: bad offset");

  prot = PROT_READ;
  if(write) prot |= PROT_WRITE;
  flags = write ? MAP_PRIVATE : MAP_SHARED;

  start = mmap(0, length, prot, flags, fd, offset);

  if(start == MAP_FAILED) caml_failwith("mmap");
  mmdv = alloc_custom(&mmd_ops, sizeof(mmd), length, 1048576);
  mmd_val(mmdv).mmd_start = start;
  mmd_val(mmdv).mmd_length = length;
  mmd_val(mmdv).mmd_offset = offset;
  CAMLreturn(mmdv);
}

value caml_mmap_sub(value mmdv, value offsetv, value lengthv)
{
  CAMLparam3(mmdv, offsetv, lengthv);
  off_t offset;
  size_t length;
  CAMLlocal1(uv);

  if(mmd_val(mmdv).mmd_start == MAP_FAILED) caml_failwith("not mapped");

  offset = Int64_val(offsetv);
  length = Int_val(lengthv);

  if(offset >= 0 &&
     offset + length <= mmd_val(mmdv).mmd_length) {
    uv = caml_alloc_string(length);
    memcpy(String_val(uv), ((char *) mmd_val(mmdv).mmd_start) + offset, length);
  } else {
    caml_invalid_argument("bad offset or length");
  }

  CAMLreturn(uv);
}

value caml_mmap_blit(value mmdv, value targetv, value stringv, value offsetv, value lengthv)
{
  CAMLparam5(mmdv, targetv, stringv, offsetv, lengthv);
  off_t target;
  int offset;
  size_t length;
  char *string;

  if(mmd_val(mmdv).mmd_start == MAP_FAILED) caml_failwith("not mapped");

  target = Int64_val(targetv);
  string = String_val(stringv);
  offset = Int_val(offsetv);
  length = Int_val(lengthv);

  if(target >= 0 &&
     target + length <= mmd_val(mmdv).mmd_length &&
     offset >= 0 &&
     offset + length <= string_length(stringv)) {
    memcpy(((char *) mmd_val(mmdv).mmd_start) + target, string + offset, length);
  } else {
    caml_invalid_argument("bad offset or length");
  }

  CAMLreturn(Val_unit);
}

#if 0
value caml_blit_mmd(value mmdv, value uv, value offsetv)
{
  CAMLparam3(mmdv, uv, offsetv);
  void *u;
  size_t offset;
  size_t length;

  offset = Int_val(offsetv);
  u = String_val(uv);
  length = string_length(uv);


  if(offset >= 0 &&
     offset + length <= mmd_val(mmdv).mmd_length) {
    memcpy(mmd_val(mmdv).mmd_start, u, length);
  } else {
    caml_invalid_argument("bad offset or length");
  }

  CAMLreturn(Val_unit);
}

typedef int (*iiti)(int, int);
typedef int (*sti)(char *, int);

value caml_exec_mmd_iiti(value mmdv, value offsetv, value xv, value yv)
{
  CAMLparam4(mmdv, offsetv, xv, yv);
  size_t offset;
  int x, y;
  int z;

  x = Int_val(xv);
  y = Int_val(yv);
  offset = Int_val(offsetv);

  if(offset >= 0 && offset < mmd_val(mmdv).mmd_length) {
    iiti f;
    f = (iiti)(mmd_val(mmdv).mmd_start + offset);
    z = f(x,y);
  } else {
    caml_invalid_argument("bad offset or length");
  }

  CAMLreturn(Val_int(z));
}

value caml_exec_mmd_sti(value mmdv, value offsetv, value wordv)
{
  CAMLparam3(mmdv, offsetv, wordv);
  size_t offset;
  int z;

  offset = Int_val(offsetv);

  if(offset >= 0 && offset < mmd_val(mmdv).mmd_length) {
    sti f;
    f = (sti)(mmd_val(mmdv).mmd_start + offset);
    z = f(String_val(wordv), string_length(wordv));
  } else {
    caml_invalid_argument("bad offset or length");
  }

  CAMLreturn(Val_int(z));
}
#endif
