//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------

#ifndef _RESMANAGER_H_
#define _RESMANAGER_H_

#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
#ifndef _STRINGTABLE_H_
#include "core/stringTable.h"
#endif

#ifndef _FILESTREAM_H_
#include "core/fileStream.h"
#endif
#ifndef _ZIPSUBSTREAM_H_
#include "core/zipSubStream.h"
#endif
#ifndef _ZIPAGGREGATE_H_
#include "core/zipAggregate.h"
#endif
#ifndef _ZIPHEADERS_H_
#include "core/zipHeaders.h"
#endif
#ifndef _CRC_H_
#include "core/crc.h"
#endif

class Stream;
class FileStream;
class ZipSubRStream;
class ResManager;
class FindMatch;

extern ResManager *ResourceManager;

//------------------------------------------------------------------------------
class ResourceObject;

/// The base class for all resources.
///
/// This must be subclassed to implement a resource that can be managed.
///
/// Creating a new resource type is very simple. First, you need a function
/// which can create a new instance of the resource given a stream:
///
/// @code
/// ResourceInstance* constructBitmapBMP(Stream &stream)
/// {
///    GBitmap *bmp = new GBitmap;
///    if(bmp->readMSBmp(stream))
///       return bmp;
///    else
///    {
///       delete bmp;
///       return NULL;
///    }
/// }
/// @endcode
///
/// Then you need to register the extension of your resource type with the resource manager:
///
/// @code
/// ResourceManager->registerExtension(".bmp", constructBitmapBMP);
/// @endcode
///
/// And, of course, you need to provide a subclass of ResourceInstance:
///
/// @code
/// class GBitmap : ResourceInstance {
///    ... whatever you need for your resource goes in here ...
/// }
/// @endcode
///
/// ResourceInstance imposes very few requirements on you as the resource type
/// implementor. All you "have" to provide is a destructor to deallocate whatever
/// storage your resource might allocate. The ResourceManager will ensure that
/// there is only one instance of the ResourceInstance for each file, and that it
/// is only destroyed when no more references to it remain.
///
/// @see TerrainFile, GBitmap, AudioBuffer for more examples.
class ResourceInstance
{
private:
public:
   /// Pointer to the ResourceObject that stores all our book-keeping data.
   ResourceObject *mSourceResource;

   ResourceInstance() { mSourceResource = NULL; }
   virtual ~ResourceInstance() {}
};


typedef ResourceInstance* (*RESOURCE_CREATE_FN)(Stream &stream);


//------------------------------------------------------------------------------
#define InvalidCRC 0xFFFFFFFF

/// Wrapper around a ResourceInstance.
///
/// This contains all the book-keeping data used by ResDictionary and ResManager.
///
/// @see ResManager
class ResourceObject
{
   friend class ResDictionary;
   friend class ResManager;

   /// @name List Book-keeping
   /// @{

   ///
   ResourceObject *prev, *next;

   ResourceObject *nextEntry;    ///< This is used by ResDictionary for its hash table.

   ResourceObject *nextResource;
   ResourceObject *prevResource;
   /// @}

public:
   enum Flags
   {
      VolumeBlock   = BIT(0),
      File          = BIT(1),
      Added         = BIT(2),
   };
   S32 flags;  ///< Set from Flags.

   StringTableEntry path;     ///< Resource path.
   StringTableEntry name;     ///< Resource name.

   /// @name ZIP Archive
   /// If the resource is stored in a zip file, these members are populated.
   /// @{

   ///
   StringTableEntry zipPath;  ///< Path of zip file.
   StringTableEntry zipName;  ///< Name of zip file.

   S32 fileOffset;            ///< Offset of data in zip file.
   S32 fileSize;              ///< Size on disk of resource block.
   S32 compressedFileSize;    ///< Actual size of resource data.
   /// @}

   ResourceInstance *mInstance;  ///< Pointer to actual object instance. If the object is not loaded,
                                 ///  this may be NULL or garbage.
   S32 lockCount;                ///< Lock count; used to control load/unload of resource from memory.
   U32 crc;                      ///< CRC of resource.

   ResourceObject();
   ~ResourceObject() { unlink(); }

   void destruct();

   /// @name List Management
   /// @{

   ///
   ResourceObject* getNext() const { return next; }
   void unlink();
   void linkAfter(ResourceObject* res);
   /// @}

   /// Get some information about file times.
   ///
   /// @param  createTime  Pointer to a FileTime structure to fill with information about when this object was
   ///                     created.
   /// @param  modifyTime  Pointer to a FileTime structure to fill with information about when this object was
   ///                     modified.
   void getFileTimes(FileTime *createTime, FileTime *modifyTime);
};


inline void ResourceObject::unlink()
{
   if (next)
      next->prev = prev;
   if (prev)
      prev->next = next;
   next = prev = 0;
}

inline void ResourceObject::linkAfter(ResourceObject* res)
{
   unlink();
   prev = res;
   if ((next = res->next) != 0)
      next->prev = this;
   res->next = this;
}


//------------------------------------------------------------------------------
/// Wrapper class around a ResourceInstance subclass.
///
/// @code
///    // Loading a resource...
///    Resource<TerrainFile> terrRes;
///
///    terrRes = ResourceManager->load(fileName);
///    if(!bool(terrRes))
///       Con::errorf(ConsoleLogEntry::General, "Terraformer::terrainFile - invalid terrain file '%s'.", fileName);
/// @endcode
///
/// When the Resource<> is destroyed, it frees the lock on the resource.
///
/// @see ResManager
template <class T> class Resource
{
private:
   ResourceObject *obj;  ///< Actual resource object

   // ***WARNING***
   // Using a faster lock that bypasses the resource manger.
   // void _lock() { if (obj) obj->rm->lockResource( obj ); }
   void _lock();   ///< Increments the lock count on this object
   void _unlock(); ///< Decrements the lock count on this object

public:
   /// If assigned a ResourceObject, it's assumed to already have
   /// been locked, lock count is incremented only for copies or
   /// assignment from another Resource.
   Resource() : obj(NULL) { ; }
   Resource(ResourceObject *p) : obj(p) { ; }
   Resource(const Resource &res) : obj(res.obj) { _lock(); }
   ~Resource() { unlock(); }  ///< Decrements the lock count on this object, and if the lock count is 0 afterwards,
                              ///< adds the object to the timeoutList for deletion on execution of purge().

   const char *getFilePath() const { return (obj ? obj->path : NULL); } ///< Returns the path of the file (without the actual name)
   const char *getFileName() const { return (obj ? obj->name : NULL); } ///< Returns the actual file name (without the path)

   Resource& operator= (ResourceObject *p) { _unlock(); obj = p; return *this; }
   Resource& operator= (const Resource &r) { _unlock(); obj = r.obj; _lock(); return *this; }

   U32 getCRC() { return (obj ? obj->crc : 0); }
   bool isNull()   const { return ((obj == NULL) || (obj->mInstance == NULL)); }
   operator bool() const { return ((obj != NULL) && (obj->mInstance != NULL)); }
   T* operator->()   { return (T*)obj->mInstance; }
   T& operator*()    { return *((T*)obj->mInstance); }
   operator T*() const    { return (obj) ? (T*)obj->mInstance : (T*)NULL; }
   const T* operator->() const  { return (const T*)obj->mInstance; }
   const T& operator*() const   { return *((const T*)obj->mInstance); }
   operator const T*() const    { return (obj) ? (const T*)obj->mInstance :  (const T*)NULL; }
   void unlock();
   void purge();
};

#define INVALID_ID ((U32)(~0))

//----------------------------------------------------------------------------
/// Resource Dictionary.
///
/// Maps of names and object IDs to objects.
///
/// Provides fast lookup for name->object, id->object and
/// for fast removal of an object given a pointer to it.
///
/// @see ResManager
class ResDictionary
{
   /// @name Hash Table
   /// @{

   enum { DefaultTableSize = 1029 };

   ResourceObject **hashTable;
   S32 entryCount;
   S32 hashTableSize;
   DataChunker memPool;
   S32 hash(StringTableEntry path, StringTableEntry name);
   S32 hash(ResourceObject *obj) { return hash(obj->path, obj->name); }
   /// @}

public:
   ResDictionary();
   ~ResDictionary();

   /// Add a ResourceObject to the dictionary.
   void insert(ResourceObject *obj, StringTableEntry path, StringTableEntry file);

   /// @name Find
   /// These functions search the hash table for an individual resource.  If the resource has
   /// already been loaded, it will find the resource and return its object.  If not,
   /// it will return NULL.
   /// @{

   ResourceObject* find(StringTableEntry path, StringTableEntry file);
   ResourceObject* find(StringTableEntry path, StringTableEntry file, StringTableEntry filePath, StringTableEntry fileName);
   ResourceObject* find(StringTableEntry path, StringTableEntry file, U32 flags);
   /// @}

   /// Move a previously added resource object to be in the list after everything that matches the specified mask.
   void pushBehind(ResourceObject *obj, S32 mask);

   /// Remove a resource object from the dictionary.
   void remove(ResourceObject *obj);
};


//------------------------------------------------------------------------------
/// A virtual file system for the storage and retrieval of ResourceObjects.
///
/// Basic resource manager behavior:
///  - Set the mod path.
///      - ResManager scans directory tree under listed base directories
///      - Any volume (.zip) file in the root directory of a mod is scanned
///        for resources.
///      - Any files currently in the resource manager become memory resources.
///      - They can be "reattached" to the first file that matches the file name.
///
/// All classes which wish to be handled by the resource manager need:
///    -# To be derived from ResourceInstance.
///    -# To register a creation function and file extension with the manager.
///
/// @nosubgrouping
class ResManager
{
private:
   /// Path to which we will write data.
   ///
   /// This is used when, for instance, we download files from a server.
   char writeablePath[1024];

   /// Primary path from which we load data.
   char primaryPath[1024];

   /// List of secondary paths to search.
   char* pathList;

   ResourceObject timeoutList;
   ResourceObject resourceList;

   ResDictionary dictionary;

   bool echoFileNames;

   bool isIgnoredSubdirectoryName(const char *name) const;

   /// Scan a zip file for resources.
   bool scanZip(ResourceObject *zipObject);

   /// Create a ResourceObject from the given file.
   ResourceObject* createResource(StringTableEntry path, StringTableEntry file);

   /// Create a ResourceObject from the given file in a zip file.
   ResourceObject* createZipResource(StringTableEntry path, StringTableEntry file, StringTableEntry zipPath, StringTableEntry zipFle);

   void searchPath(const char *pathStart);
   bool setModZip(const char* path);

   struct RegisteredExtension
   {
      StringTableEntry     mExtension;
      RESOURCE_CREATE_FN   mCreateFn;
      RegisteredExtension  *next;
   };

   Vector<char *> mMissingFileList;                ///< List of missing files.
   bool mLoggingMissingFiles;                      ///< Are there any missing files?
   void fileIsMissing(const char *fileName);       ///< Called when a file is missing.

   RegisteredExtension *registeredList;

   static char *smExcludedDirectories;
   ResManager();
public:
   RESOURCE_CREATE_FN getCreateFunction( const char *name );

   ~ResManager();
   /// @name Global Control
   /// These are called to initialize/destroy the resource manager at runtime.
   /// @{

   static void create();
   static void destroy();

   /// Load the excluded directories from the resource manager pref and
   /// stuff it into the platform layer.
   static void initExcludedDirectories();

   ///@}

   void setFileNameEcho(bool on);                     ///< Sets whether or not to echo filenames that have been accessed by means of openStream
   void setModPaths(U32 numPaths, const char **dirs); ///< Sets the path for the current game mod
   const char* getModPaths();                         ///< Gets the path for the current game mod

   void setMissingFileLogging(bool log);              ///< Should we log missing files?
   bool getMissingFileList(Vector<char *> &list);     ///< Gets which files are missing
   void clearMissingFileList();                       ///< Clears the missing file list

   /// Tells the resource manager what to do with a resource that it loads
   void registerExtension(const char *extension, RESOURCE_CREATE_FN create_fn);

   S32 getSize(const char* filename);                 ///< Gets the size of the file
   const char* getFullPath(const char * filename, char * path, U32 pathLen);  ///< Gets the full path of the file
   const char* getModPathOf(const char* fileName);    ///< Gets the path of the file local to the mod
   const char* getPathOf(const char * filename);      ///< Gets the path of the file from the base directory
   const char* getBasePath();                         ///< Gets the base path

   ResourceObject* load(const char * fileName, bool computeCRC = false);   ///< loads an instance of an object
   Stream*  openStream(const char * fileName);        ///< Opens a stream for an object
   Stream*  openStream(ResourceObject *object);       ///< Opens a stream for an object
   void     closeStream(Stream *stream);              ///< Closes the stream

   /// Decrements the lock count of an object.  If the lock count is zero post-decrement,
   /// the object is added to the timeoutList for deletion upon call of flush.
   void unlock( ResourceObject* );

   /// Add a new resource instance
   bool add(const char* name, ResourceInstance *addInstance, bool extraLock = false);

   /// Searches the hash list for the filename and returns it's object if found, otherwise NULL
   ResourceObject* find(const char * fileName);
   /// Loads a new instance of an object by means of a filename
   ResourceInstance* loadInstance(const char *fileName, bool computeCRC = false);
   /// Loads a new instance of an object by means of a resource object
   ResourceInstance* loadInstance(ResourceObject *object, bool computeCRC = false);

   /// Searches the hash list for the filename and returns it's object if found, otherwise NULL
   ResourceObject* find(const char * fileName, U32 flags);

   /// Finds a resource object with given expression
   ResourceObject* findMatch(const char *expression, const char **fn, ResourceObject *start = NULL);

   void purge();                                      ///< Goes through the timeoutList and deletes it all.  BURN!!!
   void purge( ResourceObject *obj );                 ///< Deletes one resource object.
   void freeResource(ResourceObject *resObject);      ///< Frees a resource!
   void serialize(VectorPtr<const char *> &filenames);///< Sorts the resource objects

   S32  findMatches( FindMatch *pFM );                ///< Finds multiple matches to an expression.
   bool findFile( const char *name );                 ///< Checks to see if a file exists.

   /// Computes the CRC of a file.
   ///
   /// By passing a different crcInitialVal, you can take the CRC of multiple files.
   bool getCrc(const char * fileName, U32 & crcVal, const U32 crcInitialVal = INITIAL_CRC_VALUE );

   void setWriteablePath(const char *path);           ///< Sets the writable path for a file to the one given.
   bool isValidWriteFileName(const char *fn);         ///< Checks to see if the given path is valid for writing.

   /// Opens a file for writing!
   bool openFileForWrite(FileStream &fs, const char *fileName, U32 accessMode = 1);

#ifdef TORQUE_DEBUG
   void dumpLoadedResources();                        ///< Dumps all loaded resources to the console.
#endif
};

template<class T> inline void Resource<T>::unlock()
{
   if (obj) {
      ResourceManager->unlock( obj );
      obj=NULL;
   }
}

template<class T> inline void Resource<T>::purge()
{
   if (obj) {
      ResourceManager->unlock( obj );
      if (obj->lockCount == 0)
         ResourceManager->purge(obj);
      obj = NULL;
   }
}
template <class T> inline void Resource<T>::_lock()
{
   if (obj)
      obj->lockCount++;
}

template <class T> inline void Resource<T>::_unlock()
{
   if (obj)
      ResourceManager->unlock( obj );
}

#endif //_RESMANAGER_H_
