#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
                                ; /* Ignore event */
                        }
                        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
