/**************************************************************************

    This file is part of Xclass95, a Win95-looking GUI toolkit.
    Copyright (C) 1996, 1997 David Barth, Hector Peraza.

    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., 675 Mass Ave, Cambridge, MA 02139, USA.

**************************************************************************/

#include <assert.h>
#include <stdio.h>
#include <errno.h>
#include <limits.h>
#include <dirent.h>
#include <unistd.h>
#include <grp.h>
#include <pwd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>

#include <xclass/utils.h>
#include <xclass/OMimeTypes.h>
#include <xclass/OXFrame.h>
#include <xclass/OXWidgets.h>
#include <xclass/OXMsgBox.h>
#include <xclass/OXClient.h>
#include <xclass/OString.h>
#include <xclass/OLayout.h>
#include <xclass/OXListView.h>
#include <xclass/OXFileContainer.h>


//---------------------------------------------------------------------------

void OXFileIcon::_DoRedraw() {
  OXIcon::_DoRedraw();
  if (_lpic) _lpic->Draw(_client->GetDisplay(), _id, _bckgndGC, 0, 0);
}


//---------------------------------------------------------------------------

OXFileItem::OXFileItem(const OXWindow *p,
      const OPicture *bpic, const OPicture *blpic,
      const OPicture *spic, const OPicture *slpic,
      OString *name, int type, unsigned long size, int uid, int gid,
      int ViewMode, unsigned int options, unsigned long back) :
  OXListItem(p, bpic, spic, name, NULL, ViewMode, options, back) {
    char tmp[256];
    unsigned long fsize, bsize;

    _lcurrent =
    _blpic = blpic;
    _slpic = slpic;

    _viewMode = -1;
    SetViewMode(ViewMode);

    _type = type;
    _size = size;
    _uid  = uid;
    _gid  = gid;

    // FIXME: hack...
    _is_link = (blpic != NULL);

    _subnames = new OString*[5];

    // file type
    sprintf(tmp, "%c%c%c%c%c%c%c%c%c%c",
                (_is_link ?
                 'l' : 
                 (S_ISREG(type) ?
                  '-' : 
                  (S_ISDIR(type) ?
                   'd' : 
                    (S_ISCHR(type) ?
                     'c' : 
                     (S_ISBLK(type) ?
                      'b' : 
                      (S_ISFIFO(type) ?
                       'p' : 
                       (S_ISSOCK(type) ?
                        's' : '?' ))))))),
                 ((type & S_IRUSR) ? 'r' : '-'),
                 ((type & S_IWUSR) ? 'w' : '-'),
                 ((type & S_ISUID) ? 's' : ((type & S_IXUSR) ? 'x' : '-')),
                 ((type & S_IRGRP) ? 'r' : '-'),
                 ((type & S_IWGRP) ? 'w' : '-'),
                 ((type & S_ISGID) ? 's' : ((type & S_IXGRP) ? 'x' : '-')),
                 ((type & S_IROTH) ? 'r' : '-'),
                 ((type & S_IWOTH) ? 'w' : '-'),
                 ((type & S_ISVTX) ? 't' : ((type & S_IXOTH) ? 'x' : '-')));
    _subnames[0] = new OString(tmp);

    // file size
    fsize = bsize = _size;
    if (fsize > 1024) {
      fsize /= 1024;
      if (fsize > 1024) {
        // 3.7MB is more informative than just 3MB
        sprintf(tmp, "%ld.%ldM", fsize/1024, (fsize%1024)/103, bsize);
      } else {
        sprintf(tmp, "%ld.%ldK", bsize/1024, (bsize%1024)/103, bsize);
      }
    } else {
      sprintf(tmp, "%ld", bsize);
    }
    _subnames[1] = new OString(tmp);


    {
      struct group *grp;
      struct passwd *pwd;
      char   tmp[256];

      pwd = getpwuid(_uid);
      if (pwd) {
        _subnames[2] = new OString(pwd->pw_name);
      } else {
        sprintf(tmp, "%d", _uid);
        _subnames[2] = new OString(tmp);
      }
      grp = getgrgid(_gid);
      if (grp) {
        _subnames[3] = new OString(grp->gr_name);
      } else {
        sprintf(tmp, "%d", _gid);
        _subnames[3] = new OString(tmp);
      }
    }

    _subnames[4] = NULL;

    int i;
    for (i=0; _subnames[i] != NULL; ++i);
    _ctw = new int[i];
    for (i=0; _subnames[i] != NULL; ++i)
      _ctw[i] = XTextWidth(_fontStruct, _subnames[i]->GetString(),
                                        _subnames[i]->GetLength());

}

void OXFileItem::SetViewMode(int ViewMode) {
  OXListItem::SetViewMode(ViewMode);

  if (ViewMode == LV_LARGE_ICONS)
    _lcurrent = _blpic;
  else
    _lcurrent = _slpic;

  _client->NeedRedraw(this);
}

void OXFileItem::_DoRedraw() {
  int ix, iy, lx, ly;

  OXListItem::_DoRedraw();
  if (!_lcurrent) return;

  if (_viewMode == LV_LARGE_ICONS) {
    ix = (_w - _lcurrent->GetWidth()) >> 1;
    iy = 0;
  } else {
    ix = 0;
    iy = (_h - _lcurrent->GetHeight()) >> 1;
  }
 
  _lcurrent->Draw(GetDisplay(), _id, _normGC, ix, iy);
}

//---------------------------------------------------------------------------

OXFileContainer::OXFileContainer(const OXWindow *p,
         OMimeTypes *mime_types, char *filter,
         int w, int h, unsigned int options, unsigned long back) :
  OXListViewContainer(p, w, h, options, back) {

    MimeTypesList = mime_types;

    _sortType = SORT_BY_NAME;
    _refresh = new OTimer(this, 5000);

    _folder_s = _client->GetPicture("folder.s.xpm");
    _folder_t = _client->GetPicture("folder.t.xpm");
    _app_s    = _client->GetPicture("app.s.xpm");
    _app_t    = _client->GetPicture("app.t.xpm");
    _doc_s    = _client->GetPicture("doc.s.xpm");
    _doc_t    = _client->GetPicture("doc.t.xpm");
    _slink_s  = _client->GetPicture("slink.s.xpm");
    _slink_t  = _client->GetPicture("slink.t.xpm");

    _compileFilter(filter);

    if (!_folder_s || !_folder_t ||
        !_app_s    || !_app_t    ||
        !_doc_s    || !_doc_t    ||
        !_slink_s  || !_slink_t)
      FatalError("OXFileContainer: Missing required pixmap(s)\n");
}

OXFileContainer::~OXFileContainer() {
  if (_refresh) delete _refresh;
  _client->FreePicture(_folder_s);
  _client->FreePicture(_folder_t);
  _client->FreePicture(_app_s);
  _client->FreePicture(_app_t);
  _client->FreePicture(_doc_s);
  _client->FreePicture(_doc_t);
  _client->FreePicture(_slink_s);
  _client->FreePicture(_slink_t);
}

void OXFileContainer::SetFileFilter(char *filter) {
  _compileFilter(filter);
  DisplayDirectory();
}

int OXFileContainer::HandleTimer(OTimer *t) {
  struct stat sbuf;

  if (t != _refresh) return False;

  if (stat(".", &sbuf) == 0)
    if (_st_mtime != sbuf.st_mtime) DisplayDirectory();

  delete _refresh;
  _refresh = new OTimer(this, 5000);

  return True;
}

void OXFileContainer::Sort(int sortType) {
  OXCanvas *canvas;
  SListFrameElt *ptr;
  int n;

  _sortType = sortType;

  for (n=0, ptr=_flist; ptr!=NULL; n++, ptr=ptr->next);

  _doSort(&_flist, n);
  canvas = (OXCanvas *) this->GetParent()->GetParent();
  canvas->Layout();
}

//---- Original list sort code by David Kastrup (c) 1992, Germany.

SListFrameElt **OXFileContainer::_doSort(SListFrameElt **head, int n) {
  SListFrameElt *p1, *p2, **h2, **t2;
  int m;

  switch (n) {
  case 0:
    return head;

  case 1:
    return &((*head)->next);

  case 2:
    p2 = (p1 = *head)->next;
    if (_compare(p1, p2)) return &(p2->next);
    p1->next = (*head = p2)->next;
    return &((p2->next = p1)->next);
  }

  n -= m = n / 2;
  t2 = _doSort(h2 = _doSort(head, n), m);

  if (_compare(p1 = *head, p2 = *h2)) {
    do {
      if (!--n) return *h2 = p2, t2;
    } while (_compare(p1 = *(head = &(p1->next)), p2));
  }

  while(1) {
    *head = p2;
    do {
      if (!--m) return *h2 = *t2, *t2 = p1, h2;
    } while (!_compare(p1, p2 = *(head = &(p2->next))));
      *head = p1;
      do {
      if (!--n) return *h2 = p2, t2;
    } while (_compare(p1 = *(head = &(p1->next)), p2));
  }
}

int OXFileContainer::_compare(SListFrameElt *p1, SListFrameElt *p2) {
  int type1, type2;

  OXFileItem *f1 = (OXFileItem *) p1->frame;
  OXFileItem *f2 = (OXFileItem *) p2->frame;

  switch (_sortType) {
  default:
  case SORT_BY_NAME:
    break;

  case SORT_BY_TYPE:
    //--- this is not exactly what I want...

    type1 = f1->GetType();
    type2 = f2->GetType();

    //--- use posix macros

    if (S_ISDIR(type1)) type1 = 1;
    #if defined(S_IFLNK)
    else if ((type1 & S_IFMT) == S_IFLNK) type1 = 2;
    #endif
    #if defined(S_IFSOCK)
    else if ((type1 & S_IFMT) == S_IFSOCK) type1 = 3;
    #endif
    else if (S_ISFIFO(type1)) type1 = 4;
    else if (S_ISREG(type1) &&
            (type1 & S_IXUSR)) type1 = 5;
    else type1 = 6;
   
    if (S_ISDIR(type2)) type2 = 1;
    #if defined(S_IFLNK)
    else if ((type2 & S_IFMT) == S_IFLNK) type2 = 2;
    #endif
    #if defined(S_IFSOCK)
    else if ((type2 & S_IFMT) == S_IFSOCK) type2 = 3;
    #endif
    else if (S_ISFIFO(type2)) type2 = 4;
    else if (S_ISREG(type2) &&
            (type2 & S_IXUSR)) type2 = 5;
    else type2 = 6;

    if (type1 != type2) return (type1 <= type2);
    break;

  case SORT_BY_SIZE:
    if (f1->GetSize() != f2->GetSize()) return (f1->GetSize() <= f2->GetSize());
    break;

  }
  return (strcmp(f1->GetName()->GetString(), f2->GetName()->GetString()) <= 0);
}


//---- Determine the file picture for the given file type.

void OXFileContainer::GetFilePictures(const OPicture **pic,
             const OPicture **lpic, int file_type, int is_link, 
             char *name, int small) {
  char temp[BUFSIZ];

  *pic = MimeTypesList->GetIcon(name, small);
  if (*pic == NULL) {
    *pic = small ? _doc_t : _doc_s;
    if (S_ISREG(file_type) &&
               (file_type) & S_IXUSR) {
      if (small) {
        sprintf(temp, "%s.t.xpm", name);
        *pic = _client->GetPicture(temp);
        if (*pic == NULL) *pic = _app_t;
      } else {
        sprintf(temp, "%s.s.xpm", name);
        *pic = _client->GetPicture(temp);
        if (*pic == NULL) *pic = _app_s;
      }
    }
    if (S_ISDIR(file_type))
      *pic = small ? _folder_t : _folder_s;
  }

  if (is_link)
    *lpic = small ? _slink_t : _slink_s;
  else
    *lpic = NULL;
}


//---- Change current directory

void OXFileContainer::ChangeDirectory(const char *path) {
  if (chdir(path) == 0) DisplayDirectory();
}


//---- Display the contents of the current directory in the container.
//     This can be used to refresh the contents of the window.

void OXFileContainer::DisplayDirectory() {

  RemoveAll();
  CreateFileList();

  //---- this automatically calls layout
  Sort(_sortType);

  //---- notify application about the changes
  SendMessage(_msgWindow, MK_MSG(XC_CONTAINER, CT_SELCHANGED),
                          _total, _selected);

  MapSubwindows();
}


//---- This function creates the file list from current dir.

void OXFileContainer::CreateFileList() {
  DIR *dirp;
  struct stat sbuf;
  struct dirent *dp;
  int type, is_link, uid, gid;
  unsigned long size;
  char *t, *name;
  char  filename[256];
  const OPicture *pic, *lpic, *spic, *slpic;

  if (stat(".", &sbuf) == 0) _st_mtime = sbuf.st_mtime;

  if ((dirp = opendir(".")) == NULL) return;

  while ((dp = readdir(dirp)) != NULL) {
    name = dp->d_name;
    if (strcmp (name, ".") && strcmp (name, "..")) {
      strcpy(filename, name);
      type = 0;
      size = 0;
      is_link = False;
      if (lstat(name, &sbuf) == 0) {
        if (!S_ISDIR(sbuf.st_mode) &&
          _fileMatch(name) == 0) continue;
        is_link = S_ISLNK(sbuf.st_mode);
        type = sbuf.st_mode;
        size = sbuf.st_size;
        uid = sbuf.st_uid;
        gid = sbuf.st_gid;
        if (is_link) {
          if (stat(name, &sbuf) == 0) {
            type = sbuf.st_mode;
            size = sbuf.st_size;
          }
        }
      } else {
        char msg[256];

        sprintf(msg, "Can't read file attributes of \"%s\": %s.",
                     name, strerror(errno));
        new OXMsgBox(_client->GetRoot(), _GetMainFrame(),
                     new OString("Error"), new OString(msg),
                     MB_ICONSTOP, ID_OK);
      }
      GetFilePictures(&pic, &lpic, type, is_link, filename, False);
      GetFilePictures(&spic, &slpic, type, is_link, filename, True);
      AddItem(new OXFileItem(this, pic, lpic, spic, slpic,
                     new OString(name), type, size, uid, gid, _viewMode));
      _total++;
    }
  }

  closedir(dirp);
}

void OXFileContainer::_compileFilter(const char *filter) {
  char        expression[50];
  int         pos = 0;
  const char *ptr = NULL;

  if (!filter) filter = "*";

  ptr = filter;
  while (*ptr != '\0') {
    if (*ptr == '*')
      expression[pos++] = '.';
    else if (*ptr == '.')
      expression[pos++] = '\\';
    expression[pos++] = *ptr;
    ptr++;
  }
  expression[pos] = '\0';
  regcomp(&_filter, expression, REG_EXTENDED);
}

int OXFileContainer::_fileMatch(const char *filename) {
  regmatch_t pmatch[1];
  size_t     nmatch = 1;

  if (filename) {
    if (regexec(&_filter, filename, nmatch, pmatch, 0) == 0) {
      if ((pmatch[0].rm_so == 0) &&
          (pmatch[0].rm_eo == strlen(filename))) {
        return 1;
      }
    }
  }
  return 0;
}
