// $Header: https://oberon.oapd.inaf.it/shins/trunk/shins/src/util/inotifier.cpp 2404 2020-02-17 15:46:08Z depa $
#include "inotifier.h"
#define EVENT_SIZE          ( sizeof (struct inotify_event) )
#define EVENT_BUF_LEN       ( 1024 * ( EVENT_SIZE + NAME_MAX + 1) )
#define WATCH_FLAGS         ( IN_CREATE )

namespace shins{
  namespace util{

    int INotifier::start_second_thread(std::string _pathToFile, const long _timeOutMsec)
    {
      int status = EXIT_SUCCESS;
      std::string tmpOutFile;
      INotifier local_Inotify_instance;
      boost::thread fits_notify_thread(local_Inotify_instance.start, _pathToFile,
                                       boost::ref(tmpOutFile),
                                       _timeOutMsec); // create and start thread
      fits_notify_thread.join();                      // Wait for thread to finish
      if (tmpOutFile.empty())
        {
          status = EXIT_FAILURE;
        }
      return status;
    }

    void INotifier::start(std::string _pathToFile, std::string& _outFile,
                          const long _timeOutMsec) {
      bool run = true;
      // std::map used to keep track of wd (watch descriptors) and directory names
      // As directory creation events arrive, they are added to the Watch map.
      // Directory delete events should be (but currently aren't in this sample) handled the same way.
      Watch watch;

      // watch_set is used by select to wait until inotify returns some data to
      // be read using non-blocking read.
      fd_set watch_set;
      timeval timeOut;
      timeOut.tv_sec = 0;
      timeOut.tv_usec = _timeOutMsec; // microsecods

      char buffer[ EVENT_BUF_LEN ];
      const char* root = _pathToFile.c_str();
      std::string current_dir;
      std::string new_dir;
      std::stringstream outStringStream;
      int total_file_events = 0;
      int total_dir_events = 0;
      int fd;
      int wd;
      int length;
      int ready;
      boost::filesystem::path p(_pathToFile);
      // Call sig_callback if user hits ctrl-
      // signal( SIGINT, INotify::sig_callback );

      // creating the INOTIFY instance
      // inotify_init1 not available with older kernels, consequently inotify reads block.
      // inotify_init1 allows directory events to complete immediately, avoiding buffering delays. In practice,
      // this significantly improves monotiring of newly created subdirectories.
#ifdef IN_NONBLOCK
      fd = inotify_init1( IN_NONBLOCK );
#else
      fd = inotify_init();
#endif

      // checking for error
      if ( fd < 0 ) {
        perror( "inotify_init" );
      }

      // use select watch list for non-blocking inotify read
      FD_ZERO( &watch_set );
      FD_SET( fd, &watch_set );

      // add _pathToFile to watch list. Normally, should check directory exists first
      //const char *root = _pathToFile.c_str();
      std::cout << "I am thread " <<  boost::this_thread::get_id() << std::endl;
      std::cout << _pathToFile << std::endl;
      wd = inotify_add_watch( fd, root, WATCH_FLAGS );

      while (wd < 0){
        std::cout << std::strerror(errno) << std::endl;
        if (errno == ENOENT){
          std::cout << "start parallel thread, this thread blocks ..." << std::endl; //  should sleep or wait on lock
          if (start_second_thread(p.parent_path().string(), _timeOutMsec) == EXIT_FAILURE)
            {
              run = false;
            }
          else
            {
              wd = inotify_add_watch( fd, root, WATCH_FLAGS );
            }
          std::cout << "back to original thread!" << std::endl;
        }
      }

      if (wd)
        {
          // add wd and directory name to Watch map
          watch.insert( -1, root, wd );
        }

      // Continue until run == false. See signal and sig_callback above.
      while ( run ) {
        timeOut.tv_sec = 0;
        timeOut.tv_usec = _timeOutMsec;
        // select waits until inotify has 1 or more events.
        // select syntax is beyond the scope of this sample but, don't worry, the fd+1 is correct:
        // select needs the the highest fd (+1) as the first parameter.
        std::cout << "waiting on select " << timeOut.tv_usec << std::endl;
        select( fd+1, &watch_set, NULL, NULL, &timeOut );
        if ( timeOut.tv_sec+timeOut.tv_usec == 0)
          {
            perror("select");
            std::cout << std::strerror(errno) << std::endl;
            _outFile = "Unix select command went timeout";
            run = false;
            // throw only when able to pass errors between threads;
            // see boost::future or boost::exception
            // throw fits_missing("FITS file missing after timeout");

            // boost::this_thread::interrupt();
            // std::exit(1);
            break;
          }

        // Read event(s) from non-blocking inotify fd (non-blocking specified in inotify_init1 above).
        length = read( fd, buffer, EVENT_BUF_LEN );
        if ( length < 0 )
          {
            perror( "read" );
          }

        // Loop through event buffer
        for ( int i=0; i<length; ) {
          struct inotify_event *event = ( struct inotify_event * ) &buffer[ i ];
          // Never actually seen this
          if ( event->wd == -1 ) {
   	        printf( "Overflow\n" );
          }
          // Never seen this either
          if ( event->mask & IN_Q_OVERFLOW ) {
	      		printf( "Overflow\n" );
    	    }
          if ( event->len ) {
            if ( event->mask & IN_IGNORED ) {
              printf( "IN_IGNORED\n" );
            }
            if ( event->mask & IN_CREATE ) {
              current_dir = watch.get(event->wd);
              if ( event->mask & IN_ISDIR ) {
                new_dir = current_dir + "/" + event->name;
                wd = inotify_add_watch( fd, new_dir.c_str(), WATCH_FLAGS );
                watch.insert( event->wd, event->name, wd );
                total_dir_events++;
                printf( "New directory %s created.\n", new_dir.c_str() );
                _outFile = new_dir; 
                run = false;
              } else {
                total_file_events++;
                printf( "New file %s/%s created.\n", current_dir.c_str(), event->name );
                outStringStream << current_dir << event->name;
                _outFile = outStringStream.str();
                run = false;
              }
            } else if ( event->mask & IN_DELETE ) {
              if ( event->mask & IN_ISDIR ) {
                new_dir = watch.erase( event->wd, event->name, &wd );
                inotify_rm_watch( fd, wd );
                total_dir_events--;
                printf( "Directory %s deleted.\n", new_dir.c_str() );
              } else {
                current_dir = watch.get(event->wd);
                total_file_events--;
                printf( "File %s/%s deleted.\n", current_dir.c_str(), event->name );
              }
            }
          }
          i += EVENT_SIZE + event->len;
        }
      }

      // Cleanup
      printf( "cleaning up\n" );
      std::cout << "total dir events = " << total_dir_events << ", total file events = " << total_file_events << std::endl;
      watch.stats();
      watch.cleanup( fd );
      watch.stats();
      close( fd );
      fflush(stdout);
    }
  }
}
