#ifndef AVIFILE_CACHE_H
#define AVIFILE_CACHE_H

#define _LARGEFILE64_SOURCE
#ifdef AVIFILE_AVIFMT_H
#error Cache.h has to be the first inlude file you use!!!
#endif

// These classes provide basic caching abilities for files
// in AVI format. This is useful when files are played
// over NFS or SMB. Relies heavily on AVI structure.

#include <avifmt.h>
#include <except.h>

#include <unistd.h> // read
#include <pthread.h>

#include <string>
#include <vector>
#include <list>


// Isn't this bug in NetBSD configuration - it should not have HAVE_LSEEK64
#ifdef __NetBSD__
#define lseek64 lseek
#else
#ifndef HAVE_LSEEK64
#define lseek64 lseek
#endif
#endif

//#ifdef __USE_LARGEFILE64
//#warning %%%%%%%%%%%%%%%%%%%%%% LARGEFILE64 %%%%%%%%%%%%%%%%%%%%%%
//#endif

struct req;

class Cache
{
    static const unsigned int WAIT = ~0U;
    struct stream_entry
    {
	const std::vector<AVIINDEXENTRY2>* table;
	std::list<req*> buffers;
	std::vector<req*> freebuffers;

	int64_t position;       // needed chunk
	// handled internaly
	size_t sum;             // cached bytes in stream
	int64_t offset;
	unsigned int last;
        bool filling;

	stream_entry() : position(0), sum(0), offset(0), last(0) {}
	stream_entry(const std::vector<AVIINDEXENTRY2>& _t, int64_t _pos)
	    :table(&_t), position(_pos), sum(0), offset(0), last(0) {}
    };
    size_t m_uiSize;      // preallocated chunks for one stream
    std::vector<stream_entry> m_streams;
    pthread_mutex_t mutex_in, mutex_out;
    pthread_cond_t cond_in, cond_out;
    pthread_t thread;
    req* loaded;                // currently loaded buffer
    bool m_bQuit;               // quit caching thread
    int m_iFd;                  // cached file descritor
    unsigned int m_uiId;        // currently processed stream
    unsigned int cache_access;  // total amount of cache access
    unsigned int cache_right;   // accesses satisfied by cache
    unsigned int cache_miss;    // how many times we had to wait
#ifndef QUIET
    int caller;
#endif
public:
    // FIXME: add methods for setting/getting size of cache
    Cache(size_t size);
    ~Cache();
    int addStream(streamid_t id, const std::vector<AVIINDEXENTRY2>& table);
    int clear();
    int create(int fd);
    int read(void* buffer, streamid_t id, framepos_t pos, size_t size, offset_t offset=0);
    int prefetch(streamid_t id, Unsigned position); // will be removed
    int update();//removes unused cache entries and requests newer ones
    double getSize();
    unsigned int pickChunk();
protected:
    void dumpBuffers(const char *txt, bool show_table = false);
    void* readfunc(); // main thread for cache handling
    inline bool isCachable(stream_entry& stream, streamid_t id);  // new data could be readed
private:
    unsigned int pickChunk1();	// unused - for testing
    static void* startReadfuncThread(void* arg);
};

class InputStream
{
public:
    InputStream(const char*) throw(FatalError);
    ~InputStream();
    offset_t seek(int64_t offset);
    offset_t seekCur(int64_t offset);
    offset_t pos();
    offset_t len() const { return ulLength; }

    bool eof() { return (pos() >= ulLength); }

    uint8_t readByte()
    {
        uint8_t c;
	::read(m_iFd, &c, 1);

        return c;
    }

    uint32_t readDword()
    {
	uint32_t i;
	//
	i = readByte();
	i |= (readByte() << 8);
	i |= (readByte() << 16);
	i |= (readByte() << 24);

	return i;
    }

    int async() //enter asynchronous mode
    {
	return (cache) ? cache->create(m_iFd) : -1;
    }
    
    int prefetch(streamid_t id, framepos_t position)
    {
	if (cache)
	{
	    cache->prefetch(id, position);
	    cache->update();
	}
	return 0;
    }
    
    int addStream(streamid_t id, const std::vector<AVIINDEXENTRY2>& table)
    {
	return (cache) ? cache->addStream(id, table) : -1;
    }
    //  int Read(offset_t offset, char* buffer, Unsigned size);//asynchronous, obsolete
    int read(void* buffer, size_t size);//synchronous
    //from cache:
    int read(void* buffer, streamid_t id, framepos_t _pos, size_t size, offset_t offset =0)
    {
	return (cache) ? cache->read(buffer, id, _pos, size, offset) : -1;
    }

    int clear()
    {
	return (cache) ? cache->clear() : -1;
    }

    double cacheSize() const
    {
	return (cache) ? cache->getSize() : 0.0;
    }

protected:
    int m_iFd;
    int64_t ulLength;
    Cache* cache;
};

#endif // AVIFILE_CACHE_H
