/******************************************************************************************[File.h]
Copyright (c) 2005-2010, Niklas Een, Niklas Sorensson

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**************************************************************************************************/

#ifndef File_h
#define File_h

#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "Global.h"
#include <ios>

namespace Monosat {
namespace PB {
#define lseek64 lseek   // }- (disable explicit 64-bit support for FreeBSD...)
#define open64  ::open  // }


//=================================================================================================
// A buffered file abstraction with only 'putChar()' and 'getChar()'.


#define File_BufSize 1024   // A small buffer seem to work just as fine as a big one (at least under Linux)

enum FileMode {
    READ, WRITE
};


// WARNING! This code is not thoroughly tested. May contain bugs!

class File {
    int fd;         // Underlying file descriptor.
    FileMode mode;       // Reading or writing.
    uchar* buf;        // Read or write buffer.
    int size;       // Size of buffer (at end of file, less than 'File_BufSize').
    int pos;        // Current position in buffer
    bool own_fd;     // Do we own the file descriptor? If so, will close file in destructor.

public:
#define DEFAULTS fd(-1), mode(READ), buf(NULL), size(-1), pos(0), own_fd(true)

    File() : DEFAULTS{}

    File(int fd, FileMode mode, bool own_fd = true) : DEFAULTS{
        open(fd, mode, own_fd);
    }

    File(cchar* name, cchar* mode) : DEFAULTS{
        open(name, mode);
    }

#undef DEFAULTS

    ~File(){
        close();
    }

    void open(int fd, FileMode mode,
              bool own_fd = true);    // Low-level open. If 'own_fd' is FALSE, descriptor will not be closed by destructor.
    void open(cchar* name, cchar* mode);                     // FILE* compatible interface.
    void close();

    bool null(){               // TRUE if no file is opened.
        return fd == -1;
    }

    int releaseDescriptor(){   // Don't run UNIX function 'close()' on descriptor in 'File's 'close()'.
        if(mode == READ)
            lseek64(fd, pos - size, SEEK_CUR);
        own_fd = false;
        return fd;
    }

    FileMode getMode(){
        return mode;
    }

    void setMode(FileMode m){
        if(m == mode) return;
        if(m == READ){
            flush();
            size = read(fd, buf, File_BufSize);
        }else{
            lseek64(fd, pos - size, SEEK_CUR);
            size = -1;
        }
        mode = m;
        pos = 0;
    }

    int getCharQ(){            // Quick version with minimal overhead -- don't call this in the wrong mode!
#ifdef PARANOID
        assert(mode == READ);
#endif
        if(pos < size) return (uchar) buf[pos++];
        if(size < File_BufSize) return EOF;
        size = read(fd, buf, File_BufSize);
        pos = 0;
        if(size == 0) return EOF;
        return (uchar) buf[pos++];
    }

    int putCharQ(int chr){         // Quick version with minimal overhead -- don't call this in the wrong mode!
#ifdef PARANOID
        assert(mode == WRITE);
#endif
        if(pos == File_BufSize){
            ssize_t wrote = write(fd, buf, File_BufSize);
            if(wrote != File_BufSize){
                throw std::ios_base::failure("Write failed.");
            }
            pos = 0;
        }
        return buf[pos++] = (uchar) chr;
    }

    int getChar(){
        if(mode == WRITE) setMode(READ);
        return getCharQ();
    }

    int putChar(int chr){
        if(mode == READ) setMode(WRITE);
        return putCharQ(chr);
    }

    bool eof(){
        assert(mode == READ);
        if(pos < size) return false;
        if(size < File_BufSize) return true;
        size = read(fd, buf, File_BufSize);
        pos = 0;
        if(size == 0) return true;
        return false;
    }

    void flush(){
        assert(mode == WRITE);
        ssize_t wrote = write(fd, buf, pos);
        if(wrote != pos){
            throw std::ios_base::failure("Write failed.");
        }
        pos = 0;
    }

    void seek(int64 pos, int whence = SEEK_SET);

    int64 tell();
};


//=================================================================================================
// Some nice helper functions:


void putUInt(File& out, uint64 val);

uint64 getUInt(File& in);

macro uint64 encode64(int64 val){return (val >= 0) ? (uint64) val << 1 : (((uint64) (~val) << 1) | 1);}

macro int64 decode64(uint64 val){return ((val & 1) == 0) ? (int64) (val >> 1) : ~(int64) (val >> 1);}

macro void putInt(File& out, int64 val){putUInt(out, encode64(val));}

macro uint64 getInt(File& in){return decode64(getUInt(in));}

}
}
//=================================================================================================
#endif
