
#ifndef TN_CACHE_H
#define TN_CACHE_H

#include <string>
#include <ctime>
#include <limits>
#include <iosfwd>

#include <foundation/TnTypes.h>
#include <foundation/Mutex.h>
#include <foundation/SharedPtr.h>

#include <RenderEngine/TnMapResource.h>
#include <foundation/Util/TnDb.h>


enum eTnCacheDataStatus
{
    eTnCacheDataStatus_DoesNotExist,       // Data does not exist in cache
    eTnCacheDataStatus_IsExpired,          // Data exists but is expired
    eTnCacheDataStatus_Exists,             // Data exists in cache
    eTnCacheDataStatus_Corrupt,            // Data failed CRC or hash
    eTnCacheDataStatus_MissingFromStore    // Data is in index, but not in data store
};

std::ostream& operator<<(std::ostream&, eTnCacheDataStatus);


// Data query result
struct TnCacheDataResult
{
    eTnCacheDataStatus status;
    TnMapResource resource;
    
    TnCacheDataResult() : status(eTnCacheDataStatus_DoesNotExist) {}
};


class TnCache
{
public:

    typedef Tn::Foundation::shared_ptr<TnCache> ptr;

    TnCache(std::string const & indexFile);
    ~TnCache();
    
    bool IsOpen() const;
    std::string const & GetFullPath() const;
    std::string const & GetBaseName() const;

    /*  Formerly, capacity was unlimited by default. A limited default
     *  capacity seems safer, even if it is somewhat arbitrary.
     */

    static uint64_t const default_capacity_bytes = 0x4000000; // 64MB 
    static uint64_t const default_capacity_objects = 0x10000; // 65535 objects 

    /* Set the size of the cache beyond which eviction occurs, in bytes.
     */
    void SetCapacityBytes(uint64_t bytes);
    
    /* Set the size of the cache beyond which eviction occurs, in objects.
     */
    void SetCapacityObjects(size_t objects);
    
    void Store(std::string const & name, TnMapResource const & resource);
    void Store(std::string const & name, TnMapResource const & resource, time_t expiration);
    
    eTnCacheDataStatus GetStatus(std::string const & name);
    
    // Access data from cache, returning data object
    TnCacheDataResult Find(std::string const & name);
    
    // Clear all data from cache
    void Clear();
    
private:

    bool Open();

    // These assume their callers are guarded

    eTnCacheDataStatus GetStatusInternal(std::string const & name);
    void Insert(std::string const & name, TnMapResource const & resource, time_t expiration);
    void Update(std::string const & name, TnMapResource const & resource, time_t expiration);
    void Evict();
    
    // Calculate a CRC32 for given data
    static uint32_t Crc(uint8_t const * data, size_t size);
    
private:

    static char const * formatExpiration(time_t);

    // Cache name
    std::string m_baseName, m_fullPath;

    // Cache capacity in bytes
    uint64_t m_capacityBytes;
    
    // Cache capacity in data objects
    size_t m_capacityObjects;
    
    // Syncrhonize access to cache
    Tn::Foundation::mutex m_mutex;

    bool m_isOpen;

    // Database
    sqlite m_db;
    
    // Prepared statements
    sql m_selectCountAndSize;
    sql m_selectTimesByName;
    sql m_selectMultipleByName;
    sql m_selectNameSizeOrderAccessTime;
    sql m_selectNameSizeByExpiredOrderExpireTime;
    sql m_selectNameSizeByUnexpiredOrderAccessTime;
    sql m_insert;
    sql m_update;
    sql m_updateAccessTime;
    sql m_deleteAll;
    sql m_deleteByName;
    sql m_deleteNonPersist;

};

#endif
