/*****************************************************************************
 * Copyright (C) 2004 Max Howell <max.howell@methylblue.com>                 *
 * Copyright (C) 2004-2020 Krusader Krew [https://krusader.org]              *
 *                                                                           *
 * This file is part of Krusader [https://krusader.org].                     *
 *                                                                           *
 * Krusader is free software: you can redistribute it and/or modify          *
 * it under the terms of the GNU General Public License as published by      *
 * the Free Software Foundation, either version 2 of the License, or         *
 * (at your option) any later version.                                       *
 *                                                                           *
 * Krusader 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 General Public License for more details.                              *
 *                                                                           *
 * You should have received a copy of the GNU General Public License         *
 * along with Krusader.  If not, see [http://www.gnu.org/licenses/].         *
 *****************************************************************************/

#ifndef FILETREE_H
#define FILETREE_H

#include <QString>

#include <KIO/Global>

#include <time.h>
#include <stdlib.h>
#include <unistd.h>

// TODO these are pointlessly general purpose now, make them incredibly specific

typedef KIO::filesize_t FileSize;

template <class T> class Iterator;
template <class T> class ConstIterator;
template <class T> class Chain;

template <class T>
class Link
{
public:
    explicit Link(T* const t) : prev(this), next(this), data(t) {}
    Link() : prev(this), next(this), data(0) {}

    //TODO unlinking is slow and you don't use it very much in this context.
    //  ** Perhaps you can make a faster deletion system that doesn't bother tidying up first
    //  ** and then you MUST call some kind of detach() function when you remove elements otherwise
    ~Link() {
        delete data; unlink();
    }

    friend class Iterator<T>;
    friend class ConstIterator<T>;
    friend class Chain<T>;

private:
    void unlink() {
        prev->next = next; next->prev = prev; prev = next = this;
    }

    Link<T>* prev;
    Link<T>* next;

    T* data; //ensure only iterators have access to this
};


template <class T>
class Iterator
{
public:
    Iterator() : link(0) { }   //**** remove this, remove this REMOVE THIS!!! dangerous as your implementation doesn't test for null links, always assumes they can be dereferenced
    explicit Iterator(Link<T> *p) : link(p) { }

    bool operator==(const Iterator<T>& it) const {
        return link == it.link;
    }
    bool operator!=(const Iterator<T>& it) const {
        return link != it.link;
    }
    bool operator!=(const Link<T> *p) const {
        return p != link;
    }

    //here we have a choice, really I should make two classes one const the other not
    const T* operator*() const {
        return link->data;
    }
    T* operator*() {
        return link->data;
    }

    Iterator<T>& operator++() {
        link = link->next; return *this;
    } //**** does it waste time returning in places where we don't use the retval?

    bool isNull() const {
        return (link == 0);
    } //REMOVE WITH ABOVE REMOVAL you don't want null iterators to be possible

    void transferTo(Chain<T> &chain) {
        chain.append(remove());
    }

    T* remove() { //remove from list, delete Link, data is returned NOT deleted
        T* const d = link->data;
        Link<T>* const p = link->prev;

        link->data = 0;
        delete link;
        link = p; //make iterator point to previous element, YOU must check this points to an element

        return d;
    }

private:
    Link<T> *link;
};


template <class T>
class ConstIterator
{
public:
    explicit ConstIterator(Link<T> *p) : link(p) { }

    bool operator==(const Iterator<T>& it) const {
        return link == it.link;
    }
    bool operator!=(const Iterator<T>& it) const {
        return link != it.link;
    }
    bool operator!=(const Link<T> *p) const {
        return p != link;
    }

    const T* operator*() const {
        return link->data;
    }

    ConstIterator<T>& operator++() {
        link = link->next; return *this;
    }

private:
    const Link<T> *link;
};

//**** try to make a generic list class and then a brief full list template that inlines
//     thus reducing code bloat
template <class T>
class Chain
{
public:
    Chain() { }
    virtual ~Chain() {
        empty();
    }

    void append(T* const data) {
        Link<T>* const link = new Link<T>(data);

        link->prev = head.prev;
        link->next = &head;

        head.prev->next = link;
        head.prev = link;
    }

    void transferTo(Chain &c) {
        if (isEmpty()) return;

        Link<T>* const first = head.next;
        Link<T>* const last  = head.prev;

        head.unlink();

        first->prev = c.head.prev;
        c.head.prev->next = first;

        last->next = &c.head;
        c.head.prev = last;
    }

    void empty() {
        while (head.next != &head) {
            delete head.next;
        }
    }

    Iterator<T>      iterator() const {
        return Iterator<T>(head.next);
    }
    ConstIterator<T> constIterator() const {
        return ConstIterator<T>(head.next);
    }
    const Link<T>   *end() const {
        return &head;
    }
    bool             isEmpty() const {
        return (head.next == &head);
    }

private:
    Link<T> head;
    void operator=(const Chain&) {}
};


class Directory;

class File
{
protected:
    Directory        *m_parent;   // 0 if this is treeRoot
    QString           m_name;     //< file name
    QString           m_directory;//< the directory of the file
    FileSize          m_size;     //< size with subdirectories
    FileSize          m_ownSize;  //< size without subdirectories
    mode_t            m_mode;     //< file mode
    QString           m_owner;    //< file owner name
    QString           m_group;    //< file group name
    QString           m_perm;     //< file permissions string
    time_t            m_time;     //< file modification in time_t format
    bool              m_symLink;  //< true if the file is a symlink
    QString           m_mimeType; //< file mimetype
    bool              m_excluded; //< flag if the file is excluded from du
    int               m_percent;  //< percent flag

public:
    File(Directory *parentIn, const QString &nameIn, const QString &dir, FileSize sizeIn, mode_t modeIn,
         const QString &ownerIn, const QString &groupIn, const QString &permIn, time_t timeIn, bool symLinkIn,
         const QString &mimeTypeIn)
            : m_parent(parentIn), m_name(nameIn), m_directory(dir), m_size(sizeIn), m_ownSize(sizeIn), m_mode(modeIn),
            m_owner(ownerIn), m_group(groupIn), m_perm(permIn), m_time(timeIn), m_symLink(symLinkIn),
            m_mimeType(mimeTypeIn), m_excluded(false), m_percent(-1) {}

    File(const QString &nameIn, FileSize sizeIn)
            : m_parent(0), m_name(nameIn), m_directory(QString()), m_size(sizeIn), m_ownSize(sizeIn), m_mode(0),
            m_owner(QString()), m_group(QString()), m_perm(QString()), m_time(-1),
            m_symLink(false), m_mimeType(QString()), m_excluded(false), m_percent(-1) {
    }

    virtual ~File() {}

    inline const QString &  name()                const  {
        return m_name;
    }
    inline const QString &  directory()           const  {
        return m_directory;
    }
    inline FileSize         size()                const  {
        return m_excluded ? 0 : m_size;
    }
    inline FileSize         ownSize()             const  {
        return m_excluded ? 0 : m_ownSize;
    }
    inline mode_t           mode()                const  {
        return m_mode;
    }
    inline const QString &  owner()               const  {
        return m_owner;
    }
    inline const QString &  group()               const  {
        return m_group;
    }
    inline const QString &  perm()                const  {
        return m_perm;
    }
    inline time_t           time()                const  {
        return m_time;
    }
    inline const QString &  mime()                const  {
        return m_mimeType;
    }
    inline bool             isSymLink()           const  {
        return m_symLink;
    }
    virtual bool            isDir()               const  {
        return false;
    }
    inline bool             isExcluded()          const  {
        return m_excluded;
    }
    inline void             exclude(bool flag)         {
        m_excluded = flag;
    }
    inline int              intPercent()          const  {
        return m_percent;
    }
    inline const QString    percent()             const  {
        if (m_percent < 0)
            return "INV";
        QString buf;
        buf.asprintf("%d.%02d%%", m_percent / 100, m_percent % 100);
        return buf;
    }
    inline void             setPercent(int p)          {
        m_percent = p;
    }
    inline const Directory* parent()              const  {
        return m_parent;
    }

    inline void setSizes(KIO::filesize_t totalSize, KIO::filesize_t ownSize) {
        m_ownSize = ownSize;
        m_size = totalSize;
    }

    enum UnitPrefix { kilo, mega, giga, tera };

    static const FileSize DENOMINATOR[4];
    static const char PREFIX[5][2];

    QString fullPath(const Directory* = 0) const;
    QString humanReadableSize(UnitPrefix key = mega) const;

    static QString humanReadableSize(FileSize size, UnitPrefix Key = mega);

    friend class Directory;
};


//TODO when you modify this to take into account hardlinks you should make the Chain layered not inherited
class Directory : public Chain<File>, public File
{
public:
    Directory(Directory *parentIn, const QString &nameIn, const QString &dir, FileSize sizeIn, mode_t modeIn,
              const QString &ownerIn, const QString &groupIn, const QString &permIn, time_t timeIn, bool symLinkIn,
              const QString &mimeTypeIn)
            : File(parentIn, nameIn, dir, sizeIn, modeIn, ownerIn, groupIn, permIn, timeIn, symLinkIn, mimeTypeIn),
            m_fileCount(0) {}

    Directory(const QString &name, QString url) : File(name, 0), m_fileCount(0) {
        m_directory = url;
    }

    virtual ~Directory() {}
    virtual bool isDir() const override {
        return true;
    }

    void append(File *p) {
        ++m_fileCount;

        Directory *parent = m_parent;
        while (parent) {
            parent->m_fileCount++;
            parent = parent->m_parent;
        }

        Chain<File>::append(p);
        p->m_parent = this;
    }

    void remove(File *p) {
        for (Iterator<File> it = Chain<File>::iterator(); it != Chain<File>::end(); ++it)
            if ((*it) == p) {
                --m_fileCount;

                Directory *parent = m_parent;
                while (parent) {
                    parent->m_fileCount--;
                    parent = parent->m_parent;
                }

                it.remove();
                break;
            }
    }

    uint fileCount() const {
        return m_fileCount;
    }

private:
    Directory(const Directory&);
    void operator=(const Directory&);

    uint m_fileCount;
};

#endif
