#include "dir.hpp"
#include <fcntl.h>
#include <dirent.h>
#include <algorithm>
#include <fnmatch.h>


static const char* ignore[] = {
    NULL
};


bool matches(const char** list, const char* fn) {
    for (const char** p=list; *p; ++p) {
        if (fnmatch(*p, fn, FNM_NOESCAPE) == 0) return true;
    }
    return false;
}


bool DirEntries::before(const Ent* a, const Ent* b) {
    if (a->type != b->type) {
        if (a->type == Ent::TYPE_DIR || b->type == Ent::TYPE_DIR) return b->type == Ent::TYPE_DIR; // files first
    }
    return strcmp(a->name, b->name) < 0; // utf8 'compatible'? TODO: ignore case?
}


DirEntries* DirEntries::getInst(int dfd) {
    int fd = dup(dfd);
    if (fd == -1) {
        LOG_ERRNO("dup(%d)", dfd);
        return NULL;
    }
    DIR* dp = fdopendir(fd);
    if (!dp) {
        LOG_ERRNO("fdopendir(%d)", fd);
        close(fd);
        return NULL;
    }

    DirEntries* rv = new DirEntries();

    //rewinddir(dp);
    while (true) {
        struct dirent* de = readdir(dp);
        if (!de) {
            break;
        }
        if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) {
            continue;
        }
        if (matches(ignore, de->d_name)) {
            //LOG("skipping '%s'", de->d_name);
            (void)rv->entries.push_back(new Ent(de->d_name));
            continue;
        }

        struct stat ss;
        bool have_ss = false;
        if (de->d_type == DT_UNKNOWN) {
            if (fstatat(dfd, de->d_name, &ss, AT_SYMLINK_NOFOLLOW) == -1) {
                LOG_ERRNO("fstatat(%s)", de->d_name);
            } else {
                have_ss = true;
            }
            if (S_ISDIR(ss.st_mode)) {
                de->d_type = DT_DIR;
            } else if (S_ISREG(ss.st_mode)) {
                de->d_type = DT_REG;
            }
        }

        Ent* e = NULL;
        if (de->d_type == DT_DIR) {
            e = Ent::getInst(dfd, de->d_name, true, have_ss? &ss: NULL);
        } else if (de->d_type == DT_REG) {
            e = Ent::getInst(dfd, de->d_name, false, have_ss? &ss: NULL);
        } else {
            //LOG("skipping irregular file '%s'", de->d_name); // really want to skip? maybe we do not want to hide them to detect conflicts
        }
        if (!e) {
            e = new Ent(de->d_name);
        }

#if 0
        (void)rv->entries.push_back(e);
    }
    std::sort(rv->entries.begin(), rv->entries.end(), &after); // s.t. we can pop from back
#else
        std::vector<Ent*>::iterator eit = rv->entries.begin();
        for (; eit != rv->entries.end(); ++eit) {
            if (after(e, *eit)) break; // reverse ordering, can pop lowest ('a') from back
        }
        (void)rv->entries.insert(eit, e); // inserts before
    }
#endif

    closedir(dp); // A successful call to closedir() also closes the underlying file descriptor associated with dirp (that's also why we dup'ed)
    return rv;
}


DirEntries::~DirEntries() {
    for (std::vector<Ent*>::iterator it = entries.begin(); it != entries.end(); it++) {
        delete *it;
    }
}


Ent* DirEntries::pop() {
    if (!entries.size()) {
        return NULL;
    }
    Ent* rv = entries.back();
    entries.pop_back();
    return rv;
}


void DirEntries::unpop(Ent* e) {
    assert(!entries.size() || before(e, entries.back()));
    entries.push_back(e);
}


Dir::Dir(int f, DirEntries* e, Attr* a): fd(f), attr(a), entries(e) {
    assert(fd != -1);
    assert(entries);
    assert(attr);
}


Dir::~Dir() {
    close(fd);
    delete entries;
    delete attr;
}


Dir* Dir::getInst(const char* dn, Dir* parent) {
    int fd = parent? openat(parent->fd, dn, O_DIRECTORY|O_NOFOLLOW|O_RDONLY): open(dn, O_DIRECTORY|O_NOFOLLOW|O_RDONLY);
    if (fd == -1) {
        LOG_ERRNO("open(%s, DIR)", dn);
        return NULL;
    }

    Attr* attr = Attr::getInst(fd);
    if (!attr) {
        close(fd);
        return NULL;
    }

    DirEntries* entries = DirEntries::getInst(fd);
    if (!entries) {
        delete attr;
        close(fd);
        return NULL;
    }

    return new Dir(fd, entries, attr);
}


DirStack::DirStack(const char* dn) {
    (void)push(dn);
}


DirStack::~DirStack() {
    while (st.size()) {
        pop();
    }
}


Dir* DirStack::push(const char* dn) {
    Dir* rv = Dir::getInst(dn, get());
    if (rv) {
        st.push(rv);
    }
    return rv;
}


Dir* DirStack::get() {
    return st.size()? st.top(): NULL;
}


void DirStack::pop() {
    assert(st.size());
    delete st.top();
    st.pop();
}