#ifndef _STRLIST_H_
#define _STRLIST_H_

#include "Platform.h"
#include <stdio.h>
#include <stdlib.h> // for gcc
#ifndef _WIN32
#  include <sys/mman.h>
#  include <sys/stat.h>
#  include <fcntl.h>
#  include <unistd.h>
#endif

class IGrowBuf
{
  public:
    virtual int add(const void *data, int len)=0;
    virtual void resize(int newlen)=0;
    virtual int getlen()=0;
    virtual void *get()=0;
};

class IMMap
{
  public:
    virtual void resize(int newlen)=0;
    virtual int getsize()=0;
    virtual void *get(int offset, int size)=0;
    virtual void *get(int offset, int *size)=0;
    virtual void *getmore(int offset, int *size)=0;
    virtual void release()=0;
    virtual void release(void *view, int size)=0;
    virtual void clear()=0;
    virtual void setro(BOOL bRO)=0;
    virtual void flush(int num)=0;
};

class GrowBuf : public IGrowBuf
{
  public:
    GrowBuf() { m_alloc=m_used=m_zero=0; m_s=NULL; m_bs=32768; }
    virtual ~GrowBuf() { free(m_s); }

    void set_zeroing(int zero) { m_zero=zero; }

    int add(const void *data, int len)
    {
      if (len<=0) return 0;
      resize(m_used+len);
      memcpy((char*)m_s+m_used-len,data,len);
      return m_used-len;
    }

    void resize(int newlen)
    {
      int os=m_alloc;
      int ou=m_used;
      m_used=newlen;
      if (newlen > m_alloc)
      {
        void *n;
        m_alloc = newlen*2 + m_bs;
        n = realloc(m_s, m_alloc);
        if (!n)
        {
          extern FILE *g_output;
          extern int g_display_errors;
          if (g_display_errors)
          {
            fprintf(g_output,"\nack! realloc(%d) failed, trying malloc(%d)!\n",m_alloc,newlen);
            fflush(g_output);
          }
          m_alloc=newlen; // try to malloc the minimum needed
          n=malloc(m_alloc);
          if (!n)
          {
            extern void quit();
            if (g_display_errors)
            {
              fprintf(g_output,"\nInternal compiler error #12345: GrowBuf realloc/malloc(%d) failed.\n",m_alloc);
              fflush(g_output);
            }
            quit();
          }
          memcpy(n,m_s,min(newlen,os));
          free(m_s);
        }
        m_s=n;
      }
      if (m_zero && m_used > ou)
        memset((char*)m_s + ou, 0, m_used - ou);
      if (!m_used && m_alloc > 2*m_bs) // only free if you resize to 0 and we're > 64k
      {
        m_alloc=0;
        free(m_s);
        m_s=NULL;
      }
    }

    int getlen() { return m_used; }
    void *get() { return m_s; }

  private:
    void *m_s;
    int m_alloc;
    int m_used;
    int m_zero;

  protected:
    int m_bs;
};

class TinyGrowBuf : public GrowBuf {
  public:
    TinyGrowBuf() : GrowBuf() { m_bs=1024; }
};

class StringList
{
public:
  StringList() { }
  ~StringList() { }

  int add(const char *str, int case_sensitive)
  {
    int a=find(str,case_sensitive);
    if (a >= 0 && case_sensitive!=-1) return a;
    return gr.add(str,strlen(str)+1);
  }

  // use 2 for case sensitive end-of-string matches too
  int find(const char *str, int case_sensitive, int *idx=NULL) // returns -1 if not found
  {
    char *s=(char*)gr.get();
    int ml=gr.getlen();
    int offs=0;
    if (idx) *idx=0;
    while (offs < ml)
    {
      if ((case_sensitive && !strcmp(s+offs,str)) ||
          (!case_sensitive && !stricmp(s+offs,str)))
      {
        return offs;
      }
      if (case_sensitive==2 &&
          strlen(str) < strlen(s+offs) &&  // check for end of string
          !strcmp(s+offs+strlen(s+offs)-strlen(str),str))
      {
        return offs+strlen(s+offs)-strlen(str);
      }
      offs+=strlen(s+offs)+1;
      if (idx) (*idx)++;
    }
    return -1;
  }

  void delbypos(int pos)
  {
    char *s=(char*)gr.get();
    int len=strlen(s+pos)+1;
    if (pos+len < gr.getlen()) memcpy(s+pos,s+pos+len,gr.getlen()-(pos+len));
    gr.resize(gr.getlen()-len);
  }

  int idx2pos(int idx)
  {
    char *s=(char*)gr.get();
    int offs=0;
    int cnt=0;
    if (idx>=0) while (offs < gr.getlen())
    {
      if (cnt++ == idx) return offs;
      offs+=strlen(s+offs)+1;
    }
    return -1;
  }

  int getnum() {
    char *s=(char*)gr.get();
    int ml=gr.getlen();
    int offs=0;
    int idx=0;
    while (offs < ml)
    {
      offs+=strlen(s+offs)+1;
      idx++;
    }
    return idx;
  }

  char *get() { return (char*)gr.get(); }
  int getlen() { return gr.getlen(); }
private:
  GrowBuf gr;
};

template <class T>
class SortedStringList
{
  public:
    SortedStringList() { }
    ~SortedStringList()
    {
      T *s=(T*)gr.get();
      int num=gr.getlen()/sizeof(T);

      for (int i=0; i<num; i++) {
        if (s[i].name)
          free(s[i].name);
      }
    }

    // returns -1 when name already exists and pos if added
    int add(const char *name, int case_sensitive=0)
    {
      T newstruct={0,};
      int pos=find(name,case_sensitive,1);
      if (pos==-1) return -1;
      newstruct.name=(char*)malloc(strlen(name)+1);
      if (!newstruct.name)
      {
        extern FILE *g_output;
        extern int g_display_errors;
        extern void quit();
        if (g_display_errors)
        {
          fprintf(g_output,"\nInternal compiler error #12345: GrowBuf realloc/malloc(%d) failed.\n",strlen(name)+1);
          fflush(g_output);
        }
        quit();
      }
      strcpy(newstruct.name,name);
      gr.add(&newstruct,sizeof(T));

      T *s=(T*)gr.get();
      memmove(s+pos+1,s+pos,gr.getlen()-((pos+1)*sizeof(T)));
      memcpy(s+pos,&newstruct,sizeof(T));

      return pos;
    }

    // returns -1 if not found, position if found
    // if returnbestpos=1 returns -1 if found, best pos to insert if not found
    int find(const char *str, int case_sensitive=0, int returnbestpos=0)
    {
      T *data=(T *)gr.get();
      int ul=gr.getlen()/sizeof(T);
      int ll=0;
      int nextpos=(ul+ll)/2;

      while (ul > ll)
      {
        int res;
        if (case_sensitive)
          res=strcmp(str, data[nextpos].name);
        else
          res=stricmp(str, data[nextpos].name);
        if (res==0) return returnbestpos ? -1 : nextpos;
        if (res<0) ul=nextpos;
        else ll=nextpos+1;
        nextpos=(ul+ll)/2;
      }

      return returnbestpos ? nextpos : -1;
    }

    // returns 0 on success, 1 otherwise
    int del(const char *str, int case_sensitive=0)
    {
      int pos=find(str, case_sensitive);
      if (pos==-1) return 1;

      delbypos(pos);

      return 0;
    }

    void delbypos(int pos)
    {
      T *db=(T *)gr.get();
      free(db[pos].name);
      memmove(db+pos,db+pos+1,gr.getlen()-(pos*sizeof(T))-sizeof(T));
      gr.resize(gr.getlen()-sizeof(T));
    }

  protected:
    TinyGrowBuf gr;
};

#define mymin(x, y) ((x < y) ? x : y)

template <class T>
class SortedStringListND // no delete - can be placed in GrowBuf
{
  public:
    SortedStringListND() { }
    ~SortedStringListND() { }

    // returns -1 when name already exists and pos if added
    int add(const char *name, int case_sensitive=0, int alwaysreturnpos=0)
    {
      int where;
      T newstruct={0,};
      int pos=find(name,-1,case_sensitive,1,&where);
      if (pos==-1) return alwaysreturnpos ? where : -1;
      newstruct.name=strings.add(name,strlen(name)+1);

      gr.add(&newstruct,sizeof(T));
      T *s=(T*)gr.get();
      memmove(s+pos+1,s+pos,gr.getlen()-((pos+1)*sizeof(T)));
      memcpy(s+pos,&newstruct,sizeof(T));

      return pos;
    }

    // returns -1 if not found, position if found
    // if returnbestpos=1 returns -1 if found, best pos to insert if not found
    // if n_chars equal to -1 all string is tested
    int find(const char *str, int n_chars=-1, int case_sensitive=0, int returnbestpos=0, int *where=0)
    {
      T *data=(T *)gr.get();
      int ul=gr.getlen()/sizeof(T);
      int ll=0;
      int nextpos=(ul+ll)/2;

      while (ul > ll)
      {
        int res;
        const char *pCurr = (char*)strings.get() + data[nextpos].name;
        if (n_chars < 0)
        {
          if (case_sensitive)
            res = strcmp(str, pCurr);
          else
            res = stricmp(str, pCurr);
        }
        else
        {
          if (case_sensitive)
            res=strncmp(str, pCurr, mymin((unsigned int) n_chars, strlen(pCurr)));
          else
            res=strnicmp(str, pCurr, mymin((unsigned int) n_chars, strlen(pCurr)));
          if (res == 0 && n_chars != -1 && (unsigned int) n_chars != strlen(pCurr))
            res = n_chars - strlen(pCurr);
        }

        if (res==0)
        {
          if (where) *where = nextpos;
          return returnbestpos ? (case_sensitive!=-1 ? -1 : nextpos) : nextpos;
        }
        if (res<0) ul=nextpos;
        else ll=nextpos+1;
        nextpos=(ul+ll)/2;
      }

      return returnbestpos ? nextpos : -1;
    }

  protected:
    TinyGrowBuf gr;
    GrowBuf strings;
};

struct define {
  char *name;
  char *value;
};

class DefineList : public SortedStringList<struct define>
{
  public:
    DefineList() { }
    ~DefineList()
    {
      struct define *s=(struct define*)gr.get();
      int num=gr.getlen()/sizeof(struct define);

      for (int i=0; i<num; i++) {
        free(s[i].value);
      }
    }

    int add(const char *name, const char *value="")
    {
      int pos=SortedStringList<struct define>::add(name);
      if (pos == -1)
      {
        return 1;
      }

      char **newvalue=&(((struct define*)gr.get())[pos].value);
      *newvalue=(char*)malloc(strlen(value)+1);
      if (!(*newvalue))
      {
        extern FILE *g_output;
        extern int g_display_errors;
        extern void quit();
        if (g_display_errors)
        {
          fprintf(g_output,"\nInternal compiler error #12345: GrowBuf realloc/malloc(%d) failed.\n",strlen(value)+1);
          fflush(g_output);
        }
        quit();
      }
      strcpy(*newvalue,value);
      return 0;
    }

    char *find(const char *name)
    {
      int v=SortedStringList<struct define>::find(name);
      if (v==-1)
      {
        return NULL;
      }
      return ((struct define*)gr.get())[v].value;
    }

    // returns 0 on success, 1 otherwise
    int del(const char *str)
    {
      int pos=SortedStringList<struct define>::find(str);
      if (pos==-1) return 1;

      struct define *db=(struct define *)gr.get();
      free(db[pos].value);
      delbypos(pos);

      return 0;
    }

    int getnum()
    {
      return gr.getlen()/sizeof(define);
    }

    char *getname(int num)
    {
      if ((unsigned int)getnum() <= (unsigned int)num)
        return 0;
      return ((struct define*)gr.get())[num].name;
    }

    char *getvalue(int num)
    {
      if ((unsigned int)getnum() <= (unsigned int)num)
        return 0;
      return ((struct define*)gr.get())[num].value;
    }
};

struct string_t {
  int name;
};

class FastStringList : public SortedStringListND<struct string_t>
{
  public:
    FastStringList() { }
    ~FastStringList() { }

    int add(const char *name, int case_sensitive=0)
    {
      int pos = SortedStringListND<struct string_t>::add(name, case_sensitive);
      if (pos == -1) return -1;
      return ((struct string_t*)gr.get())[pos].name;
    }

    char *get()
    {
      return (char*)strings.get();
    }

    int getlen()
    {
      return strings.getlen();
    }

    int getnum()
    {
      return gr.getlen()/sizeof(struct string_t);
    }
};

class MMapFile : public IMMap
{
  public:
    MMapFile()
    {
#ifdef _WIN32
      m_hFile = INVALID_HANDLE_VALUE;
      m_hFileMap = NULL;
#else
      m_hFile = NULL;
      m_hFileDesc = -1;
#endif

      m_pView = NULL;
      m_iSize = 0;
      m_bReadOnly = FALSE;
      m_bTempHandle = FALSE;

      if (!m_iAllocationGranularity)
      {
#ifdef _WIN32
        SYSTEM_INFO si;
        GetSystemInfo(&si);
        m_iAllocationGranularity = (int) si.dwAllocationGranularity;
#else
        m_iAllocationGranularity = getpagesize();
#endif
      }
    }

    virtual ~MMapFile()
    {
      clear();
    }

    void clear()
    {
      release();

#ifdef _WIN32
      if (m_hFileMap)
        CloseHandle(m_hFileMap);
      if (m_bTempHandle && m_hFile)
        CloseHandle(m_hFile);

      m_hFileMap = 0;
#else
      if (m_bTempHandle && m_hFile)
        fclose(m_hFile);
#endif
    }

    void setro(BOOL bRO)
    {
      m_bReadOnly = bRO;
    }

#ifdef _WIN32
    int setfile(HANDLE hFile, DWORD dwSize)
#else
    int setfile(int hFile, DWORD dwSize)
#endif
    {
      clear();

#ifdef _WIN32
      m_hFile = hFile;
#else
      m_hFileDesc = hFile;
#endif
      m_bTempHandle = FALSE;

#ifdef _WIN32
      if (m_hFile == INVALID_HANDLE_VALUE)
#else
      if (m_hFileDesc == -1)
#endif
        return 0;

      m_iSize = (int) dwSize;

      if (m_iSize <= 0)
        return 0;

#ifdef _WIN32
      m_hFileMap = CreateFileMapping(m_hFile, NULL, PAGE_READONLY, 0, m_iSize, NULL);

      if (!m_hFileMap)
        return 0;
#endif

      m_bReadOnly = TRUE;

      return 1;
    }

    void resize(int newsize)
    {
      release();

      if (newsize > m_iSize)
      {
#ifdef _WIN32
        if (m_hFileMap)
          CloseHandle(m_hFileMap);

        m_hFileMap = 0;
#endif

        m_iSize = newsize;

#ifdef _WIN32
        if (m_hFile == INVALID_HANDLE_VALUE)
        {
          char buf[MAX_PATH], buf2[MAX_PATH];

          GetTempPath(MAX_PATH, buf);
          GetTempFileName(buf, "nsd", 0, buf2);

          m_hFile = CreateFile(
            buf2,
            GENERIC_READ | GENERIC_WRITE,
            0,
            NULL,
            CREATE_ALWAYS,
            FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_SEQUENTIAL_SCAN,
            NULL
          );

          m_bTempHandle = TRUE;
        }

        if (m_hFile != INVALID_HANDLE_VALUE)
        {
          m_hFileMap = CreateFileMapping(
            m_hFile,
            NULL,
            m_bReadOnly ? PAGE_READONLY : PAGE_READWRITE,
            0,
            m_iSize,
            NULL
          );
        }
#else
        if (m_hFile == NULL)
        {
          m_hFile = tmpfile();
          if (m_hFile != NULL)
          {
            m_hFileDesc = fileno(m_hFile);
            m_bTempHandle = TRUE;
          }
        }

        // resize
        if (m_hFileDesc != -1)
        {
          unsigned char c = 0;

          if (lseek(m_hFileDesc, m_iSize, SEEK_SET) != (off_t)-1)
          {
            if (read(m_hFileDesc, &c, 1) != -1)
            {
              if (lseek(m_hFileDesc, m_iSize, SEEK_SET) != (off_t)-1)
              {
                if (write(m_hFileDesc, &c, 1) != -1)
                {
                  return; // no errors
                }
              }
            }
          }
        }

        m_hFileDesc = -1; // some error occured, bail
#endif

#ifdef _WIN32
        if (!m_hFileMap)
#else
        if (m_hFileDesc == -1)
#endif
        {
          extern FILE *g_output;
          extern void quit(); extern int g_display_errors;
          if (g_display_errors)
          {
            fprintf(g_output,"\nInternal compiler error #12345: error creating mmap the size of %d.\n", m_iSize);
            fflush(g_output);
          }
          quit();
        }
      }
    }

    int getsize()
    {
      return m_iSize;
    }

    void *get(int offset, int size)
    {
      return get(offset, &size);
    }

    void *get(int offset, int *sizep)
    {
      if (!sizep)
        return NULL;

      if (m_pView)
        release();

      int size = *sizep;

      if (!m_iSize || offset + size > m_iSize)
      {
        extern FILE *g_output;
        extern void quit(); extern int g_display_errors;
        if (g_display_errors) 
        {
          fprintf(g_output,"\nInternal compiler error #12345: error mmapping file (%d, %d) is out of range.\n", offset, size);
          fflush(g_output);
        }
        quit();
      }

      // fix offset
      int alignedoffset = offset - (offset % m_iAllocationGranularity);
      size += offset - alignedoffset;

#ifdef _WIN32
      m_pView = MapViewOfFile(m_hFileMap, m_bReadOnly ? FILE_MAP_READ : FILE_MAP_WRITE, 0, alignedoffset, size);
#else
      m_pView = mmap(0, size, m_bReadOnly ? PROT_READ : PROT_READ | PROT_WRITE, MAP_SHARED, m_hFileDesc, alignedoffset);
      m_iMappedSize = *sizep = size;
#endif

#ifdef _WIN32
      if (!m_pView)
#else
      if (m_pView == MAP_FAILED)
#endif
      {
        extern FILE *g_output;
        extern void quit(); extern int g_display_errors;
        if (g_display_errors) 
        {
          fprintf(g_output,"\nInternal compiler error #12345: error mmapping datablock to %d.\n", size);
          fflush(g_output);
        }
        quit();
      }

      return (void *)((char *)m_pView + offset - alignedoffset);
    }

    void *getmore(int offset, int *size)
    {
      void *pView;
      void *pViewBackup = m_pView;
#ifndef _WIN32
      int iMappedSizeBackup = m_iMappedSize;
#endif
      m_pView = 0;
      pView = get(offset, size);
      m_pView = pViewBackup;
#ifndef _WIN32
      m_iMappedSize = iMappedSizeBackup;
#endif
      return pView;
    }

    void release()
    {
      if (!m_pView)
        return;

#ifdef _WIN32
      UnmapViewOfFile(m_pView);
#else
      munmap(m_pView, m_iMappedSize);
#endif
      m_pView = NULL;
    }

    void release(void *pView, int size)
    {
      if (!pView)
        return;

#ifdef _WIN32
      UnmapViewOfFile(pView);
#else
      munmap(pView, size);
#endif
    }

    void flush(int num)
    {
      if (m_pView)
#ifdef _WIN32
        FlushViewOfFile(m_pView, num);
#else
        msync(m_pView, num, MS_SYNC);
#endif
    }

  private:
#ifdef _WIN32
    HANDLE m_hFile, m_hFileMap;
#else
    FILE *m_hFile;
    int m_hFileDesc;
    int m_iMappedSize;
#endif
    void *m_pView;
    int m_iSize;
    BOOL m_bReadOnly;
    BOOL m_bTempHandle;

    static int m_iAllocationGranularity;
};

class MMapFake : public IMMap
{
  public:
    MMapFake()
    {
      m_pMem = NULL;
      m_iSize = 0;
    }

    void set(const char *pMem, int iSize)
    {
      m_pMem = pMem;
      m_iSize = iSize;
    }

    int getsize()
    {
      return m_iSize;
    }

    void *get(int offset, int size)
    {
      return get(offset, &size);
    }

    void *get(int offset, int *size)
    {
      if (!size || (offset + *size > m_iSize))
        return NULL;
      return (void *)(m_pMem + offset);
    }

    void *getmore(int offset, int *size)
    {
      return get(offset, size);
    }

    void resize(int n) {}
    void release() {}
    void release(void *p, int size) {}
    void clear() {}
    void setro(BOOL b) {}
    void flush(BOOL b) {}

  private:
    const char *m_pMem;
    int m_iSize;
};

class MMapBuf : public IGrowBuf, public IMMap
{
  public:
    MMapBuf() 
    { 
      m_gb_u=0;
      m_alloc=m_used=0;
    }

    virtual ~MMapBuf() 
    { 
      m_fm.release();
    }

    int add(const void *data, int len) 
    { 
      if (len <= 0) return 0;
      resize(getlen() + len);
      memcpy((char*)get(getlen() - len, len), data, len);
      return getlen() - len;
    }

    void setro(BOOL bRO)
    {
      m_fm.setro(bRO);
    }

    void resize(int newlen)
    {
      if (!m_gb_u && newlen < (16 << 20)) // still in db mode
      {
        m_gb.resize(newlen);
        return;
      }

      // not in db mode
      m_gb_u = 1;
      m_used = newlen;

      if (newlen > m_alloc)
      {
        m_alloc = newlen + (16 << 20); // add 16mb to top of mapping

        m_fm.resize(m_alloc);

        if (m_gb.getlen())
        {
          memcpy(m_fm.get(0, m_gb.getlen()), m_gb.get(), m_gb.getlen());
          m_fm.flush(m_gb.getlen());
          m_fm.release();
          m_gb.resize(0);
        }
      }
    }

    int getsize()
    {
      if (m_gb_u)
        return m_fm.getsize();
      return m_gb.getlen();
    }

    int getlen()
    {
      if (m_gb_u)
        return m_used;
      return m_gb.getlen();
    }

    void *get()
    {
      return get(0, m_alloc);
    }

    void *get(int offset, int *sizep)
    {
      if (!sizep)
        return NULL;
      int size = *sizep;
      return get(offset, size);
    }

    void *get(int offset, int size)
    {
      if (m_gb_u)
        return m_fm.get(offset, size);
      return (void *) ((char *) m_gb.get() + offset);
    }

    void *getmore(int offset, int *size)
    {
      if (m_gb_u)
        return m_fm.getmore(offset, size);
      return (void *) ((char *) m_gb.get() + offset);
    }

    void release()
    {
      if (m_gb_u)
        m_fm.release();
    }

    void release(void *pView, int size)
    {
      if (m_gb_u)
        m_fm.release(pView, size);
    }

    void clear()
    {
      if (m_gb_u)
        m_fm.clear();
    }

    void flush(int num)
    {
      if (m_gb_u)
        m_fm.flush(num);
    }

  private:
    GrowBuf m_gb;
    MMapFile m_fm;

    int m_gb_u;
    int m_alloc, m_used;
};


#endif//_STRLIST_H_
