#define _FILE_OFFSET_BITS 64

#include "fuse_attr.h"
#include "fuse_dirent.h"
#include "fuse_direntplus.h"
#include "fuse_dirents.h"
#include "fuse_entry.h"
#include "stat_utils.h"

#include <dirent.h>
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define DEFAULT_SIZE (1024 * 16)

static
uint64_t
dirent_exact_namelen(const struct dirent *d_)
{
#ifdef _D_EXACT_NAMELEN
  return _D_EXACT_NAMELEN(d_);
#elif defined _DIRENT_HAVE_D_NAMLEN
  return d_->d_namlen;
#else
  return strlen(d_->d_name);
#endif
}

static
uint64_t
dirent_alloc_namelen(const struct dirent *d_)
{
  return (dirent_exact_namelen(d_) + 1);
}

static
uint64_t
align_uint64_t(uint64_t v_)
{
  return ((v_ + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1));
}

static
uint64_t
fuse_dirent_size(const uint64_t namelen_)
{
  uint64_t rv;

  rv  = offsetof(fuse_dirent_t,name);
  rv += namelen_;
  rv  = align_uint64_t(rv);

  return rv;
}

static
uint64_t
fuse_direntplus_size(const uint64_t namelen_)
{
  uint64_t rv;

  rv  = offsetof(fuse_direntplus_t,dirent.name);
  rv += namelen_;
  rv  = align_uint64_t(rv);

  return rv;
}

static
int
fuse_dirents_resize(fuse_dirents_t *d_,
                    uint64_t        size_)
{
  void *p;

  if((d_->data_len + size_) >= d_->buf_len)
    {
      p = realloc(d_->buf,(d_->buf_len * 2));
      if(p == NULL)
        return -errno;

      d_->buf      = p;
      d_->buf_len *= 2;
    }

  return 0;
}

static
void*
fuse_dirents_alloc(fuse_dirents_t *d_,
                   uint64_t        size_)
{
  int rv;
  fuse_dirent_t *d;

  rv = fuse_dirents_resize(d_,size_);
  if(rv)
    return NULL;

  d = (fuse_dirent_t*)&d_->buf[d_->data_len];

  d_->data_len += size_;

  return d;
}

static
void
fuse_dirents_fill_attr(fuse_attr_t       *attr_,
                       const struct stat *st_)
{
  attr_->ino       = st_->st_ino;
  attr_->size      = st_->st_size;
  attr_->blocks    = st_->st_blocks;
  attr_->atime     = st_->st_atime;
  attr_->mtime     = st_->st_mtime;
  attr_->ctime     = st_->st_ctime;
  attr_->atimensec = ST_ATIM_NSEC(st_);
  attr_->mtimensec = ST_MTIM_NSEC(st_);
  attr_->ctimensec = ST_CTIM_NSEC(st_);
  attr_->mode      = st_->st_mode;
  attr_->nlink     = st_->st_nlink;
  attr_->uid       = st_->st_uid;
  attr_->gid       = st_->st_gid;
  attr_->rdev      = st_->st_rdev;
  attr_->blksize   = st_->st_blksize;
}

fuse_dirent_t*
fuse_dirent_next(fuse_dirent_t *cur_)
{
  char *buf;

  buf  = (char*)cur_;
  buf += fuse_dirent_size(cur_->namelen);

  return (fuse_dirent_t*)buf;
}

fuse_direntplus_t*
fuse_direntplus_next(fuse_direntplus_t *cur_)
{
  char *buf;

  buf  = (char*)cur_;
  buf += fuse_direntplus_size(cur_->dirent.namelen);

  return (fuse_direntplus_t*)buf;
}

fuse_dirent_t*
fuse_dirent_find(fuse_dirents_t *d_,
                 const uint64_t  ino_)
{
  fuse_dirent_t *cur;
  fuse_dirent_t *end;

  if(d_->type != NORMAL)
    return NULL;

  cur = (fuse_dirent_t*)&d_->buf[0];
  end = (fuse_dirent_t*)&d_->buf[d_->data_len];
  while(cur < end)
    {
      if(cur->ino == ino_)
        return cur;

      cur = fuse_dirent_next(cur);
    }

  return NULL;
}

fuse_direntplus_t*
fuse_direntplus_find(fuse_dirents_t *d_,
                     const uint64_t  ino_)
{
  fuse_direntplus_t *cur;
  fuse_direntplus_t *end;

  if(d_->type != PLUS)
    return NULL;

  cur = (fuse_direntplus_t*)&d_->buf[0];
  end = (fuse_direntplus_t*)&d_->buf[d_->data_len];
  while(cur < end)
    {
      if(cur->dirent.ino == ino_)
        return cur;

      cur = fuse_direntplus_next(cur);
    }

  return NULL;
}

void*
fuse_dirents_find(fuse_dirents_t *d_,
                  const uint64_t  ino_)
{
  switch(d_->type)
    {
    default:
    case UNSET:
      return NULL;
    case NORMAL:
      return fuse_dirent_find(d_,ino_);
    case PLUS:
      return fuse_direntplus_find(d_,ino_);
    }
}

int
fuse_dirents_convert_plus2normal(fuse_dirents_t *d_)
{
  int rv;
  uint64_t size;
  fuse_dirent_t *d;
  fuse_dirents_t normal;
  fuse_direntplus_t *cur;
  fuse_direntplus_t *end;

  rv = fuse_dirents_init(&normal);
  if(rv < 0)
    return rv;

  cur = (fuse_direntplus_t*)&d_->buf[0];
  end = (fuse_direntplus_t*)&d_->buf[d_->data_len];
  while(cur < end)
    {
      size = fuse_dirent_size(cur->dirent.namelen);
      d = fuse_dirents_alloc(&normal,size);
      if(d == NULL)
        return -ENOMEM;

      memcpy(d,&cur->dirent,size);
      d->off = normal.data_len;;

      cur = fuse_direntplus_next(cur);
    }

  fuse_dirents_free(d_);

  normal.type = NORMAL;
  *d_ = normal;

  return 0;
}

int
fuse_dirents_add(fuse_dirents_t      *d_,
                 const struct dirent *dirent_)
{
  uint64_t size;
  uint64_t namelen;
  fuse_dirent_t *d;

  switch(d_->type)
    {
    case UNSET:
      d_->type = NORMAL;
      break;
    case NORMAL:
      break;
    case PLUS:
      return -EINVAL;
    }

  namelen = dirent_alloc_namelen(dirent_);
  size    = fuse_dirent_size(namelen);

  d = fuse_dirents_alloc(d_,size);
  if(d == NULL)
    return -ENOMEM;

  d->ino     = dirent_->d_ino;
  d->off     = d_->data_len;
  d->namelen = namelen;
  d->type    = dirent_->d_type;
  memcpy(d->name,dirent_->d_name,namelen);

  return 0;
}

int
fuse_dirents_add_plus(fuse_dirents_t      *d_,
                      const struct dirent *dirent_,
                      const fuse_entry_t  *entry_,
                      const struct stat   *st_)
{
  uint64_t size;
  uint64_t namelen;
  fuse_direntplus_t *d;

  switch(d_->type)
    {
    case UNSET:
      d_->type = PLUS;
      break;
    case NORMAL:
      return -EINVAL;
    case PLUS:
      break;
    }

  namelen = dirent_alloc_namelen(dirent_);
  size    = fuse_direntplus_size(namelen);

  d = fuse_dirents_alloc(d_,size);
  if(d == NULL)
    return -ENOMEM;

  d->dirent.ino     = dirent_->d_ino;
  d->dirent.off     = d_->data_len;
  d->dirent.namelen = namelen;
  d->dirent.type    = dirent_->d_type;
  memcpy(d->dirent.name,dirent_->d_name,namelen);

  d->entry = *entry_;

  fuse_dirents_fill_attr(&d->attr,st_);

  return 0;
}

#ifdef __linux__
struct linux_dirent64
{
  uint64_t d_ino;
  int64_t  d_off;
  uint16_t d_reclen;
  uint8_t  d_type;
  char     d_name[];
};

int
fuse_dirents_add_linux(fuse_dirents_t              *d_,
                       const struct linux_dirent64 *dirent_)
{
  uint64_t size;
  uint64_t namelen;
  fuse_dirent_t *d;

  switch(d_->type)
    {
    case UNSET:
      d_->type = NORMAL;
      break;
    case NORMAL:
      break;
    case PLUS:
      return -EINVAL;
    }

  namelen = (dirent_->d_reclen - offsetof(struct linux_dirent64,d_name));
  size    = fuse_dirent_size(namelen);

  d = fuse_dirents_alloc(d_,size);
  if(d == NULL)
    return -ENOMEM;

  d->ino     = dirent_->d_ino;
  d->off     = d_->data_len;
  d->namelen = namelen;
  d->type    = dirent_->d_type;
  memcpy(d->name,dirent_->d_name,namelen);

  return 0;
}

int
fuse_dirents_add_linux_plus(fuse_dirents_t              *d_,
                            const struct linux_dirent64 *dirent_,
                            const fuse_entry_t          *entry_,
                            const struct stat           *st_)
{
  uint64_t size;
  uint64_t namelen;
  fuse_direntplus_t *d;

  switch(d_->type)
    {
    case UNSET:
      d_->type = PLUS;
      break;
    case NORMAL:
      return -EINVAL;
    case PLUS:
      break;
    }

  namelen = (dirent_->d_reclen - offsetof(struct linux_dirent64,d_name));
  size    = fuse_direntplus_size(namelen);

  d = fuse_dirents_alloc(d_,size);
  if(d == NULL)
    return -ENOMEM;

  d->dirent.ino     = dirent_->d_ino;
  d->dirent.off     = d_->data_len;
  d->dirent.namelen = namelen;
  d->dirent.type    = dirent_->d_type;
  memcpy(d->dirent.name,dirent_->d_name,namelen);

  d->entry = *entry_;

  fuse_dirents_fill_attr(&d->attr,st_);

  return 0;
}
#endif

void
fuse_dirents_reset(fuse_dirents_t *d_)
{
  d_->data_len = 0;
  d_->type     = UNSET;
}

int
fuse_dirents_init(fuse_dirents_t *d_)
{
  void *buf;

  buf = calloc(DEFAULT_SIZE,1);
  if(buf == NULL)
    return -ENOMEM;

  d_->buf      = buf;
  d_->buf_len  = DEFAULT_SIZE;
  d_->data_len = 0;
  d_->type     = UNSET;

  return 0;
}

void
fuse_dirents_free(fuse_dirents_t *d_)
{
  d_->buf_len  = 0;
  d_->data_len = 0;
  d_->type     = UNSET;
  free(d_->buf);
}