#pragma once

/*
 *   License:	LGPL - See file COPYING.LIB for details.
 *   Author:	Stefan Hundhammer <sh@suse.de>
 *              Joshua Hodosh <kdirstat@grumpypenguin.org>
 */

#include <QDebug>
#include <kfileitem.h>
#include <limits.h>
#include <sys/types.h>

#ifndef NOT_USED
#define NOT_USED(PARAM) ((void)(PARAM))
#endif

// Open a new name space since KDE's name space is pretty much cluttered
// already - all names that would even remotely match are already used up,
// yet the resprective classes don't quite fit the purposes required here.

namespace KDirStat {
// With today's hard disks, the 2 GB we could sum up with 'long' (or 4 GB
// with 'unsigned long') are definitely not enough. So we have to go for
// something larger:
typedef long long KFileSize;

// Taken from Linux <limits.h> (the Alpha definition - 64 Bit long!).
// This is how much bytes this program can handle.
#define KFileSizeMax 9223372036854775807LL

// Forward declarations
class KDirInfo;
class KDirTree;
class KFileInfo;

/**
 * Status of a directory read job.
 **/
typedef enum {
  KDirQueued,        // Waiting in the directory read queue
  KDirReading,       // Reading in progress
  KDirFinished,      // Reading finished and OK
  KDirOnRequestOnly, // Will be read upon explicit request only (mount points)
  KDirCached,        // Content was read from a cache
  KDirAborted,       // Reading aborted upon user request
  KDirError          // Error while reading
} KDirReadState;

/**
 * The most basic building block of a @ref KDirTree:
 *
 * Information about one single directory entry. This is the type of info
 * typically obtained by stat() / lstat() or similar calls.	 Most of this
 * can also be obtained by @ref KIO::KDirListJob, but not all: The device
 * this file resides on is something none of KIO's many classes will tell
 * (since of course this only makes sense for local files) - yet this had
 * been _the_ single most requested feature of KDirStat <1.0: Stay on one
 * filesystem. To facilitate this, information about the device is
 * required, thus we'll do lstat() sys calls ourselves for local
 * files. This is what the classes in this file are all about.
 *
 * This class is tuned for size rather than speed: A typical Linux system
 * easily has 150,000+ file system objects, and at least one entry of this
 * sort is required for each of them.
 *
 * This class provides stubs for children management, yet those stubs all
 * are default implementations that don't really deal with children.
 * Derived classes need to take care of that.
 *
 * @short Basic file information (like obtained by the lstat() sys call)
 **/
class KFileInfo {
public:
  /**
   * Default constructor.
   **/
  KFileInfo(KDirInfo *parent = nullptr, const char *name = nullptr);

  /**
   * Constructor from a stat buffer (i.e. based on an lstat() call).
   **/
  KFileInfo(const QString &filenameWithoutPath, struct stat *statInfo,
            KDirInfo *parent = nullptr);

  /**
   * Constructor from a KFileItem, i.e. from a @ref KIO::StatJob
   **/
  KFileInfo(const KFileItem *fileItem, KDirInfo *parent = nullptr);

  /**
   * Constructor from the bare neccessary fields
   * for use from a cache file reader
   *
   * If 'blocks' is -1, it will be calculated from 'size'.
   **/
  KFileInfo(KDirInfo *parent,
            const QString &filenameWithoutPath, mode_t mode, KFileSize size,
            time_t mtime, KFileSize blocks = -1, nlink_t links = 1);

  virtual ~KFileInfo() {};

  /**
   * Returns whether or not this is a local file (protocol "file:").
   * It might as well be a remote file ("ftp:", "smb:" etc.).
   **/
  bool isLocalFile() const { return _isLocalFile; }

  /**
   * Returns the file or directory name without path, i.e. only the last
   * path name component (i.e. "printcap" rather than "/etc/printcap").
   *
   * If a directory scan doesn't begin at the root directory and this is
   * the top entry of this directory scan it will also contain the base
   * path and maybe the protocol (for remote files),
   * i.e. "/usr/share/man" rather than just "man" if a scan was requested
   * for "/usr/share/man". Notice, however, that the entry for
   * "/usr/share/man/man1" will only return "man1" in this example.
   **/
  QString name() const { return _name; }

  /**
   * Returns the full URL of this object with full path and protocol
   * (unless the protocol is "file:").
   *
   * This is a (somewhat) expensive operation since it will recurse up
   * to the top of the tree.
   **/
  QString url() const;

  /**
   * Very much like @ref KFileInfo::url(), but with "/<Files>" appended
   * if this is a dot entry. Useful for debugging.
   * Notice: You can simply use the @ref kdbgstream operator<< to
   * output exactly this:
   *
   * kdDebug() << "Found fileInfo " << info << endl;
   **/
  QString debugUrl() const;

  /**
   * Returns part no. "level" of this object's URL, i.e. traverses up the
   * tree until this tree level is reached and returns this predecessor's
   * @ref name() . This is useful for tree searches in symmetrical trees
   * to find an item's counterpart in the other tree.
   **/
  QString urlPart(int level) const;

  /**
   * Returns the major and minor device numbers of the device this file
   * resides on or 0 if this is a remote file.
   **/
  dev_t device() const { return _device; }

  /**
   * The file permissions and object type as returned by lstat().
   * You might want to use the repective convenience methods instead:
   * @ref isDir(), @ref isFile(), ...
   **/
  mode_t mode() const { return _mode; }

  /**
   * The number of hard links to this file. Relevant for size summaries
   * to avoid counting one file several times.
   **/
  nlink_t links() const { return _links; }

  /**
   * The file size in bytes. This does not take unused space in the last
   * disk block (cluster) into account, yet it is the only size all kinds
   * of info functions can obtain. This is also what most file system
   * utilities (like "ls -l") display.
   **/
  KFileSize byteSize() const { return _size; }

  /**
   * The number of bytes actually allocated on the file system. Usually
   * this will be more than @ref byteSize() since the last few bytes of a
   * file usually consume an additional cluster on the file system.
   *
   * In the case of sparse files, however, this might as well be
   * considerably less than @ref byteSize() - this means that this file
   * has "holes", i.e. large portions filled with zeros. This is typical
   * for large core dumps for example. The only way to create such a file
   * is to lseek() far ahead of the previous file size and then writing
   * data. Most file system utilities will however disregard the fact
   * that files are sparse files and simply allocate the holes as well,
   * thus greatly increasing the disk space consumption of such a
   * file. Only some few file system utilities like "cp", "rsync", "tar"
   * have options to handle this more graciously - but usually only when
   * specifically requested. See the respective man pages.
   **/
  KFileSize allocatedSize() const;

  /**
   * The file size, taking into account multiple links for plain files or
   * the true allocated size for sparse files. For plain files with
   * multiple links this will be size/no_links, for sparse files it is
   * the number of bytes actually allocated.
   *
   * This is only called in the treeview to display the size of individual
   * files. It's not used to compute the size of folders recursively.
   **/
  KFileSize size() const;

  /**
   * The file size in 512 byte blocks.
   **/
  KFileSize blocks() const { return _blocks; }

  /**
   * The modification time of the file (not the inode).
   **/
  time_t mtime() const { return _mtime; }

  /**
   * Returns the total size in bytes of this subtree.
   * Derived classes that have children should overwrite this.
   **/
  virtual KFileSize totalSize() { return allocatedSize() / _links; }

  /**
   * Returns the total number of children in this subtree, excluding this item.
   * Derived classes that have children should overwrite this.
   **/
  virtual int totalItems() { return 0; }

  /**
   * Returns the total number of subdirectories in this subtree,
   * excluding this item. Dot entries and "." or ".." are not counted.
   * Derived classes that have children should overwrite this.
   **/
  virtual int totalSubDirs() { return 0; }

  /**
   * Returns the total number of plain file children in this subtree,
   * excluding this item.
   * Derived classes that have children should overwrite this.
   **/
  virtual int totalFiles() { return 0; }

  /**
   * Returns the latest modification time of this subtree.
   * Derived classes that have children should overwrite this.
   **/
  virtual time_t latestMtime() { return _mtime; }

  /**
   * Returns 'true' if this had been excluded while reading.
   * Derived classes may want to overwrite this.
   **/
  virtual bool isExcluded() const { return false; }

  /**
   * Set the 'excluded' status.
   *
   * This default implementation silently ignores the value passed and
   * does nothing. Derived classes may want to overwrite this.
   **/
  virtual void setExcluded(bool excl) {
    ((void)excl);
    return;
  }

  /**
   * Returns whether or not this is a mount point.
   * Derived classes may want to overwrite this.
   **/
  virtual bool isMountPoint() { return false; }

  /**
   * Sets the mount point state, i.e. whether or not this is a mount
   * point.
   *
   * This default implementation silently ignores the value passed and
   * does nothing. Derived classes may want to overwrite this.
   **/
  virtual void setMountPoint(bool isMountPoint = true) {
    ((void)isMountPoint);
    return;
  }

  /**
   * Returns true if this subtree is finished reading.
   *
   * This default implementation always returns 'true';
   * derived classes should overwrite this.
   **/
  virtual bool isFinished() { return true; }

  /**
   * Returns true if this subtree is busy, i.e. it is not finished
   * reading yet.
   *
   * This default implementation always returns 'false';
   * derived classes should overwrite this.
   **/
  virtual bool isBusy() { return false; }

  /**
   * Returns the number of pending read jobs in this subtree. When this
   * number reaches zero, the entire subtree is done.
   * Derived classes that have children should overwrite this.
   **/
  virtual int pendingReadJobs() { return 0; }

  //
  // Tree management
  //

  /**
   * Returns a pointer to this entry's parent entry or 0 if there is
   * none.
   **/
  KDirInfo *parent() const { return _parent; }

  /**
   * Set the "parent" pointer.
   **/
  void setParent(KDirInfo *newParent) { _parent = newParent; }

  virtual size_t numChildren() const { return 0; }
  virtual KFileInfo * child(size_t) { return nullptr; }

  /**
   * Returns true if this entry has any children.
   **/
  bool hasChildren() const { return numChildren() > 0 || dotEntry(); }

  /**
   * Returns true if this entry is in subtree 'subtree', i.e. if this is
   * a child or grandchild etc. of 'subtree'.
   **/
  bool isInSubtree(const KFileInfo *subtree) const;

  /**
   * Locate a child somewhere in this subtree whose URL (i.e. complete
   * path) matches the URL passed. Returns 0 if there is no such child.
   *
   * Notice: This is a very expensive operation since the entire subtree
   * is searched recursively.
   *
   * Derived classes might or might not wish to overwrite this method;
   * it's only advisable to do so if a derived class comes up with a
   * different method than brute-force search all children.
   *
   * 'findDotEntries' specifies if locating "dot entries" (".../<Files>")
   * is desired.
   **/
  virtual KFileInfo *locate(QString url, bool findDotEntries = false);

  /**
   * Insert a child into the children list.
   *
   * The order of children in this list is absolutely undefined;
   * don't rely on any implementation-specific order.
   *
   * This default implementation does nothing.
   **/
  virtual void insertChild(KFileInfo *newChild) { NOT_USED(newChild); }

  /**
   * Return the "Dot Entry" for this node if there is one (or 0
   * otherwise): This is a pseudo entry that directory nodes use to store
   * non-directory children separately from directories. This way the end
   * user can easily tell which summary fields belong to the directory
   * itself and which are the accumulated values of the entire subtree.
   *
   * This default implementation always returns 0.
   **/
  virtual KDirInfo *dotEntry() const { return nullptr; }

  /**
   * Set a "Dot Entry". This makes sense for directories only.
   *
   * This default implementation does nothing.
   **/
  virtual void setDotEntry(KDirInfo *newDotEntry) { NOT_USED(newDotEntry); }

  /**
   * Returns true if this is a "Dot Entry".
   * See @ref dotEntry() for details.
   *
   * This default implementation always returns false.
   **/
  virtual bool isDotEntry() const { return false; }

  /**
   * Returns the tree level (depth) of this item.
   * The topmost level is 0.
   *
   * This is a (somewhat) expensive operation since it will recurse up
   * to the top of the tree.
   **/
  int treeLevel() const;

  /**
   * Notification that a child has been added somewhere in the subtree.
   *
   * This default implementation does nothing.
   **/
  virtual void childAdded(KFileInfo *newChild) { NOT_USED(newChild); }

  /**
   * Notification that a child is about to be deleted somewhere in the
   * subtree.
   **/
  virtual void deletingChild(KFileInfo *deletedChild) {
    NOT_USED(deletedChild);
  }

  /**
   * Get the current state of the directory reading process:
   *
   * This default implementation always returns KDirFinished.
   * Derived classes should overwrite this.
   **/
  virtual KDirReadState readState() const { return KDirFinished; }

  /**
   * Returns true if this is a @ref KDirInfo object.
   *
   * Don't confuse this with @ref isDir() which tells whether or not this
   * is a disk directory! Both should return the same, but you'll never
   * know - better be safe than sorry!
   *
   * This default implementation always returns 'false'. Derived classes
   * (in particular, those derived from @ref KDirInfo) should overwrite this.
   **/
  virtual bool isDirInfo() const { return false; }

  /**
   * @brief return true if allocatedSize() and byteSize() are different
   * enough to be reported in the tree view.
   */
  bool isSparseFile() const;

  //
  // File type / mode convenience methods.
  // These are simply shortcuts to the respective macros from
  // <sys/stat.h>.
  //

  /**
   * Returns true if this is a directory.
   **/
  bool isDir() const { return S_ISDIR(_mode) ? true : false; }

  /**
   * Returns true if this is a regular file.
   **/
  bool isFile() const { return S_ISREG(_mode) ? true : false; }

  /**
   * Returns true if this is a symbolic link.
   **/
  bool isSymLink() const { return S_ISLNK(_mode) ? true : false; }

  /**
   * Returns true if this is a (block or character) device.
   **/
  bool isDevice() const {
    return (S_ISBLK(_mode) || S_ISCHR(_mode)) ? true : false;
  }

  /**
   * Returns true if this is a block device.
   **/
  bool isBlockDevice() const { return S_ISBLK(_mode) ? true : false; }

  /**
   * Returns true if this is a block device.
   **/
  bool isCharDevice() const { return S_ISCHR(_mode) ? true : false; }

  /**
   * Returns true if this is a FIFO.
   **/
  bool isFifo() const { return S_ISFIFO(_mode) ? true : false; }

  /**
   * Returns true if this is a socket.
   **/
  bool isSocket() const { return S_ISSOCK(_mode) ? true : false; }

  /**
   * Returns true if this is a "special" file, i.e. a (block or character)
   * device, a FIFO (named pipe) or a socket.
   **/
  bool isSpecial() const {
    return (S_ISBLK(_mode) || S_ISCHR(_mode) || S_ISFIFO(_mode) ||
            S_ISSOCK(_mode))
               ? true
               : false;
  }

protected:
  // Data members.
  //
  // Keep this short in order to use as little memory as possible -
  // there will be a _lot_ of entries of this kind!

  QString _name;          // the file name (without path!)
  bool _isLocalFile : 1;  // flag: local or remote file?
  dev_t _device;          // device this object resides on
  mode_t _mode;           // file permissions + object type
  nlink_t _links;         // number of links
  KFileSize _size;        // size in bytes
  KFileSize _blocks;      // 512 bytes blocks
  time_t _mtime;          // modification time

  KDirInfo *_parent; // pointer to the parent entry
}; // class KFileInfo

//----------------------------------------------------------------------
//			       Static Functions
//----------------------------------------------------------------------

/**
 * Make a valid, fixed and cleaned URL from a (possibly dirty) URL or maybe
 * a path.
 **/
QUrl fixedUrl(const QString &dirtyUrl);

/**
 * Format a file / subtree size human readable, i.e. in "GB" / "MB"
 * etc. rather than huge numbers of digits.
 *
 * Note: For kdDebug() etc., operator<< is overwritten to do exactly that:
 *
 *	   kdDebug() << "Size: " << x->totalSize() << endl;
 **/
QString formatSize(KFileSize lSize);

/**
 * Print the debugUrl() of a @ref KFileInfo in a debug stream.
 **/
inline QDebug &operator<<(QDebug &stream, const KFileInfo *info) {
  if (info)
    stream << info->debugUrl();
  else
    stream << "<NULL>";

  return stream;
}

/**
 * Human-readable output of a file size in a debug stream.
 **/
inline QDebug &operator<<(QDebug &stream, KFileSize lSize) {
  stream << formatSize(lSize);

  return stream;
}

} // namespace KDirStat

