/* Blinker
   Copyright 2011-2019 Stefan Schuermans <stefan@blinkenarea.org>
   Copyleft GNU public license - http://www.gnu.org/copyleft/gpl.html
   a blinkenarea.org project */

#ifndef BLINKER_LISTTRACKER_IMPL_H
#define BLINKER_LISTTRACKER_IMPL_H

#include <list>
#include <string>

#include "Directory.h"
#include "File.h"
#include "ListTracker.h"

namespace Blinker {

/* ###################
   # ListTrackerFiDi #
   ################### */


/// internal helper class to achive different behavior for FIDI parameter
template <typename FIDI>
class ListTrackerFiDi
{
public:
  static inline Directory::Type type();
  static inline FIDI get(const Directory &dir, const std::string name);
};

template<>
inline Directory::Type ListTrackerFiDi<File>::type()
{
  return Directory::TypeFile;
}

template<>
inline File ListTrackerFiDi<File>::
  get(const Directory &dir, const std::string name)
{
  return dir.getFile(name);
}

template<>
inline Directory::Type ListTrackerFiDi<Directory>::type()
{
  return Directory::TypeSubdir;
}

template<>
inline Directory ListTrackerFiDi<Directory>::
  get(const Directory &dir, const std::string name)
{
  return dir.getSubdir(name);
}

/* ###############
   # ListTracker #
   ############### */

/**
 * @brief constructor
 * @param[in] dir directory containing list and begin tracked
 * @param[in] parent parent object
 */
template<typename PARENT, typename TYPE, typename FIDI>
ListTracker<PARENT, TYPE, FIDI>::ListTracker(PARENT &parent,
                                             const Directory &dir):
  m_parent(parent),
  m_dir(dir)
{
}

/// destructor
template<typename PARENT, typename TYPE, typename FIDI>
ListTracker<PARENT, TYPE, FIDI>::~ListTracker()
{
  /* free leftover list entries:
     Depending on the interaction bet ween the list entry object and
     the parent, it might be too late to free them, as the parent might
     already be partly deconstructed and thus might be in an inconsistent
     state. It's recommended to call clear manually before, so that the
     following call becomes a no-operation. */
  clear();
}

/// initialize list by reading directory
template<typename PARENT, typename TYPE, typename FIDI>
void ListTracker<PARENT, TYPE, FIDI>::init()
{
  updateListFull();
}

/// clear list
template<typename PARENT, typename TYPE, typename FIDI>
void ListTracker<PARENT, TYPE, FIDI>::clear()
{
  while (!m_list.empty()) {
    delete m_list.back().m_pObj;
    m_list.pop_back();
  }
}

/// check for update of configuration
template<typename PARENT, typename TYPE, typename FIDI>
void ListTracker<PARENT, TYPE, FIDI>::updateConfig()
{
  // directory modified -> full, otherwise -> light
  if (m_dir.checkModified())
    updateListFull();
  else
    updateListLight();
}

/// light update of list, i.e. check all entries in current list
template<typename PARENT, typename TYPE, typename FIDI>
void ListTracker<PARENT, TYPE, FIDI>::updateListLight()
{
  typename List::iterator it;
  for (it = m_list.begin(); it != m_list.end(); ++it)
    it->m_pObj->updateConfig();
}

/// full update of list, i.e. scan files/subdirs in list directory
template<typename PARENT, typename TYPE, typename FIDI>
void ListTracker<PARENT, TYPE, FIDI>::updateListFull()
{
  // get list of files/subdirs in directory
  typedef std::list<std::string> FileList;
  FileList curFiles;
  m_dir.getEntries(ListTrackerFiDi<FIDI>::type(), curFiles);

  // walk through current list and file/subdir list simultaneously
  FileList::const_iterator itFile = curFiles.begin();
  typename List::iterator  it     = m_list.begin();
  while (itFile != curFiles.end() || it != m_list.end()) {

    // new entry inserted
    if (it == m_list.end() ||
        (itFile != curFiles.end() && *itFile < it->m_name)) {
      // create object
      Entry entry;
      entry.m_name = *itFile;
      entry.m_pObj = new TYPE(m_parent, *itFile,
                              ListTrackerFiDi<FIDI>::get(m_dir, *itFile));
      if (entry.m_pObj)
        // insert list entry
        m_list.insert(it, entry);
      // advance to next file/subdir
      ++itFile;
    }

    // entry removed
    else if (itFile == curFiles.end() || *itFile > it->m_name) {
      // remove entry
      delete it->m_pObj;
      it = m_list.erase(it);
      // do not advance to next file/subdir
    }

    // entry stayed in list
    else {
      // check for update
      it->m_pObj->updateConfig();
      // advance to next file/subdir and next entry
      ++itFile;
      ++it;
    }

  } // while itFile it
}

} // namespace Blinker

#endif // #ifndef BLINKER_LISTTRACKER_IMPL_H

