/*
 *  JLib - Jacob's Library.
 *  Copyright (C) 2003, 2004  Juan Carlos Seijo Pérez
 * 
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 * 
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 * 
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 *  Juan Carlos Seijo Pérez
 *  jacob@mainreactor.net
 */

/** Virtual filesystem.
 * @file    JFS.h
 * @author  Juan Carlos Seijo Pérez
 * @date    23/12/2003
 * @version 0.0.1 - 23/12/2003 - First version.
 */
#ifndef _JFS_INCLUDED
#define _JFS_INCLUDED

#include <JLib/Util/JTypes.h>
#include <JLib/Util/JTree.h>
#include <vector>
#include <dirent.h>
#include <JLib/Util/JObject.h>
#include <JLib/Util/JString.h>
#include <JLib/Util/JFile.h>
#include <JLib/Util/JTextFile.h>
#include <JLib/Util/JLoadSave.h>
#include <JLib/Graphics/JImage.h>
#include <JLib/Graphics/JImageSprite.h>

/*** Identification string for JFS files */
#define JRES_JFS_FILE                   "JLIBJFS "
/*** Major version number */
#define JRES_JFS_MAJOR                  1
/*** Minor version number */
#define JRES_JFS_MINOR                  0
/*** Size of the header */
#define JRES_JFS_HEADER_SIZE            10
/*** Version of the exporter */
#define JRES_JFS_EXPORT_VERSION         "///~ JFS V1.0"
/*** Begin of export id */
#define JRES_JFS_EXPORT_BEGIN           "///~ RESOURCES BEGIN"
/*** End of export id */
#define JRES_JFS_EXPORT_END             "///~ RESOURCES END"
/*** Export warning label */
#define JRES_JFS_EXPORT_WARNING         " - DO NOT EDIT THIS FILE MANUALLY. USE JLib's JFSBrowser INSTEAD"

// Built-in resource identifiers

/*** Resource block id */
#define JRES_RESOURCEBLOCK              0x80000000
/*** Resource link id */
#define JRES_RESOURCELINK               0x40000000
/*** Image file id */
#define JRES_IMAGE                      0x20000000
/*** Sound file id */
#define JRES_SOUND                      0x10000000
/*** Text file id */
#define JRES_TEXT                       0x08000000
/*** Image sprite id */
#define JRES_IMAGESPRITE                0x04000000
/*** Font file id */
#define JRES_FONT                       0x02000000
/*** Generic data id */
#define JRES_BINARY                     0x01000000

// Resource flags

/*** Compression flag */
#define JRES_FLAGS_COMPRESSED           0x00000001
/*** Opened block flag (edit mode) */
#define JRES_FLAGS_BLOCK_OPENED         0x80000000
/** Selected resource flag (edit mode) */
#define JRES_FLAGS_SELECTED             0x40000000
/** Save pending flag (edit mode) */
#define JRES_FLAGS_IMPORTED             0x20000000

/*** Check if the resource is compressed */
#define JFS_COMPRESSED(p)               ((p)->Header().flags & JRES_FLAGS_COMPRESSED)

/** Check if the resource is a block */
#define JFS_IS_BLOCK(p)                 ((p)->Header().type == JRES_RESOURCEBLOCK)

/** Check if the resource is a link */
#define JFS_IS_LINK(p)                  ((p)->Header().type == JRES_RESOURCELINK)

/** Checks if the resource is opened (has no sense for other resource than a block) */
#define JFS_OPENED(p)                   ((p)->Header().flags & JRES_FLAGS_BLOCK_OPENED)

/** Checks if the reosurce is a block and it is opened */
#define JFS_BLOCKOPENED(p)              (((p)->Header().type == JRES_RESOURCEBLOCK) && ((p)->Header().flags & JRES_FLAGS_BLOCK_OPENED))

/** Checks if the resource is seleced */
#define JFS_SELECTED(p)                 ((p)->Header().flags & JRES_FLAGS_SELECTED)

class JFS;

/** Resource header.
 */
class JResourceHeader : public JLoadSave
{
public:
		u32 id;                 /**< Unique resource id */
		u32 pid;                /**< Parent resource id */
    u32 type;               /**< Resource type */
    u32 flags;              /**< Additional flags */
		u32 size;               /**< Size of the data this resource contains in the file */

    /** Loads the header.
     * @param  f File opened and correctly positioned from which to load the data.
     * @return 0 if ok, 1 if I/O error, 2 if data integrity error.
     */
    virtual u32 Load(JRW &f);

    /** Saves the header.
     * @param  f File opened and correctly positioned where to save the data.
     * @return 0 if ok, 1 if I/O error, 2 if data integrity error.
     */
    virtual u32 Save(JRW &f);
		
		/** Size of this header in the file.
		 * @return Size of the header in the file.
		 */
		virtual u32 Size() {return 20;}
};

/** Basic resource.
 */
class JResource : public JLoadSave
{
	friend class JFS;

private:
	u32 instanceCount;        /**< Number of existing instances of this resource */

protected:
  JResourceHeader header;   /**< Resource header */

  JLoadSave *data;          /**< Unique instance of the data of the object this resource refers to */
  bool loaded;              /**< Indicates whether this resource has benn loaded or not */

public:
  /** Creates an empty resource.
   */
  JResource() : instanceCount(0), data(0), loaded(false)
  {}

  /** Returns the data size for this resource.
   * @return Data size of this resource.
   */
  u32 Size() {return header.size;}

  /** Returns the type of resource.
   * @return Type of this resource as in the header.
   */
  u32 Type() const {return header.type;}

  /** Returns this resource's id.
   * @return Resource id.
   */
  u32 Id() const {return header.id;}

  /** Returns the data of this resource.
   * @return Dta of this resource.
   */
  JLoadSave* Data() const {return data;}

  /** Checks if the resource has been loaded already.
   * @return <b>true</b> if so, <b>false</b> if not.
   */
  bool Loaded() {return loaded;}

  /** Destroys the resource, unloads it and frees its allocated memory.
   */
  virtual ~JResource() {}

  /** Loads the resource.
   * @param  f File opened and correctly positioned from which to load the resource data.
   * @param  where Pointer to the object in where to load resource data.
   * @return 0 if ok, 1 if I/O error, 2 if data integrity error.
   */
  virtual u32 Load(JRW &f, JLoadSave *where);
	
	/** Returns this resource's header.
	 * @return Resource header.
	 */
	JResourceHeader & Header() {return header;}

	/** Returns the resource type based upon the file extension.
	 * @param  filename File name (.txt, .png, .tga, .jpg, .bmp, .xpm, .wav, .spr, .jfs).
	 * @return Resource type, one of JRES_XXX.
	 */
	static u32 TypeOf(const char *filename);
};

/** Resource list iterator.
  */
typedef std::vector<JResource*>::iterator JResIterator;

/** Resource index entry.
 */
struct JFSIndexEntry
{
  u32 offset;                       /**< Offset of the resource from the beginning of the file. */
	JString name;                     /**< String with the resource id in text form. */
	JResource *res;                   /**< Resource. */
};

/** Virtual filesystem.
 * Consists on a file organized in a hierarchy of blocks.
 * At least always one block exists, the root block from which the other resources in the file hang.
 * A resource is identified biunivocally from its id.
 */
class JFS : public JTree<JResource *>
{
protected:
  JString resFilename;                  /**< Name of the file */
  JString prefix;                       /**< Export prefix for identifiers at the '.h' export file */
  JRW resFile;                          /**< Resource file */
	JTree<JResource *>::Iterator *it;     /**< Auxiliar iterator */
	std::vector<JFSIndexEntry *> index;   /**< Resource offset index */
	u32 defaultFlags;                     /**< Default flags when adding a resource */
	s32 compressionLevel;                 /**< Compression level */

	/** Chenges the size of the file. Places resFile ready to write.
	 * @param  offset Offset at which to change the size.
	 * @param  numBytes Number of bytes to increment size.
	 * @return <b>true</b> if ok, <b>false</b> if the operation couldn't be accomplished.
	 */
	bool ResizeAt(u32 offset, u32 numBytes);

  /** Loads the resource header and checks if it's in the expected format.
	 * @return 0 if ok, 1 if I/O error, 2 if data integrity error.
	 */
  s32 LoadHeader();

  /** Load the resource offset index.
	 * @return 0 if ok, 1 if I/O error, 2 if data integrity error.
	 */
  s32 LoadIndex();

  /** Saves the resource header.
	 * @return 0 if ok, 1 if I/O error, 2 if data integrity error.
	 */
  s32 SaveHeader();

  /** Saves the resource offset index.
	 * @return 0 if ok, 1 if I/O error, 2 if data integrity error.
	 */
  s32 SaveIndex();

	/** Adds a resource in the current position of the tree's global iterator.
	 * @param  res Resource to add.
	 * @return 0 if successful, -1 if not.
	 */
	s32 AddTreeResource(JResource *res);

	/** Adds an entry to the resource offset index and adjusts the following indices.
	 * @param  offset Offset to add.
	 * @param  name Name of the resource to add.
	 * @param  res Resource to add.
	 * @return 0 if ok, 1 if I/O error, 2 if data integrity error.
	 */
	u32 AddToIndex(u32 offset, const JString &name, JResource *res);
	
  /** Checks the resource header and loads the index.
	 * @return 0 if ok, 1 if I/O error, 2 if data integrity error.
	 */
  s32 Load();

  /** Creates the tree structure based upon the actual index. Destroys any previously existing structure.
	 * @return 0 if ok, -1 if an error ocurred.
	 */
  s32 BuildTree();

public:
  /** Creates an empty virtual resource filesystem. Any of Open or Create must be called before using it.
   */
  JFS() : it(0), defaultFlags(0), compressionLevel(9)
  {}

  /** Adds a file to the current block.
	 * @param  filename Name of the file to import.
	 * @param  flags Import flags.
	 * @return 0 if ok, <0 if error.
   */
  s32 AddResource(const char *filename, u32 flags = 0);

  /** Creates a resource block in the actual block.
	 * @param  filename Name of the block to create.
	 * @return 0 if ok, <0 if error.
   */
  s32 AddBlock(const char *name);

	/** Opens an existing virtual resource filesystem file.
   * @param  _name Name of the file.
   * @param  edit Edit mode indicator. If not editing, only the index is read and waits for resource queries. 
   * If editing, the tree structure is also created.
	 * @return 0 if it exists and could be loaded, -1 if _name is 0, -2 if the file couldn't be opened, 1 if the file couldn't be loaded.
	 */
	s32 Open(const char *_name = 0, bool edit = false);

	/** Creates a new JFS file.
   * @param  _name Name of the file.
	 * @return 0 if it could be created, 1 if not.
	 */
	s32 Create(const char *name = 0);

	/** Imports a file or a directory structure.
	 * The directories are added as blocks containing the files as resources. .png, .tga, .bmp, .jpg, etc. files are added as image resources,
	 * .wav files as sound resource, .spr as image sprites, .txt files as text resources, .jfs files as blocks (if not accompained of a .h 
   * definition file (a file with its same name but extension '.h', in the same directory), unique id's are created for its resources.
	 * The rest of files are treated as binary data (egenric resources).
	 * @param  filename File or base directory where to import.
	 * @param  where Tree node from which import. If it's a opened block or root imports inside, else imports at the same level and
	 * where 'after' says.
	 * @param  after Indicates whether to add before or after the current node.
	 * @return 0 If import succeeded, -1 if not.
	 */
	s32 Import(const char *filename, JTree<JResource *>::Node *where = 0, bool after = true);
	
	/** Import from a JFS file.
	 * @param  _name Name of the file to import.
	 * @return 0 If import succeeded, -1 if the file didn't exist, -2 if its not a valid JFS file.
	 * -3 if error during import.
	 */
	s32 ImportJFS(const char *_name);
	
  /** Loads the given resource.
	 * @param  id Resource id of the resource to be loaded.
	 * @param  where Pointer to the object where to load the resource.
	 * @return 0 if ok, 1 if I/O error and 2 if data integrity error.
	 */
  u32 Load(u32 id, JLoadSave *where);

  /** Returns an instance of the resource with the given name. If not loaded, loads it.
   * @param  _name Name of the resource.
   * @return Requested reosurce or 0 (zero) if it wasn't found or an error occured.
   */
	JLoadSave * Get(const JString &_name);
  
  /** Returns an instance of the resource with the given id.
   * @param  id Resource id.
   * @return Requested reosurce or 0 (zero) if it wasn't found or an error occured.
   */
	JLoadSave * Get(u32 id);
  
  /** Deletes the requested resource.
   * @param  _name Name of the resource to delete.
   * @return <b>true</b> if it could be deleted <b>false</b> if not.
   */
  bool Delete(JString &_name);

  /** Returns the name of the JFS file.
   * @return Name of the JFS file.
   */
  const JString & Name() {return resFilename;}
	
	/** Sets the current preffix. 
	 * @param  p Preffix.
	 */
	void Prefix(const JString &p) {prefix = p;}

	/** Gets the current preffix. 
	 * @return p Current preffix.
	 */
	const char * Prefix() {return prefix.Str();}

	/** Sets the default flags.
	 * @param  p Default flags to set.
	 */
	void DefaultFlags(u32 flags) {defaultFlags = flags;}

	/** Gets the default flags.
	 * @return Default flags.
	 */
	u32 DefaultFlags() {return defaultFlags;}

	/** Sets the default compression level. 
	 * @param  c Default compression level to set.
	 */
	void CompressionLevel(s32 c) {compressionLevel = c;}

	/** Returns the default compression level.
	 * @return Default compression level.
	 */
	s32 CompressionLevel() {return compressionLevel;}

	/** Returns a textual resource id based upon a filename and the current preffix.
	 * @param  name Name of the file the resource comes from.
	 * @return Id string representation.
	 */
	const JString IdFromFilename(const char *name);

	/** Returns a file name from a resource id and the current preffix.
	 * @param  name Name of the id.
	 * @return Filename.
	 */
	const JString FilenameFromId(const char *name);

	/** Exports the index in C header format to the given file.
	 * @param  filename Name of the file where to export the identifiers.
	 * @return <b>true</b> if export succeeded, <b>false</b> if not.
	 */
	bool ExportIndex(const char *filename);

	/** Export the resource structure to a directory structure.
	 * @return 0 if ok, <0 if not.
	 */
	s32 Export();
 
	/** Destroys this object.
	 */
	~JFS() {JDELETE(it);}
};

#endif  // _JFS_INCLUDED
