/* verzeichnis.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2001-2022 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 "verzeichnis.hh"
#include "datei.h"
#include "aguix/lowlevelfunc.h"
#include "wconfig.h"
#include "grouphash.h"
#include "worker.h"
#include "wcfiletype.hh"
#include <algorithm>
#include <functional>
#include <nwc_path.hh>
#include "worker_locale.h"
#include "fileentry.hh"
#include "aguix/request.h"
#include "stringcomparator.hh"

Verzeichnis::Verzeichnis() : _dir_opened( false ),
                             _highest_ID_plus_one( 0 ),
                             _id_serial( -1 )
{
}

Verzeichnis::~Verzeichnis()
{
    closeDir();
}

int Verzeichnis::readDir( const char *dirstr )
{
    if ( dirstr == NULL ) return -1;
    if ( strlen( dirstr ) < 1 ) return -1;
    
    if ( dirOpened() == true )
        closeDir();
    
    int returnvalue = 0;
    DIR *dir;
    const char *myerror;
    std::string str1;
    
    _files.clear();
    
    dir = worker_opendir( dirstr );
    if ( dir != NULL ) {
        worker_struct_dirent *namelist;

        // first create .. entry
        FileEntry *fe1 = new FileEntry();
        fe1->name = dupstring( ".." );
      
        str1 = NWC::Path::join( dirstr, fe1->name );
        fe1->fullname = dupstring( str1.c_str() );
      
        if ( fe1->readInfos() != 0 ) {
            // failed, use some default values
            //TODO: Besonders toll gefällt mir das nicht
            //      Füge ich in FileEntry ein Feld hinzu, muss ich dies u.U. auch hier
            //      init.
            //      Besser wäre, wenn ich statbuf direkt in Klasse aufnehme und
            //      ich dann einfach ein memset machen kann
            memset( &( fe1->statbuf ), 0, sizeof ( fe1->statbuf ) );
            memset( &( fe1->dstatbuf ), 0, sizeof ( fe1->dstatbuf ) );
            fe1->statbuf.mode = S_IFDIR | S_IRUSR | S_IWUSR | S_IXUSR;
            fe1->statbuf.userid = (uid_t)-1;
            fe1->statbuf.groupid = (gid_t)-1;
            fe1->isCorrupt = false;
            fe1->isLink = false;
        }
      
        if ( fe1->name[0] == '.' )
            fe1->isHidden = true;
        else
            fe1->isHidden = false;
      
        _files.push_back( fe1 );
      
        while ( ( namelist = worker_readdir( dir ) ) != NULL ) {
            if ( strcmp( namelist->d_name, "." ) != 0 &&
                 strcmp( namelist->d_name, ".." ) != 0 ) {

                fe1 = new FileEntry();
                fe1->name = dupstring( namelist->d_name );

                str1 = NWC::Path::join( dirstr, fe1->name );
                fe1->fullname = dupstring( str1.c_str() );
              
                if ( fe1->readInfos() != 0 ) {
                }

                if ( fe1->name[0] == '.' )
                    fe1->isHidden = true;
                else
                    fe1->isHidden = false;
                _files.push_back( fe1 );
            }
        }
        worker_closedir( dir );
      
        _dir_opened = true;
        _dir_name = dirstr;
    } else {
        // requester: can't read directory
        myerror = strerror( errno );
        char *tstr2 = (char*)_allocsafe( strlen( catalog.getLocale( 532 ) ) +
                                         strlen( dirstr ) +
                                         strlen( myerror ) + 1 );
        sprintf( tstr2, catalog.getLocale( 532 ), dirstr, myerror );
        str1 = catalog.getLocale( 11 );
        Worker::getRequester()->request( catalog.getLocale( 347 ), tstr2, str1.c_str() );
        _freesafe( tstr2 );
        returnvalue = -1;
      
        _files.clear();
        _dir_opened = false;
    }
  
    createIDs();
    buildID2FE();
    return returnvalue;
}

int Verzeichnis::sortfunction(void *p1,void *p2,int mode)
{
    FileEntry *fe1,*fe2;
    int comp=0;
    fe1=(FileEntry*)p1;
    fe2=(FileEntry*)p2;
    loff_t s1,s2;
    int dirm=mode&0x600;
    WCFiletype *ft1, *ft2;
  
    /*  if(strcmp(fe1->name,"..")==0) return true;  // Sonderfall
        if(strcmp(fe2->name,"..")==0) return false;*/
    if((strcmp(fe1->name,"..")==0)&&(strcmp(fe2->name,"..")==0)) return 0;
    else if(strcmp(fe1->name,"..")==0) return -1;
    else if(strcmp(fe2->name,"..")==0) return 1;

    switch(mode&0xff) {
      case SORT_NAME:
          comp = StringComparator::compare( fe1->name, fe2->name );
          break;
        case SORT_STRICT_NAME:
            comp = strcmp( fe1->name, fe2->name );
            break;
      case SORT_SIZE:
          s1=-1;
          s2=-1;
          if ( S_ISCHR( fe1->mode() ) ||
               S_ISBLK( fe1->mode() ) ) {
              s1 = (loff_t)fe1->rdev();
          } else {
              if ( fe1->isDir() == true) s1 = fe1->dirsize;
              if ( s1 < 0 ) s1 = fe1->size();
          }
          if ( S_ISCHR( fe2->mode() ) ||
               S_ISBLK( fe2->mode() ) ) {
              s2 = (loff_t)fe2->rdev();
          } else {
              if ( fe2->isDir() == true ) s2 = fe2->dirsize;
              if ( s2 < 0 ) s2 = fe2->size();
          }
          if(s1<s2) comp=-1;
          else if(s1>s2) comp=1;
          else comp=strcmp(fe1->name,fe2->name);
          break;
      case SORT_ACCTIME:
          if ( fe1->lastaccess() < fe2->lastaccess() ) comp = -1;
          else if ( fe1->lastaccess() > fe2->lastaccess() ) comp = 1;
          else comp=strcmp(fe1->name,fe2->name);
          break;
      case SORT_MODTIME:
          if ( fe1->lastmod() < fe2->lastmod() ) comp = -1;
          else if ( fe1->lastmod() > fe2->lastmod() ) comp = 1;
          else comp=strcmp(fe1->name,fe2->name);
          break;
      case SORT_CHGTIME:
          if ( fe1->lastchange() < fe2->lastchange() ) comp = -1;
          else if ( fe1->lastchange() > fe2->lastchange() ) comp = 1;
          else comp=strcmp(fe1->name,fe2->name);
          break;
      case SORT_TYPE:
          if ( fe1->filetype != NULL ) {
              ft1 = fe1->filetype;
          } else {
              if(fe1->isDir()==true) {
                  ft1 = wconfig->getdirtype();
              } else {
                  ft1 = wconfig->getnotyettype();
              }
          }
          if ( fe2->filetype != NULL ) {
              ft2 = fe2->filetype;
          } else {
              if(fe2->isDir()==true) {
                  ft2 = wconfig->getdirtype();
              } else {
                  ft2 = wconfig->getnotyettype();
              }
          }
          comp = ft1->getName().compare( ft2->getName() );
          if ( comp == 0 )
              comp = strcmp( fe1->name, fe2->name );
          break;
      case SORT_OWNER:
          // first cmp usernames
          // if equal cmp groupnames
          // if equal cmp names
      
          // getUserNameS only look for existing entry in hashtable and returns
          // static pointer
          // so it's fast
          //TODO: perhaps it makes more sense to first compare groups
          comp = strcmp( ugdb->getUserNameS( fe1->userid() ),
                         ugdb->getUserNameS( fe2->userid() ) );
          if ( comp == 0 ) {
              comp = strcmp( ugdb->getGroupNameS( fe1->groupid() ),
                             ugdb->getGroupNameS( fe2->groupid() ) );
              if ( comp == 0 )
                  comp = strcmp( fe1->name, fe2->name );
          }
          break;
      case SORT_INODE:
          if ( (loff_t)fe1->inode() < (loff_t)fe2->inode() ) comp = -1;
          else if ( (loff_t)fe1->inode() > (loff_t)fe2->inode() ) comp = 1;
          else comp = strcmp( fe1->name, fe2->name );
          break;
      case SORT_NLINK:
          if ( (loff_t)fe1->nlink() < (loff_t)fe2->nlink() ) comp = -1;
          else if ( (loff_t)fe1->nlink() > (loff_t)fe2->nlink() ) comp = 1;
          else comp = strcmp( fe1->name, fe2->name );
          break;
      case SORT_PERMISSION:
          if ( fe1->mode() < fe2->mode() ) comp = -1;
          else if ( fe1->mode() > fe2->mode() ) comp = 1;
          else comp = strcmp( fe1->name, fe2->name );
          break;
    }
    if((mode&SORT_REVERSE)==SORT_REVERSE) comp*=-1;
    if(dirm!=SORT_DIRMIXED) {
        if(fe1->isDir()==true) {
            if(fe2->isDir()==false) comp=(dirm==SORT_DIRLAST)?1:-1;
        } else {
            if(fe2->isDir()==true) comp=(dirm==SORT_DIRLAST)?-1:1;
        }
    }
    return comp;
}

void Verzeichnis::closeDir()
{
    if ( _dir_opened == true ) {
        for ( verz_it fe1_it1 = _files.begin();
              fe1_it1 != _files.end();
              fe1_it1++ ) {
            delete *fe1_it1;
        }

        _files.clear();
        _dir_opened = false;

        _dir_name.clear();
    }
}

int Verzeichnis::getSize()
{
    if ( _dir_opened == true )
        return _files.size();
    else
        return -1;
}

const char *Verzeichnis::getDir()
{
    if ( _dir_name.empty() == true ) {
        return NULL;
    }
    return _dir_name.c_str();
}

int Verzeichnis::sort( int mode )
{
    std::sort( _files.begin(),
               _files.end(),
               [mode]( auto &lhs, auto &rhs ) {
                   return ( Verzeichnis::sortfunction( lhs, rhs, mode ) < 0 ) ? true : false;
               });
    return 0;
}

int Verzeichnis::getUseSize()
{
    int size = 0;
    if ( _dir_opened == true ) {
        for ( verz_it fe1_it1 = _files.begin();
              fe1_it1 != _files.end();
              fe1_it1++ ) {
            if ( (*fe1_it1)->use == true )
                size++;
        }
    }
    return size;
}

void Verzeichnis::buildID2FE()
{
    if ( _dir_opened == false ||
         _highest_ID_plus_one < 1 ) {
        _id2fe.clear();
    } else {
        _id2fe.resize( _highest_ID_plus_one );
        std::fill( _id2fe.begin(),
                   _id2fe.end(),
                   static_cast<FileEntry*>( NULL ) );
        
        for ( int pos = 0; pos < static_cast<int>( _files.size() ); pos++ ) {
            FileEntry *fe = _files[pos];
            if ( fe->getID() < 0 ||
                 fe->getID() >= static_cast<int>( _id2fe.size() ) ) {
                fprintf( stderr, "Worker fatal error: impossible ID detected!" );
                exit( EXIT_FAILURE );
            } else if ( _id2fe[ fe->getID() ] != NULL ) {
                fprintf( stderr, "Worker fatal error: file entry got wrong ID!" );
                exit( EXIT_FAILURE );
            } else {
                _id2fe[ fe->getID() ] = fe;
            }
        }
    }
}

FileEntry *Verzeichnis::getEntryByID( int ID )
{
    if ( ID < 0 || ID >= static_cast<int>( _id2fe.size() ) )
        return NULL;

    FileEntry *fe = _id2fe[ID];
    if ( fe == NULL )
        return NULL;

    if ( fe->getID() != ID ) {
        // oops, wrong position, looks like someone modified the list
        fprintf( stderr, "Worker error: wrong position for entry!\n" );
        abort();
    }
    
    return fe;
}

void Verzeichnis::createIDs()
{
    if ( _dir_opened == false )
        return;
    
    int ID = 0;
    for ( verz_it fe_it1 = _files.begin();
          fe_it1 != _files.end();
          fe_it1++ ) {
        FileEntry *fe = *fe_it1;
        fe->setID( ID++ );
    }

    _highest_ID_plus_one = ID;

    _id_serial++;

    // -1 is not a valid ID
    if ( _id_serial == -1 )
        _id_serial++;
}

void Verzeichnis::removeElement( const FileEntry *fe )
{
    verz_it it1 = std::find( _files.begin(), _files.end(), fe );
    if ( it1 != _files.end() ) {
        _files.erase( it1 );

        if ( _id2fe[ fe->getID() ] == fe ) {
            _id2fe[ fe->getID() ] = NULL;
        } else {
            fprintf( stderr, "Worker error: removing list entry with wrong ID!\n" );
            abort();
        }
    }
}

int Verzeichnis::getSerialOfIDs()
{
    return _id_serial;
}

int Verzeichnis::getInvalidSerial()
{
    return -1;
}

Verzeichnis::verz_it Verzeichnis::begin()
{
    return _files.begin();
}

Verzeichnis::verz_it Verzeichnis::end()
{
    return _files.end();
}

bool Verzeichnis::dirOpened() const
{
    return _dir_opened;
}
