#ifndef bqtFileWindowHH
#define bqtFileWindowHH

#ifndef _LARGEFILE64_SOURCE
 #define _LARGEFILE64_SOURCE
#endif
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>

#include <iostream>
#include <cstdlib>
#include <cstdio>

//#define FILEWINDOW_TMPDIR "/tmp"
#define FILEWINDOW_TMPDIR "/mnt/gbatmp"

template<size_t WindowSize>
struct FileWindow
{
    int fd;
    
    size_t Begin;
    unsigned char* Data;
    bool mapped;
    
    FileWindow(): fd(-1), mapped(false)
    {
    }
    
    void Init(int f)
    {
        if(mapped) { munmap(Data, WindowSize); mapped=false; }
        
        fd = f;
        mapped=false;
    }
    ~FileWindow()
    {
        if(mapped) munmap(Data, WindowSize);
    }
    
    void Detach()
    {
        if(mapped) { munmap(Data, WindowSize); mapped=false; }
    }
    
    bool Overlap(size_t bBegin) const
    {
        if(!mapped) return false;
        
        size_t end = Begin   + WindowSize;
        size_t bend = bBegin + WindowSize;
        return (Begin < bend && bBegin < end);
    }
    
    void Load(size_t ptr)
    {
        if(ptr % getpagesize())
        {
            std::cerr << "ptr is not aligned on page size: 0x" << std::hex << ptr << std::endl;
            throw "MMap page alignment failure";
        }
        
        Begin=ptr;
        if(mapped) munmap(Data, WindowSize);
        
        void* p = mmap64(0, WindowSize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, Begin);
        
        if(p == (void*)-1 || p == (void*)0)
        {
            std::cerr << "mmap64 " << Begin << "\n";
            std::perror("mmap64");
            throw "MMap failure";
        }
        else
            Data = (unsigned char*)p;

        mapped=true;
    }
    
    static int CreateTmpFile()
    {
        static unsigned counter=0;
        static int pid = getpid();
        char Buf[4096];
        sprintf(Buf, FILEWINDOW_TMPDIR "/rope-%d-%lu-%u", pid, (unsigned long)WindowSize, counter++);
        int fd = open(Buf, O_CREAT | O_RDWR | O_LARGEFILE | O_TRUNC, 0666);
        if(fd < 0)
        {
            perror(Buf);
        }
        unlink(Buf);
        
        return fd;
    }

    FileWindow(const FileWindow& b) : fd(b.fd), Begin(0), mapped(false)
    {
    }
    FileWindow& operator=(const FileWindow& b)
    {
        Detach();
        fd = b.fd;
        mapped = false;
        return *this;
    }
private:
    //bool Overlap(size_t bBegin) const;
    //void Load(size_t ptr);
};

template<size_t WindowSize,
         size_t NumWindows,
         typename DataType=char,
         size_t MaxSize=0>
class TempFileBuffer
{
public:
    typedef FileWindow<WindowSize> Window;

    static const size_t ElemCount = MaxSize / sizeof(DataType);
    static const size_t NBytes = ElemCount * (size_t)sizeof(DataType);

    TempFileBuffer(): fd(Window::CreateTmpFile()), FileLimit(0)
    {
        ReinitWindows();
    }
    ~TempFileBuffer()
    {
        Detach();
        close(fd);
    }
    
    inline size_t GetCap() const { return FileLimit; }
    void clear()
    {
        Detach();
        ftruncate64(fd, FileLimit=0);
    }
    
    DataType* GetElemWindow(const size_t ElemNo, size_t amount=1) const
    {
        return (DataType*)GetWindow(ElemNo * sizeof(DataType),
                                    amount * sizeof(DataType));
    }
    
    unsigned char* GetWindow(const size_t begin, size_t size) const
    {
        const size_t end = begin + size;
        
        //fprintf(stderr, "GetWindow(0x%lX,0x%lX)\n", begin,size);
        
        if(end > FileLimit)
        {
            if(NBytes > 0 && FileLimit < NBytes) GrowTo(NBytes);
            if(end > FileLimit)
            {
                size_t align    = 0x10000;
                size_t newlimit = (end + align-1) & ~(align-1);
                GrowTo(newlimit);
            }
        }

        /* Find if there's a window that works */
        /* Try most recently used windows first. */
        for(size_t b=0; b<NumWindows; ++b)
        {
            const size_t windownum = age[b];
            Window& w = Windows[windownum];
            if(!w.mapped) continue;
            
            size_t wbegin = w.Begin;
            size_t wend   = w.Begin + WindowSize;
            
            if(begin >= wbegin && end <= wend)
            {
                MoveFirst(windownum, b);
                return &w.Data[begin - wbegin];
            }
        }
        
        /* Pick a window randomly */
        size_t windownum = OldestWindow();
        Window& w = Windows[windownum];
        
        /* Find a new base for it */
        ssize_t new_begin = begin;
        new_begin -= ((ssize_t)WindowSize - (ssize_t)size) / 2;
        if(new_begin < 0) new_begin=0;
        
        new_begin &= ~0xFFFULL;
        if(new_begin + WindowSize < end)
        {
            new_begin=begin;
        }
        
        new_begin &= ~0xFFFULL;
        if(new_begin + WindowSize < end)
        {
            new_begin=begin;
        }

        if(new_begin + (ssize_t)WindowSize < (ssize_t)end
        || new_begin > (ssize_t)begin)
        {
            throw "Eh?";
        }
        
        //fprintf(stderr, "new_begin=0x%lX\n", new_begin);

/*
        // According to Warp, this doesn't seem to be needed.
        
        for(size_t b=0; b<NumWindows; ++b)
            if(b != a && Windows[b].Overlap(new_begin))
                Windows[b].Detach();
*/
        w.Load(new_begin);
        return &w.Data[begin - w.Begin];
    }
    
    void Detach()
    {
        /* Free the memory maps, but keep the file intact */
        for(size_t n=0; n<NumWindows; ++n) Windows[n].Detach();
    }
private:
    /* Copying is not allowed. If you need to include this structure
     * within a STL container, you must use boost::shared_ptr.
     */
    TempFileBuffer(const TempFileBuffer& b);
    TempFileBuffer& operator=(const TempFileBuffer& b);
private:
    int fd;
    mutable size_t FileLimit;
    mutable Window Windows[NumWindows];
    mutable unsigned char age[NumWindows];
    
    inline void ReinitWindows()
    {
        for(size_t n=0; n<NumWindows; ++n)
        {
            Windows[n].Init(fd);
            age[n] = n;
        }
    }
    inline void Touch(size_t windownum) const
    {
        for(size_t a=0; a<NumWindows; ++a)
        {
            if(age[a] == windownum)
            {
                MoveFirst(windownum, a);
                return;
            }
        }
    }
    inline size_t OldestWindow() const
    {
        size_t a = NumWindows-1;
        size_t windownum = age[a];
        MoveFirst(windownum, a);
        return windownum;
    }
    inline void MoveFirst(size_t windownum, size_t oldpos) const
    {
        // move this window to the beginning of the list
        for(size_t a=oldpos; a > 0; --a) age[a] = age[a-1];
        age[0] = windownum;
    }
    
    void GrowTo(size_t newsize) const
    {
        if(newsize > FileLimit)
        {
            size_t grow_amount = newsize - FileLimit;
            
            const size_t BufSize = 0x40000;
            static const char* Buffer = new char[BufSize];
            while(grow_amount > 0)
            {
                size_t eat = BufSize;
                if(eat > grow_amount) eat = grow_amount;
                
                pwrite64(fd, Buffer, eat, FileLimit);
                FileLimit += eat;
                grow_amount -= eat;
            }
        }
        FileLimit = newsize;
        
        /* The file cannot simply be extended with ftruncate64.
         * mmap() requires that the actual pages exist.
         */
    }
};

#endif
