#define _GNU_SOURCE

#include <config.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdarg.h>

#include "misc.h"

int
map_open(Map *m,const char *s,int new) {

  int flags;
  struct stat ss;

  if (!s)
    return 0;

  flags=new ? O_CREAT|O_RDWR|O_TRUNC : O_RDONLY;

  if ((m->l=open(s,flags,0700))==-1) {
    err("Can't open %s as %s\n",s,new ? "new file" : "existing file");
    return 0;
  }
  m->ro=new ? 0 : 1;

  if (stat(s,&ss)) {
    err("Can't stat %s\n",s);
    return 0;
  }

  m->size=ss.st_size;

  if (!new && !m->size) {
    err("Cannot map 0 size file %s read-only\n",s);
    return 0;
  }

  m->n=strdup(s);
  return 1;

}

int
map_close(Map *m) {

  if (m->m1 && msync(m->m1,m->me-m->m1,MS_ASYNC | MS_INVALIDATE)) {
    err("Can't msync %s\n",m->n);
    return 0;
  }

  if (m->m1 && munmap(m->m1,m->me-m->m1)) {
    err("Can't munmap %s\n",m->n);
    return 0;
  }

  if (!m->ro && m->l)
    if (ftruncate(m->l,m->size)) {
      err("ftruncate error\n");
      return 0;
    }

  if (m->l && close(m->l)) {
    err("Can't close %s\n",m->n);
    return 0;
  }

  if (m->n)
    free((void *)m->n);

  memset(m,0,sizeof(*m));

  return 1;

}


  
static __inline__ int
map_resize(Map *m,unsigned size) {

  unsigned i;

  i=m->m-m->m1;
  if ((m->m1=(mremap(m->m1,m->me-m->m1,size,MREMAP_MAYMOVE))) ==
      (char *)-1) {
    err("Cannot remap %s %p %u %u\n",m->n,m->m1,m->me-m->m1,size);
    return 0;
  }

  m->me=m->m1+size;
  i=i>size ? size : i;
  m->m=m->m1+i;

  return 1;

}


static __inline__ int
map_remap(Map *m,unsigned o1,unsigned ns) {

  unsigned i,j,o;
  void *m1;

  j=m->l && !m->ro ? MAP_SHARED : MAP_PRIVATE;
  if (!m->l)
    j|=MAP_ANONYMOUS;
  if ((m1=mmap(0,ns,PROT_READ|PROT_WRITE,j,m->l,o1))==(void *)-1) {
    err("Can't mmap %u %u %s\n",o1,ns,m->n);
    return 0;
  }

  if (!m->l && m->m1) {

    i=m->o>o1 ? m->o : o1;
    j=m->o+m->me-m->m1;
    j=j<o1+ns ? j : o1+ns;
    
    if (j>i)
      memcpy(m1,m->m1+i-m->o,j-i);
    
  }
  
  if (m->m1 && munmap(m->m1,m->me-m->m1)) {
    err("Can't munmap %p %u %s\n",m->m1,m->me-m->m1,m->n);
    return 0;
  }

  o=m->m-m->m1+m->o;

  m->m1=m1;
  m->o=o1;
  m->me=m->m1+ns;

  if (o>=o1 && o<o1+ns)
    m->m=m->m1+o-m->o;
  else if (o>=o1+ns)
    m->m=m->me;
  else
    m->m=m->m1;
  
  return 1;

}


static __inline__ int
map_file_expand(Map *m,unsigned size) {

  char c=0;
  
  if (!m->l || m->size>=size) 
    return 1;

  if (lseek(m->l,size-sizeof(c),SEEK_SET)==-1) 
    return 0;

  if (write(m->l,&c,sizeof(c))!=sizeof(c)) 
    return 0; 

  m->size=size;
  
  return 1;

}

int
map_get(Map *m,unsigned o1,unsigned o2) {

  if (!map_file_expand(m,o2)) {
    o2=m->size;
    if (o1>=m->size) {
      if (o1>m->size)
	err("Requested map %u %u for file %s off boundary %u\n",
	    o1,o2,m->n,m->size);
      return 0;
    }
  }
  
  if (m->m1 && m->o==o1) 
    return map_resize(m,o2-o1);
  else
    return map_remap(m,o1,o2-o1);
  
}


int
map_write(Map *m,const void *v,unsigned size) {

  if (size>m->me-m->m) 
    if (!map_get(m,m->o,m->o+m->me-m->m1+size))
      return 0;

  memcpy(m->m,v,size);
  m->m+=size;
  
  return size;

}


int
map_fgets(Map *m,FILE *f) {

  const unsigned ll=80;
  unsigned j;
  char *c;
  void *v1;

  if ((j=m->me-m->m)<ll) {
    if (!map_get(m,m->o,m->o+m->me-m->m1+ll))
      return 0;
    j=m->me-m->m;
  }

  for (c=NULL,v1=m->m;fgets(m->m,j,f) && !(c=memchr(m->m,10,j));) {
    
    if (!map_get(m,m->o,m->o+m->me-m->m1+ll))
      return 0;
    m->m=memchr(m->m,0,m->me-m->m);
    j=m->me-m->m;

  }

  if (c) {
    *c++=0;
    m->m=c;
  }

  return m->m-v1;

}

static __inline__ int
map_vsprintf(Map *m,const char *fmt,va_list args) {

  const unsigned ll=80;
  int j;

#if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1
    while ((j=vsnprintf(m->m,m->me-m->m,fmt,args))>=m->me-m->m)
#else
    while ((j=vsnprintf(m->m,m->me-m->m,fmt,args))<=0)
#endif
      if (!map_get(m,m->o,m->o+m->me-m->m1+ll))
	return 0;

  return j;

}

char *
map_temp_str(const char *fmt,...) {

  int j;
  va_list args;
  static Map m;

  m.m=m.m1;
  va_start(args,fmt);
  if (!(j=map_vsprintf(&m,fmt,args)))
    err("Can't write in map_temp_str\n");
  va_end(args);

  return j ? m.m1 : NULL;

}
  
