/* nwcentryselectionstate.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2013-2023 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 "nwcentryselectionstate.hh"
#include "verzeichnis.hh"
#include "stringcomparator.hh"
#include "grouphash.h"
#include "wcfiletype.hh"
#include "wconfig.h"
#include "flagreplacer.hh"

NWCEntrySelectionState::NWCEntrySelectionState( int entry_id )
    : m_entry_id( entry_id ),
      m_use( false ),
      m_filetype( NULL ),
      m_selected( false )
{
}

NWCEntrySelectionState::NWCEntrySelectionState( const NWCEntrySelectionState &other )
    : m_dir( other.m_dir ),
      m_cloned_entry( other.m_cloned_entry ),
      m_entry_id( other.m_entry_id ),
      m_use( other.m_use ),
      m_filetype( other.m_filetype ),
      m_colors( other.m_colors ),
      m_selected( other.m_selected ),
      m_filetype_file_output( other.m_filetype_file_output ),
      m_mime_type( other.m_mime_type ),
      m_custom_attribute( other.m_custom_attribute )
{
    if ( other.m_bookmark_info.get() != NULL ) {
        m_bookmark_info.reset( new NWCBookmarkInfo( *other.m_bookmark_info ) );
    }
}

NWCEntrySelectionState::NWCEntrySelectionState( const NWCEntrySelectionState &other, bool decoupleDir )
    : m_entry_id( other.m_entry_id ),
      m_use( other.m_use ),
      m_filetype( other.m_filetype ),
      m_colors( other.m_colors ),
      m_selected( other.m_selected ),
      m_filetype_file_output( other.m_filetype_file_output ),
      m_mime_type( other.m_mime_type ),
      m_custom_attribute( other.m_custom_attribute )
{
    if ( decoupleDir ) {
        const NWC::FSEntry *fse = other.getNWCEntry();

        if ( fse ) {
            m_cloned_entry.reset( fse->clone() );
        }
    } else {
        m_dir = other.m_dir;
        m_cloned_entry = other.m_cloned_entry;
    }

    if ( other.m_bookmark_info.get() != NULL ) {
        m_bookmark_info.reset( new NWCBookmarkInfo( *other.m_bookmark_info ) );
    }
}

NWCEntrySelectionState::NWCEntrySelectionState( const NWC::FSEntry &entry )
    : m_entry_id( -1 ),
      m_use( true ),
      m_filetype( nullptr ),
      m_colors( FileEntryColor() ),
      m_selected( false )
{
    m_cloned_entry.reset( entry.clone() );
}

NWCEntrySelectionState &NWCEntrySelectionState::operator=( const NWCEntrySelectionState &rhs )
{
    if ( this != &rhs ) {
        m_dir = rhs.m_dir;
        m_cloned_entry = rhs.m_cloned_entry;
        m_entry_id = rhs.m_entry_id;
        m_use = rhs.m_use;
        m_filetype = rhs.m_filetype;
        m_colors = rhs.m_colors;
        m_selected = rhs.m_selected;
        m_filetype_file_output = rhs.m_filetype_file_output;
        m_mime_type = rhs.m_mime_type;
        m_custom_attribute = rhs.m_custom_attribute;

        if ( rhs.m_bookmark_info.get() != NULL ) {
            m_bookmark_info.reset( new NWCBookmarkInfo( *rhs.m_bookmark_info ) );
        } else {
            m_bookmark_info = NULL;
        }
    }

    return *this;
}

void NWCEntrySelectionState::setDir( RefCount< NWC::Dir > dir )
{
    m_dir = dir;
}

void NWCEntrySelectionState::setUse( bool use )
{
    m_use = use;
}

bool NWCEntrySelectionState::getUse() const
{
    return m_use;
}

void NWCEntrySelectionState::setEntryID( int entry_id )
{
    m_entry_id = entry_id;
}

int NWCEntrySelectionState::getEntryID() const
{
    return m_entry_id;
}

const NWC::FSEntry *NWCEntrySelectionState::getNWCEntry() const
{
    if ( m_cloned_entry.get() != NULL ) return m_cloned_entry.get();

    if ( m_dir.get() == NULL ) return NULL;

    if ( m_entry_id < 0 || m_entry_id >= (int)m_dir->size() ) return NULL;

    return m_dir->getEntryAtPos( m_entry_id );
}

NWC::FSEntry *NWCEntrySelectionState::getNWCEntry()
{
    if ( m_cloned_entry.get() != NULL ) return m_cloned_entry.get();

    if ( m_dir.get() == NULL ) return NULL;

    if ( m_entry_id < 0 || m_entry_id >= (int)m_dir->size() ) return NULL;

    return m_dir->getEntryAtPos( m_entry_id );
}

void NWCEntrySelectionState::setFiletype( WCFiletype *ft )
{
    m_filetype = ft;
}

WCFiletype *NWCEntrySelectionState::getFiletype() const
{
    return m_filetype;
}

WCFiletype *NWCEntrySelectionState::getEffectiveFiletype() const
{
    if ( getFiletype() != NULL ) {
        return getFiletype();
    } else {
        const NWC::FSEntry *fse;

        fse = getNWCEntry();

        if ( fse && fse->isDir( true ) == true ) {
            return wconfig->getdirtype();
        } else {
            return wconfig->getnotyettype();
        }
    }
}

int NWCEntrySelectionState::sortfunction( const NWCEntrySelectionState &e1,
                                          const NWCEntrySelectionState &e2,
                                          int mode )
{
    int comp = 0;
    loff_t s1,s2;
    const int dirm = mode & 0x600;
    const NWC::FSEntry *fse1, *fse2;
    WCFiletype *ft1, *ft2;

    fse1 = e1.getNWCEntry();
    fse2 = e2.getNWCEntry();

    if ( fse1 == NULL && fse2 == NULL ) return 0;
    if ( fse1 == NULL ) return -1;
    if ( fse2 == NULL ) return 1;
  
    if ( fse1->getBasename() == ".." && fse2->getBasename() == ".." ) return 0;
    else if ( fse1->getBasename() == ".." ) return -1;
    else if ( fse2->getBasename() == ".." ) return 1;

    switch ( mode & 0xff ) {
      case SORT_NAME:
          comp = StringComparator::compare( fse1->getFullname(),
                                            fse2->getFullname() );
          break;
      case SORT_STRICT_NAME:
          comp = strcmp( fse1->getFullname().c_str(),
                         fse2->getFullname().c_str() );
          break;
      case SORT_SIZE:
          s1 = -1;
          s2 = -1;
          if ( S_ISCHR( fse1->stat_mode() ) ||
               S_ISBLK( fse1->stat_mode() ) ) {
              s1 = (loff_t)fse1->stat_rdev();
          } else {
              if ( fse1->isDir() == true ) {
                  const NWC::Dir *d = dynamic_cast< const NWC::Dir * >( fse1 );
                  if ( d ) {
                      s1 = d->getBytesInDir();
                  }
              }
              if ( s1 < 0 ) s1 = fse1->stat_size();
          }
          if ( S_ISCHR( fse2->stat_mode() ) ||
               S_ISBLK( fse2->stat_mode() ) ) {
              s2 = (loff_t)fse2->stat_rdev();
          } else {
              if ( fse2->isDir() == true ) {
                  const NWC::Dir *d = dynamic_cast< const NWC::Dir * >( fse2 );
                  if ( d ) {
                      s2 = d->getBytesInDir();
                  }
              }
              if ( s2 < 0 ) s2 = fse2->stat_size();
          }
          if ( s1 < s2 ) comp = -1;
          else if ( s1 > s2 ) comp = 1;
          else comp = fse1->getFullname().compare( fse2->getFullname() );
          break;
      case SORT_ACCTIME:
          if ( fse1->stat_lastaccess() < fse2->stat_lastaccess() ) comp = -1;
          else if ( fse1->stat_lastaccess() > fse2->stat_lastaccess() ) comp = 1;
          else comp = fse1->getFullname().compare( fse2->getFullname() );
          break;
      case SORT_MODTIME:
          if ( fse1->stat_lastmod() < fse2->stat_lastmod() ) comp = -1;
          else if ( fse1->stat_lastmod() > fse2->stat_lastmod() ) comp = 1;
          else comp = fse1->getFullname().compare( fse2->getFullname() );
          break;
      case SORT_CHGTIME:
          if ( fse1->stat_lastchange() < fse2->stat_lastchange() ) comp = -1;
          else if ( fse1->stat_lastchange() > fse2->stat_lastchange() ) comp = 1;
          else comp = fse1->getFullname().compare( fse2->getFullname() );
          break;
      case SORT_TYPE: {
          if ( e1.getFiletype() != NULL ) {
              ft1 = e1.getFiletype();
          } else {
              if ( fse1->isDir( true ) == true ) {
                  ft1 = wconfig->getdirtype();
              } else {
                  ft1 = wconfig->getnotyettype();
              }
          }
          if ( e2.getFiletype() != NULL ) {
              ft2 = e2.getFiletype();
          } else {
              if ( fse2->isDir( true ) == true ) {
                  ft2 = wconfig->getdirtype();
              } else {
                  ft2 = wconfig->getnotyettype();
              }
          }

          auto fpr1 = std::make_shared< NWCFlagProducer >( &e1, nullptr );
          auto fpr2 = std::make_shared< NWCFlagProducer >( &e2, nullptr );
          FlagReplacer rpl1( fpr1 );
          FlagReplacer rpl2( fpr2 );

          std::string s1 = getStringRepresentation( WorkerTypes::LISTCOL_TYPE,
                                                    0,
                                                    fse1,
                                                    ft1,
                                                    &rpl1,
                                                    e1.getCustomAttribute() );
          std::string s2 = getStringRepresentation( WorkerTypes::LISTCOL_TYPE,
                                                    0,
                                                    fse2,
                                                    ft2,
                                                    &rpl2,
                                                    e2.getCustomAttribute() );

          comp = s1.compare( s2 );
          if ( comp == 0 )
              comp = fse1->getFullname().compare( fse2->getFullname() );
          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( fse1->stat_userid() ),
                         ugdb->getUserNameS( fse2->stat_userid() ) );
          if ( comp == 0 ) {
              comp = strcmp( ugdb->getGroupNameS( fse1->stat_groupid() ),
                             ugdb->getGroupNameS( fse2->stat_groupid() ) );
              if ( comp == 0 )
                  comp = fse1->getFullname().compare( fse2->getFullname() );
          }
          break;
      case SORT_INODE:
          if ( (loff_t)fse1->stat_inode() < (loff_t)fse2->stat_inode() ) comp = -1;
          else if ( (loff_t)fse1->stat_inode() > (loff_t)fse2->stat_inode() ) comp = 1;
          else comp = fse1->getFullname().compare( fse2->getFullname() );
          break;
      case SORT_NLINK:
          if ( (loff_t)fse1->stat_nlink() < (loff_t)fse2->stat_nlink() ) comp = -1;
          else if ( (loff_t)fse1->stat_nlink() > (loff_t)fse2->stat_nlink() ) comp = 1;
          else comp = fse1->getFullname().compare( fse2->getFullname() );
          break;
      case SORT_PERMISSION:
          if ( fse1->stat_mode() < fse2->stat_mode() ) comp = -1;
          else if ( fse1->stat_mode() > fse2->stat_mode() ) comp = 1;
          else comp = fse1->getFullname().compare( fse2->getFullname() );
          break;
      case SORT_EXTENSION:
          comp = fse1->getExtension().compare( fse2->getExtension() );
          if ( comp == 0 ) {
              comp = fse1->getFullname().compare( fse2->getFullname() );
          }
          break;
      case SORT_CUSTOM_ATTRIBUTE:
          comp = e1.getCustomAttribute().compare( e2.getCustomAttribute() );
          break;
    }

    if ( ( mode & SORT_REVERSE ) == SORT_REVERSE ) comp *= -1;

    if ( dirm != SORT_DIRMIXED ) {
        if ( fse1->isDir( true ) == true ) {
            if ( fse2->isDir( true ) == false ) comp = ( dirm == SORT_DIRLAST ) ? 1 : -1;
        } else {
            if ( fse2->isDir( true ) == true ) comp = ( dirm == SORT_DIRLAST ) ? -1 : 1;
        }
    }

    return comp;
}

void NWCEntrySelectionState::setMatchingLabels( const std::list<std::string> &labels )
{
    if ( labels.empty() == true ) {
        if ( m_bookmark_info.get() != NULL ) {
            m_bookmark_info = NULL;
        }
    } else {
        if ( ! m_bookmark_info.get() ) {
            m_bookmark_info.reset( new NWCBookmarkInfo() );
        }

        m_bookmark_info->m_matching_labels = labels;
    }
}

void NWCEntrySelectionState::clearMatchingLabels()
{
    if ( m_bookmark_info.get() ) {
        m_bookmark_info = NULL;
    }
}

const std::list<std::string> &NWCEntrySelectionState::getMatchingLabels() const
{
    if ( ! m_bookmark_info.get() ) {
        throw 1;
    }

    return m_bookmark_info->m_matching_labels;
}

bool NWCEntrySelectionState::hasMatchingLabels() const
{
    if ( m_bookmark_info.get() == NULL ) return false;
    if ( m_bookmark_info->m_matching_labels.empty() == true ) return false;

    return true;
}

FileEntry::bookmark_match_t NWCEntrySelectionState::getBookmarkMatch() const
{
    if ( ! m_bookmark_info.get() ) {
        return FileEntry::NOT_IN_BOOKMARKS;
    }

    return m_bookmark_info->m_bookmark_match;
}

void NWCEntrySelectionState::setBookmarkMatch( FileEntry::bookmark_match_t m )
{
    if ( m == FileEntry::NOT_IN_BOOKMARKS ) {
        if ( m_bookmark_info.get() != NULL ) {
            m_bookmark_info->m_bookmark_match = m;
        }
    } else {
        if ( m_bookmark_info.get() == NULL ) {
            m_bookmark_info.reset( new NWCBookmarkInfo() );
        }

        m_bookmark_info->m_bookmark_match = m;
    }
}

void NWCEntrySelectionState::setCustomColor( const FileEntryCustomColor &c )
{
    m_colors.setCustomColor( c );
}

void NWCEntrySelectionState::setCustomColors( const int fg[4], const int bg[4] )
{
    m_colors.setCustomColors( fg, bg );
}

const FileEntryCustomColor &NWCEntrySelectionState::getCustomColor() const
{
    return m_colors.getCustomColor();
}

int NWCEntrySelectionState::getCustomColorSet() const
{
    return m_colors.getCustomColorSet();
}

void NWCEntrySelectionState::clearCustomColor()
{
    m_colors.setCustomColorSet( 0 );
}

void NWCEntrySelectionState::clearOverrideColor()
{
    m_colors.setOverrideColorSet( 0 );
}

void NWCEntrySelectionState::setColor( const FileEntryColor &c )
{
    m_colors = c;
}

const FileEntryColor &NWCEntrySelectionState::getColor() const
{
    return m_colors;
}

void NWCEntrySelectionState::setOverrideColor( int type, int fg, int bg )
{
    m_colors.setOverrideColor( type, fg, bg );
}

void NWCEntrySelectionState::setSelected( bool s )
{
    m_selected = s;
}

bool NWCEntrySelectionState::getSelected() const
{
    return m_selected;
}

std::string NWCEntrySelectionState::getStringRepresentation( WorkerTypes::listcol_t type,
                                                             int file_name_skip,
                                                             FlagReplacer *rpl ) const
{
    std::string res;

    const NWC::FSEntry *fse = getNWCEntry();

    if ( ! fse ) return res;

    return getStringRepresentation( type, file_name_skip, fse, m_filetype, rpl, m_custom_attribute );
}

std::string NWCEntrySelectionState::getStringRepresentation( WorkerTypes::listcol_t type,
                                                             int file_name_skip,
                                                             const NWC::FSEntry *fse,
                                                             const WCFiletype *filetype,
                                                             FlagReplacer *rpl,
                                                             const std::string &custom_attribute )
{
    std::string res;
    struct tm *timeptr;
    time_t timestamp;
    loff_t dissize, li;
    bool sfds = false;
    int ost, x;
    char nbuf[ A_BYTESFORNUMBER( loff_t ) ];
    struct tm temptm;

    if ( ! fse ) return res;
  
    switch ( type ) {
        case WorkerTypes::LISTCOL_NAME:
            if ( fse->isLink() == true ) {
                if ( fse->isBrokenLink() == true ) res += '!';
                else {
                    if ( S_ISDIR( fse->stat_dest_mode() ) ) res += '~';
                    else res += '@';
                }
            } else {
                if ( S_ISDIR( fse->stat_mode() ) ) res += '/';
                else if ( S_ISFIFO( fse->stat_mode() ) ) res += '|';
                else if ( S_ISSOCK( fse->stat_mode() ) ) res += '=';
                else if ( S_ISCHR( fse->stat_mode() ) ) res += '-';
                else if ( S_ISBLK( fse->stat_mode() ) ) res += '+';
                else if ( fse->isExe() == true ) res += '*';
                else res += ' ';
            }

            if ( file_name_skip > 0 ) {
                res.append( fse->getFullname(), file_name_skip, fse->getFullname().size() );
            } else {
                res += fse->getFullname();
            }
            break;
        case WorkerTypes::LISTCOL_SIZE:
        case WorkerTypes::LISTCOL_SIZEH:
            if ( S_ISCHR( fse->stat_mode() ) ||
                 S_ISBLK( fse->stat_mode() ) ) {
                x = (int)fse->stat_rdev();
                snprintf( nbuf, sizeof( nbuf ), "%d", x >> 8 );
                res = nbuf;
                res += ", ";
                snprintf( nbuf, sizeof( nbuf ), "%3d", x & 0xff );
                res += nbuf;
            } else {
                loff_t s1 = -1;

                if ( fse->isDir() == true &&
                     fse->isLink() == false ) {
                    const NWC::Dir *d = dynamic_cast< const NWC::Dir * >( fse );
                    if ( d ) {
                        s1 = d->getBytesInDir();
                    }
                }

                if ( s1 < 0 ) dissize = fse->stat_size();
                else dissize = s1;
  
                if ( fse->isDir() == true ) {
                    if ( ! ( fse->isLink() == false && s1 >= 0 ) ) {
                        if ( wconfig->getShowStringForDirSize() == true )
                            sfds = true;
                    }
                }
                if ( sfds == true ) {
                    res = wconfig->getStringForDirSize();
                } else {
                    if ( type == WorkerTypes::LISTCOL_SIZEH ) {
                        res = AGUIXUtils::bytes_to_human_readable_f( dissize, 1, 0 );
                    } else {
                        MakeLong2NiceStr( dissize, res );
                    }
                }
            }
            break;
        case WorkerTypes::LISTCOL_TYPE:
            if ( filetype != NULL ) {
                if ( rpl && filetype->nameContainsFlags() ) {
                    res = rpl->replaceFlags( filetype->getName(), false );
                } else {
                    res = filetype->getName();
                }
            } else {
                if ( fse->isDir( true ) == true ) {
                    // Dir
                    res = wconfig->getdirtype()->getName();
                } else {
                    res = wconfig->getnotyettype()->getName();
                }
            }
            break;
        case WorkerTypes::LISTCOL_PERM:
            fse->getPermissionString( res );
            break;
        case WorkerTypes::LISTCOL_OWNER:
            // will be 1 for Owner.group, 0 else
            ost = wconfig->getOwnerstringtype();

            res = ugdb->getUserNameS( fse->stat_userid() );
            if(ost==1) {
                res += '.';
            } else {
                res += " @ ";
            }
            res += ugdb->getGroupNameS( fse->stat_groupid() );
            break;
        case WorkerTypes::LISTCOL_DEST:
            fse->getDestination( res );
            break;
        case WorkerTypes::LISTCOL_MOD:
            timeptr = localtime_r( &( timestamp = fse->stat_lastmod() ), &temptm );
            wconfig->writeDateToString( res, timeptr );
            
            break;
        case WorkerTypes::LISTCOL_ACC:
            timeptr = localtime_r( &( timestamp = fse->stat_lastaccess() ), &temptm );
            wconfig->writeDateToString( res, timeptr );
            break;
        case WorkerTypes::LISTCOL_CHANGE:
            timeptr = localtime_r( &( timestamp = fse->stat_lastchange() ), &temptm );
            wconfig->writeDateToString( res, timeptr );
            break;
        case WorkerTypes::LISTCOL_INODE:
            li = (loff_t)fse->stat_inode();
            MakeLong2NiceStr( li, res, false );
            break;
        case WorkerTypes::LISTCOL_NLINK:
            li = (loff_t)fse->stat_nlink();
            MakeLong2NiceStr( li, res, false );
            break;
        case WorkerTypes::LISTCOL_BLOCKS:
            li = (loff_t)fse->stat_blocks();
            MakeLong2NiceStr( li, res, false );
            break;
        case WorkerTypes::LISTCOL_EXTENSION:
            res = fse->getExtension();
            break;
        case WorkerTypes::LISTCOL_CUSTOM_ATTR:
            res = custom_attribute;
            break;
        default:
            break;
    }

    return res;
}

RefCount< NWCBookmarkInfo > NWCEntrySelectionState::getBookmarkInfo()
{
    return m_bookmark_info;
}

void NWCEntrySelectionState::setBookmarkInfo( RefCount< NWCBookmarkInfo >  bookmark_info )
{
    m_bookmark_info = bookmark_info;
}

void NWCEntrySelectionState::setFiletypeFileOutput( const std::string &output )
{
    m_filetype_file_output = output;
}

std::string NWCEntrySelectionState::getFiletypeFileOutput() const
{
    return m_filetype_file_output;
}

void NWCEntrySelectionState::setMimeType( const std::string &mimetype )
{
    m_mime_type = mimetype;
}

std::string NWCEntrySelectionState::getMimeType() const
{
    return m_mime_type;
}

void NWCEntrySelectionState::setCustomAttribute( const std::string &content )
{
    m_custom_attribute = content;
}

const std::string &NWCEntrySelectionState::getCustomAttribute() const
{
    return m_custom_attribute;
}

std::string NWCFlagProducer::getFlagReplacement( const std::string &flag )
{
    if ( flag == "mimetype" ) {
        if ( m_es ) {
            return m_es->getMimeType();
        } else if ( m_fe ) {
            return m_fe->getMimeType();
        }
    }

    return "";
}

bool NWCFlagProducer::hasFlag( const std::string &str )
{
    return str == "mimetype";
}
