/* nwc_dir.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2006-2017 Ralf Hoffmann.
 * You can contact me at: ralf@boomerangsworld.de
 *   or http://www.boomerangsworld.de/worker
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "nwc_dir.hh"
#include "nwc_file.hh"
#include "nwc_path.hh"
#include <iostream>
#include <typeinfo>
#include <algorithm>

namespace NWC
{

  Dir::Dir( const std::string &fullname,
            bool follow_symlinks ) : FSEntry( fullname ),
                                     _dirtype( VIRTUAL ),
                                     _follow_symlinks( follow_symlinks ),
                                     m_bytes_in_dir( -1 )
  {
    if ( getFullname()[0] == '/' )
      _dirtype = FS_DIR;
  }

  Dir::Dir( const FSEntry &other,
            bool follow_symlinks ) : FSEntry( other ),
                                     _dirtype( VIRTUAL ),
                                     _follow_symlinks( follow_symlinks ),
                                     m_bytes_in_dir( -1 )
  {
    if ( getFullname()[0] == '/' )
      _dirtype = FS_DIR;
  }

  Dir::Dir( const Dir &other ) : FSEntry( other ),
                                 _follow_symlinks( other._follow_symlinks ),
                                 m_bytes_in_dir( other.m_bytes_in_dir )
  {
    for ( std::vector<FSEntry*>::const_iterator it1 = other._subentries.begin();
          it1 != other._subentries.end(); it1++ ) {
      addEntry( **it1 );
    }
    _dirtype = other._dirtype;
  }

  Dir::~Dir()
  {
    clearLists();
  }

  int Dir::readDir( bool recursive )
  {
    DIR *dir;
    worker_struct_dirent *dirent;
    std::string str1, str2;
    int res = 0;

    if ( isVirtual() ) {
        for ( auto &t_entry : _subentries ) {
            if ( t_entry ) {
                t_entry->updateStats();
            }
        }
    } else {
        if ( entryExists() == true ) {
            clearLists();

            dir = worker_opendir( getFullname().c_str() );
            if ( dir != NULL ) {
                while ( ( dirent = worker_readdir( dir ) ) != NULL) {
                    str1 = dirent->d_name;
                    if ( str1 != ".." && str1 != "." ) {
                        str2 = Path::join( getFullname(), str1 );
                        handleDirEntry( str2 );
                    }
                }
                worker_closedir( dir );
            } else {
                res = 1;
            }

            _dirtype = FS_DIR_READ;
        }
    }
  
    if ( recursive == true ) {
      for ( std::vector<FSEntry*>::iterator it1 = _subentries.begin(); it1 != _subentries.end(); it1++ ) {
        if ( Dir *direntry = dynamic_cast<Dir*>( *it1 ) ) {
          direntry->readDir( recursive );
        }
      }
    }

    return res;
  }

  void Dir::handleDirEntry( const std::string &filename )
  {
    addEntry( filename );
  }

  int Dir::addEntry( const FSEntry &entry )
  {
    FSEntry *cloned = NULL;

    // keep in mind that it's possible that the entry exists but no stat
    // information can be obtained because of missing permissions
    // so exists will be false
    if ( typeid( entry ) == typeid( FSEntry ) ) {
      if ( entry.isDir( _follow_symlinks ) == true ) {
        cloned = createDir( entry );
      } else {
        cloned = createFile( entry );
      }
    } else {
      cloned = entry.clone();
    }
  
    if ( cloned != NULL ) {
      _subentries.push_back( cloned );
    }

    return 0;
  }

  void Dir::walkHandleEntry( FSEntry *entry, 
                             DirWalkCallBack &dircb, WalkControlObj &cobj,
                             const walk_t walk_mode )
  {
      if ( entry == NULL ) return;

      Dir *direntry = dynamic_cast<Dir*>( entry );

      if ( direntry != NULL ) {
          dircb.visitEnterDir( *direntry, cobj );

          if ( walk_mode != NORMAL && cobj.getMode() == WalkControlObj::NORMAL ) {
              bool read_and_clear = false;

              // if direntry exists but isn't real it means it's not read yet
              if ( direntry->entryExists() == true &&
                   direntry->isRealDir() == false &&
                   walk_mode == RECURSIVE_DEMAND )
                  read_and_clear = true;

              if ( read_and_clear == true )
                  direntry->readDir( false );

              cobj = direntry->walk( dircb, walk_mode );

              if ( read_and_clear == true )
                  direntry->clearLists();
          }

          // leaveDir will be called even when aborting to allow
          // some cleanup
          dircb.visitLeaveDir( *direntry, cobj );
      } else {
          File *dateientry = dynamic_cast<File*>( entry );
          if ( dateientry != NULL ) {
              dircb.visit( *dateientry, cobj );
          }
      }
  }

  Dir::WalkControlObj Dir::walk( DirWalkCallBack &dircb, walk_t walk_mode )
  {
    WalkControlObj cobj;
    std::list< RefCount< FSEntry > > additional_entries, skip_entries;

    dircb.prepareDirWalk( *this, cobj, additional_entries, skip_entries );

    for ( std::vector<FSEntry*>::iterator it1 = _subentries.begin();
          it1 != _subentries.end() && cobj.getMode() != WalkControlObj::ABORT;
          it1++ ) {

        bool skip = false;

        for ( std::list< RefCount< FSEntry > >::iterator it2 = skip_entries.begin();
              it2 != skip_entries.end();
              it2++ ) {
            //TODO this is rather slow
            if ( (*it2)->getFullname() == (*it1)->getFullname() ) {
                skip = true;
                break;
            }
        }

        if ( ! skip ) {
            walkHandleEntry( *it1, dircb, cobj, walk_mode );
        }

        if ( cobj.getMode() == WalkControlObj::SKIP )
            cobj.setMode( WalkControlObj::NORMAL );
    }

    for ( std::list< RefCount< FSEntry > >::iterator it1 = additional_entries.begin();
          it1 != additional_entries.end() && cobj.getMode() != WalkControlObj::ABORT;
          it1++ ) {

        walkHandleEntry( it1->get(), dircb, cobj, walk_mode );

        if ( cobj.getMode() == WalkControlObj::SKIP )
            cobj.setMode( WalkControlObj::NORMAL );
    }
    return cobj;
  }

  int Dir::add( std::unique_ptr<FSEntry> file )
  {
    FSEntry *fileP = file.release();

    if ( fileP == NULL ) return 1;

    makeVirtual();
  
    _subentries.push_back( fileP );
    return 0;
  }

  int Dir::add( const FSEntry &ent )
  {
    makeVirtual();
  
    return addEntry( ent );
  }

  int Dir::clearLists()
  {
    for ( std::vector<FSEntry*>::iterator it1 = _subentries.begin();
          it1 != _subentries.end();
          ++it1 ) {
      delete (*it1);
    }

    _subentries.clear();
  
    if ( getFullname()[0] == '/' )
      _dirtype = FS_DIR;
    else
      _dirtype = VIRTUAL;
  
    return 0;
  }

  File *Dir::createFile( const FSEntry &ent )
  {
    return new File( ent );
  }

  Dir *Dir::createDir( const FSEntry &ent )
  {
    return new Dir( ent, _follow_symlinks );
  }

  bool Dir::isRealDir() const
  {
    if ( _dirtype == FS_DIR_READ )
      return true;
  
    return false;
  }

  bool Dir::isVirtual() const
  {
    return ( ( _dirtype == VIRTUAL ) ? true : false );
  }

  void Dir::makeVirtual()
  {
    _dirtype = VIRTUAL;
  }

  FSEntry *Dir::clone() const
  {
    return new Dir( *this );
  }

  Dir::WalkControlObj::WalkControlObj() : _walk_mode( NORMAL )
  {
  }

  Dir::WalkControlObj::~WalkControlObj()
  {
  }

  void Dir::WalkControlObj::setMode( mode_t newmode )
  {
    _walk_mode = newmode;
  }

  Dir::WalkControlObj::mode_t Dir::WalkControlObj::getMode() const
  {
    return _walk_mode;
  }

  size_t Dir::size() const
  {
    return _subentries.size();
  }

  bool Dir::empty() const
  {
    return _subentries.empty();
  }

  int Dir::removeFromList( const std::string &name )
  {
    int found = 0;
    for ( std::vector<FSEntry*>::iterator it1 = _subentries.begin();
          it1 != _subentries.end();
          it1++ ) {
      if ( (*it1)->getFullname() == name ) {
        _subentries.erase( it1 );

        makeVirtual();
        found = 1;
        break;
      }
    }

    return found;
  }

    int Dir::removeFromList( const std::string &name, int pos )
    {
        int found = 0;

        if ( pos >= 0 && pos < (int)_subentries.size() ) {
            if ( _subentries[pos]->getFullname() == name ) {
                auto it = _subentries.begin();

                std::advance( it, pos );

                _subentries.erase( it );

                makeVirtual();
                found = 1;
            }
        }

        if ( ! found ) {
            return removeFromList( name );
        }

        return found;
    }

  void Dir::setFollowSymlinks( bool nv )
  {
    _follow_symlinks = nv;
  }

  bool Dir::getFollowSymlinks() const
  {
    return _follow_symlinks;
  }

  std::list< std::string> Dir::getFullnamesOfSubentries() const
  {
      std::list< std::string > l;

      for ( std::vector<FSEntry*>::const_iterator it1 = _subentries.begin();
            it1 != _subentries.end();
            it1++ ) {
          l.push_back( (*it1)->getFullname() );
      }
      return l;
  }

    FSEntry *Dir::getEntryAtPos( size_t pos )
    {
        if ( pos < 0 || pos >= _subentries.size() ) return NULL;

        return _subentries[pos];
    }

    loff_t Dir::getBytesInDir() const
    {
        return m_bytes_in_dir;
    }

    void Dir::setBytesInDir( loff_t bytes )
    {
        m_bytes_in_dir = bytes;
    }

    int Dir::changeBasenameOfEntry( const std::string &newbasename,
                                    int pos,
                                    bool keep_dirtype )
    {
        if ( pos < 0 || pos >= (int)_subentries.size() ) return 1;

        if ( ! keep_dirtype ) makeVirtual();

        return _subentries[pos]->changeBasename( newbasename );
    }

    void Dir::sort( std::function< bool( const FSEntry &lhs,
                                         const FSEntry &rhs ) > compare_cb )
    {
        std::sort( _subentries.begin(),
                   _subentries.end(),
                   [compare_cb]( const FSEntry *lhs,
                                 const FSEntry *rhs )
                   {
                       return compare_cb( *lhs, *rhs );
                   } );
    }
}
