#ifndef WIN32

#include "dcinotify.h"

#include <sys/inotify.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/select.h>
#include <fcntl.h>
#include <unistd.h>

#include <QTextCodec>
#include <QList>
#include <QStringList>
#include <QFile>

//#include <QtDebug>

#define EVENT_SIZE  ( sizeof (struct inotify_event) )
#define BUF_LEN     ( 512 * ( EVENT_SIZE + 16 ) )

DCInotify::DCInotify() {
    recursive = false;

    setInterval(60);
}

DCInotify::~DCInotify() {
    QMapIterator<QDir*, int> it(dirs);

    while (it.hasNext()) {
        it.next();

        inotify_rm_watch(ino_fd, it.value());

        delete it.key();
    }
    close(ino_fd);
}

void DCInotify::run() {

    if (dirs.empty())
        terminate();

    //qDebug() << "DCInotify::run()";
    //qDebug() << "\tinterval=" << getInterval();

    fd_set readfds;
    int err = 0, length = 0, i = 0;
    char buffer[BUF_LEN];
    QTextCodec *codec = QTextCodec::codecForName("UTF-8");

    FD_ZERO(&readfds);
    FD_SET(ino_fd, &readfds);

    while (true) {

        err = select(100, &readfds, NULL, NULL, 0);

        if (err > 0) {
            length = read(ino_fd, buffer, BUF_LEN);

            if (length < 0)
                continue;

            while (i < length) {
                struct inotify_event *event = (struct inotify_event*) & buffer[i];

                if (event->len) {
                    InotifyObject *obj = new InotifyObject;
                    bool in_q = false;

                    obj->name = codec->toUnicode(event->name);
                    QMapIterator<QDir*, int> it(dirs);

                    while (it.hasNext()) {
                        it.next();

                        if (it.value() == event->wd) {
                            obj->name = (it.key())->absolutePath() + (it.key())->separator() + obj->name; //absolute file/dir. path
                            break;
                        }
                    }

                    if (!wait_q.empty()) {
                        InotifyObject *w_obj;

                        for (int j = 0; j < wait_q.size(); j++) {
                            w_obj = wait_q.at(j);

                            if (w_obj->name == obj->name) {
                                delete obj;
                                w_obj = wait_q.takeAt(j);
                                obj = w_obj;
                                in_q = true;
                                break;
                            }
                        }
                    }

                    //qDebug() << "\tDCInotify::run()::New object " << obj->name;

                    if (event->mask & IN_ISDIR)
                        obj->event_obj = eIDIRECTORY;
                    else
                        obj->event_obj = eIFILE;

                    if (event->mask & IN_CREATE) {
                        //qDebug() << "\tIN_CREATE";

                        if (obj->event_obj == eIDIRECTORY) {
                            QDir *dir = new QDir(obj->name);

                            if (!dir->exists(obj->name)) {
                                delete dir;
                                return;
                            }

                            int wd = inotify_add_watch(ino_fd,
                                    obj->name.toAscii().data(),
                                    IN_CREATE | IN_DELETE | IN_CLOSE_WRITE | IN_MOVE | IN_MODIFY
                                    );

                            dirs[dir] = wd;

                            if (recursive) {

                                QStringList list = dir->entryList(QDir::Dirs | QDir::NoDotAndDotDot);

                                for (int i = 0; i < list.size(); i++) {
                                    addSubDirs(dir->absoluteFilePath(list.at(i)));
                                }
                            }

                            break;
                        } else {
                            QFile file(obj->name);

                            if (file.size() > 0) {
                                //qDebug() << "Created file with size " << file.size();
                                emit fs_event(obj);
                            } else {
                                //qDebug() << "Object " << obj->name << " added in wait quique";
                                wait_q << obj;
                            }
                        }
                    } else if (event->mask & IN_DELETE) {
                        //qDebug() << "\tIN_DELETE";
                        if (!in_q) {
                            obj->event_type = eIDELETE;
                            emit fs_event(obj);
                        } else
                            delete obj;
                    } else if (event->mask & IN_MODIFY) {
                        //qDebug() << "\tIN_MODIFY";
                        if (!in_q) {
                            //qDebug() << obj->name << " not quique. emit signal with eICHANGE";
                            obj->event_type = eICHANGE;
                            emit fs_event(obj);
                        } else {
                            //qDebug() << obj->name << " in quique. emit signal with eICREATE";
                            obj->event_type = eICREATE;
                            emit fs_event(obj);
                        }
                    } else if (event->mask & IN_CLOSE_WRITE) {

                        if (in_q) {
                            obj->event_type = eICREATE;
                            emit fs_event(obj);
                        }
                    } else if (event->mask & IN_MOVE) {
                        //qDebug() << "\tIN_MOVE";
                        obj->event_type = eIMOVE;
                        emit fs_event(obj);
                    } else {
                        delete obj;
                    }

                }

                i += EVENT_SIZE + event->len;

            }

            i = 0;

        }

        usleep(getInterval()*1000 * 1000);
    }
}

void DCInotify::addDirectory(QString path) {
    if (isRunning())
        return;

    QDir *dir = new QDir(path);
    dir->setFilter(QDir::NoDotAndDotDot);
    dir->setFilter(dir->filter() & (~QDir::Hidden));

    if (!dir->exists(path)) {
        delete dir;
        return;
    }

    int wd = inotify_add_watch(ino_fd,
            path.toAscii().data(),
            IN_CREATE | IN_DELETE | IN_CLOSE_WRITE | IN_MOVE | IN_MODIFY
            );

    dirs[dir] = wd;

    if (!recursive)
        return;

    QStringList list = dir->entryList(QDir::Dirs | QDir::NoDotAndDotDot);

    for (int i = 0; i < list.size(); i++) {
        addSubDirs(dir->absoluteFilePath(list.at(i)));
    }
}

bool DCInotify::init_inotify() {
    if (isRunning())
        return false;

    ino_fd = inotify_init();

    return (ino_fd > 0);
}

void DCInotify::addSubDirs(QString path) {
    QDir *dir = new QDir(path);

    if (!dir->exists())
        return;

    int wd = inotify_add_watch(ino_fd,
            path.toAscii().data(),
            IN_CREATE | IN_DELETE | IN_CLOSE_WRITE | IN_MOVE | IN_MODIFY
            );

    dirs[dir] = wd;

    QStringList list = dir->entryList(QDir::Dirs | QDir::NoDotAndDotDot);

    for (int i = 0; i < list.size(); i++) {
        addSubDirs(dir->absoluteFilePath(list.at(i)));
    }
}

void DCInotify::setRecursive(bool rec) {
    if (isRunning())
        return;

    recursive = rec;
}

void DCInotify::setInterval(int secs) {
    if (isRunning())
        return;

    mutex.lock();

    shot_c = secs;

    mutex.unlock();

}

int DCInotify::getInterval() {
    mutex.lock();

    int c = shot_c;

    mutex.unlock();

    return c;
}

#endif //WIN32
