#include <unistd.h>  
#include <errno.h>
#include <pwd.h>
#include <grp.h>
#include <stdio.h>

#include "UnixFile.hpp"
#include "StringUtils.hpp"
#include "text/format.h"
#include <vector>
using namespace std;

#ifdef __ultrix__
#include "Ultrix-Compatibility.hpp"
#endif


// Last changed: 1999-12-10


// Löscht bei direkt aufeinanderfolgenden '/' alle bis auf einen.
// Entfernt letztes Zeichen falls es '/' ist (und der String länger als 1 ist).
inline void beautifyPathname( string& name) {
  string::size_type pos= name.find( UnixFile::separator), 
                    succ;
  while (pos != string::npos) {
    succ= pos+1;
    while (succ < name.size() && name[succ]== UnixFile::separator)
#ifdef GNU_CPPSTDLIB_VER27
      name.remove( succ, 1);
#else
      name.erase( succ, 1);
#endif
    if (succ>=name.size()) break;   
    pos= name.find( UnixFile::separator, succ);
  } // end while

  if (name.size() > 1) {
    pos = name.size()-1;
    if (name[pos]==UnixFile::separator)       // letztes Zeichen ein '/'
#ifdef GNU_CPPSTDLIB_VER27
      name.remove( pos);
#else
      name.erase( pos);
#endif
  }
}


UnixFile::UnixFile( const string& full_, 
                    const short int  mode,
                    const bool force_canonical)
: state( NORMAL),
  stat_mode( mode),
  fullname( full_),  
  status_change_time( 0),
  access_time( 0),
  modification_time( 0),
  linkedFile( NULL)
{
  if (force_canonical && !fullname.empty() && fullname[0]!=separator)
    fullname= getCanonicalPath( getAbsolutePath( fullname));
  else  
    beautifyPathname( fullname);

  sync();
}


UnixFile::UnixFile( const UnixFile& orig) : 
        state( orig.state), stat_mode( orig.stat_mode),
        fullname( orig.fullname), type( orig.type),
        size( orig.size), 
        status_change_time( orig.status_change_time),
        access_time( orig.access_time),
        modification_time( orig.modification_time),
        fileMode( orig.fileMode) { 
  if (orig.linkedFile!=NULL)
    linkedFile= new UnixFile( *orig.linkedFile); // call Copy Constructor
  else linkedFile= NULL;
}
        
UnixFile& UnixFile::operator= (const UnixFile& orig) {
  if (this!=&orig) {
    state= orig.state; stat_mode= orig.stat_mode;
    fullname= orig.fullname; type= orig.type;
    size= orig.size;
    status_change_time= orig.status_change_time;
    access_time= orig.access_time;
    modification_time= orig.modification_time;
    fileMode= orig.fileMode; 
    if (orig.linkedFile!=NULL) {
      delete linkedFile;
      linkedFile= new UnixFile( *orig.linkedFile);
    }
    else linkedFile= NULL;
  }
  return *this;  
}

UnixFile::~UnixFile() {
  delete linkedFile;
}

bool UnixFile::isNavigable()  const {
  return (type==DIRECTORY || ( linkedFile != 0 && linkedFile->isNavigable() ) );
}   

bool UnixFile::isSubNavigable()  const {
  if ( !isNavigable() )       return false;
  if ( isSpecialDirectory() ) return false;
  if ( isDirectory() )        return true;
  
  return ! startsWith(   // startsWith <==> 2nd arg is prefix of 1st arg.
    getCanonicalPath( getAbsolutePath( this->getParent() )), 
    getCanonicalPath( getAbsoluteDirectLinkPath())  );


//                     bool result= isNavigable();
//                     if (result) {
//                       string name;
//                       if ( linkedFile!=NULL) 
//                         name= linkedFile->getName(); // problem: name is still the link's one
//                       else
//                         name= getName();
//                         
//                       result= name != "." && name != "..";
//                     }
//                     return result;
}


bool UnixFile::exists()  const {
  return (state==NORMAL);
}

bool UnixFile::equals( UnixFile& f)  const {
  return getCanonicalPath(getAbsolutePath(this->getFullName())) 
         == getCanonicalPath(getAbsolutePath(f.getFullName()));
}

bool UnixFile::isAbsolute()  const {
  return ( !fullname.empty() && fullname[0]==separator);
}

bool UnixFile::isDirectory()  const {
  return (type==DIRECTORY);
}   

bool UnixFile::isFile()  const {
  return (type==REGFILE);
}

bool UnixFile::isLink()  const {
  return (type==SYMLINK);
}

bool UnixFile::isBrokenLink()  const {
  return (isLink() && linkedFile!=NULL && linkedFile->state == NOT_EXISTING);
}

bool UnixFile::isExecutable()  const {
  return ( isFile() && ( fileMode.getMode() & (S_IXUSR|S_IXGRP|S_IXOTH)) != 0 );
}

string UnixFile::getName()  const { 
  string::size_type pos= fullname.rfind("/");
  
  if (pos==string::npos)       return fullname;
  else if (fullname.size()==1) return fullname;       // file was '/'
  else                         return fullname.substr( pos + 1);
}

string UnixFile::getParent()  const {
  string::size_type pos= fullname.rfind("/");
  
  if (pos==string::npos) return ".";   // file spec has no access-path part
  else if (pos==0)       return "/";  // file was '/xxx' or '/'
  else                   return fullname.substr( 0, pos );  // without last '/'
}

string UnixFile::getPath()  const {  return fullname; }

string UnixFile::getAbsolutePath()  const { 
  if (isAbsolute())  return fullname;
  else  return getAbsolutePath( fullname); 
}

string UnixFile::getCanonicalPath()  const {
  return getCanonicalPath( this->getAbsolutePath());
}

string UnixFile::getNameExtension()  const {
  string name= getName();
  string::size_type pos= name.rfind('.');
  
  if (pos!=string::npos && pos >= 1)
    return name.substr( pos + 1);
  else
    return "";
}

string UnixFile::getLinkName()  const {
  char path[1024];
  int count= readlink( getFullName().c_str(), path, 1024);  // Link auslesen
  if (count > -1) {     // successful readlink()
    path[count]= '\0';
    return path;
  }
  else return "";  
}

off_t UnixFile::getSize()  const {  return size; }

string UnixFile::getAttributeString()  const {
  string s;
  s= type + fileMode.toString();
  return s;
}

string UnixFile::getOwner( bool generate)  const {
  struct passwd* p;
  p= getpwuid( this->owner);   // Eintrag aus Benutzerdatei
  if (p==0)                    // --> kein Eintrag
    return (generate ? "(" + text::format( "%d", this->owner) + ")" : "");
  else 
    return string( p->pw_name);
}

string UnixFile::getOwnerName()  const {
  struct passwd* p;
  p= getpwuid( this->owner);
  if (p==0)  return "";
  else       return string( p->pw_gecos);
} 

string UnixFile::getGroup( bool generate)  const {
  struct group* p;
  p= getgrgid( this->group);
  if (p==0) 
    return (generate ? "(" + text::format("%d", this->group) + ")" : "");
  else 
    return string( p->gr_name);
}


void UnixFile::sync() {

  delete linkedFile;  linkedFile= NULL;

  struct stat  buf;
  int          err;
  
  if (stat_mode == UnixFile::AS_IS) 
    err= lstat( this->getFullName().c_str(), &buf );
  else
    err= stat( this->getFullName().c_str(), &buf );

  if (err != 0) { 
         /* What could have happened here? (value of errno)
            E_NOENT  This file is not existing (No such directory entry)
            E_ACCES  Access to file (entry) not permitted.
            E_LOOP   A (possibly) infinite long chain of symlinks
            ENAMETOOLONG File name too long
          */
     type='!';  // for compatib.
     if (errno == EACCES)   state= ACCESS_DENIED;
     else                   state= NOT_EXISTING;
  }
  else {             // successful stat() / lstat()
    state= NORMAL;
                          // collect file infos...
    size=  buf.st_size;  
    owner= buf.st_uid;  
    group=  buf.st_gid; 
    status_change_time= Date( buf.st_ctime);
    access_time= Date( buf.st_atime);
    modification_time= Date( buf.st_mtime);

    fileMode= UnixFileMode( buf.st_mode);

                   // now check the file type...
    if (S_ISREG( buf.st_mode))       type= '-';   // regular file
    else if (S_ISDIR( buf.st_mode))  type= 'd';   // directory
    else if (S_ISBLK( buf.st_mode))  type= 'b';   // block device
    else if (S_ISCHR( buf.st_mode))  type= 'c';   // character device
    else if (S_ISFIFO( buf.st_mode)) type= 'p';   // fifo (pipe) 
    else if (S_ISSOCK( buf.st_mode)) type= 's';   // socket
    else if (S_ISLNK( buf.st_mode))   {           // symbolic link
      this->type= 'l';

      linkedFile= new UnixFile( fullname, DEREF);
    }
  } 
} 

string UnixFile::getAbsolutePath( const string& path) {
  if (!path.empty() && path[0]!='/') {
    string s;
    char b[1024];
    char* buf= getcwd( b, 1024);
    if (buf!=0) { s += buf;  s += '/'; }
    return s + path;
  }
  else 
    return path; 
}

string UnixFile::getCanonicalPath( string path) {
  bool absolute= ( !path.empty() && path[0] == '/' );
  StringTokenizer st( path, "/");
#ifdef GNU_CPPSTDLIB_VER27
  path.remove();
#else
  path.erase();
#endif
  if (absolute) path="/";
  
  vector<string> parts;
  while ( st.hasMoreTokens()) parts.push_back( st.getNextToken());
  
  vector<string>::size_type idx= 0;
  while ( idx < parts.size()) {
    const string& elem= parts[idx];
    
    if (elem==".")  parts.erase( parts.begin()+idx);
    else if (elem=="..") 
      if ( idx!=0 && parts[idx-1]!="..")  {
        parts.erase( parts.begin()+idx-1, parts.begin()+idx+1);
        idx--;
      } 
      else
        idx++;
    else
      idx++;
  }

  vector<string>::iterator i;
  for ( i= parts.begin(); i < parts.end(); ++i) {
    path += *i + separator;
  }

  if (path.size()>1 && path[path.size()-1]==UnixFile::separator)
#ifdef GNU_CPPSTDLIB_VER27
    path.remove( path.size()-1);
#else
    path.erase( path.size()-1);
#endif
  return path;
}


string UnixFile::getAbsoluteDirectLinkPath()  const {
  string link= getLinkName();
  if (!link.empty() && link[0]=='/') 
    return link;
  else 
    return getAbsolutePath( getParent() ) + separator + link;
}


// ---- methods for class UnixFileMode ----

UnixFileMode::UnixFileMode( mode_t fm) {
  filemode= fm;
}

string UnixFileMode::toString()  const {
  string s= "---------";
  if (S_IRUSR & filemode) s[0]='r';
  if (S_IWUSR & filemode) s[1]='w';
  if (S_ISUID & filemode)
    if (S_IXUSR & filemode) s[2]='s';  // set UID
    else                    s[2]='S';  // set UID without execution permission
  else if (S_IXUSR & filemode) s[2]='x';  // ordinary execution permission
  
  if (S_IRGRP & filemode) s[3]='r';
  if (S_IWGRP & filemode) s[4]='w';
  if (S_ISGID & filemode) 
    if (S_IXGRP & filemode) s[5]='s';  // set GID
    else                    s[5]='S';
  else if (S_IXGRP & filemode) s[5]='x';
  
  if (S_IROTH & filemode) s[6]='r';
  if (S_IWOTH & filemode) s[7]='w';
  if (S_ISVTX & filemode)
    if (S_IXOTH & filemode) s[8]='t';    // sticky bit
    else                    s[8]='T';
  else if (S_IXOTH & filemode) s[8]='x';
  
  return s;
}

