/***************************************************************************
                              CDirectoryDS.cpp  -  description
                              -------------------
        begin                : Thu Nov 28 2002
        copyright            : (C) 2002 by Alexey Parshin
        email                : alexeyp@gmail.com
 ***************************************************************************/

/***************************************************************************
   This library is free software; you can redistribute it and/or modify it 
   under the terms of the GNU Library General Public License as published by
   the Free Software Foundation; either version 2 of the License, or (at 
   your option) any later version.

   This library 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 Library
   General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; if not, write to the Free Software Foundation, 
   Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.

   Please report all bugs and problems to "alexeyp@gmail.com"
 ***************************************************************************/

#include <string>
#include <stdio.h>

#include <FL/filename.H>
#include <FL/Fl_Pixmap.H>

#include <sptk3/CDirectoryDS.h>
#include <sptk3/CException.h>
#include <sptk3/CSmallPixmaps.h>
#include <sptk3/CStrings.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <ctype.h>

#ifndef _WIN32
# include <unistd.h>
#endif

#ifdef _WIN32

# define strcasecmp stricmp

# include <windows.h>
# include <io.h>
# include <direct.h>

# define S_ISLNK(m)      (false)
# define S_ISEXEC(m)     (((m) & _S_IEXEC) == _S_IEXEC)
#if !defined(__GNUC__) && !defined(__BORLANDC__)
# define S_ISREG(m)      (((m) & _S_IFREG) == _S_IFREG)
# define S_ISDIR(m)      (((m) & _S_IFDIR) == _S_IFDIR)
# define S_ISBLK(m)      (((m) & _S_IFBLK) == _S_IFBLK)
#endif /* __GNUC__ */

# define lstat stat

#else /* _WIN32 */

# include <dirent.h>

#ifndef S_ISEXEC
# define S_ISEXEC(m)  (((m) & S_IEXEC) == S_IEXEC)
#endif
#ifndef S_ISREG
# define S_ISREG(m)   (((m) & S_IFMT) == S_IFREG)
#endif
#ifndef S_ISDIR
# define S_ISDIR(m)   (((m) & S_IFMT) == S_IFDIR)
#endif
#ifndef S_ISBLK
# define S_ISBLK(m)   (((m) & S_IFMT) == S_IFBLK)
#endif

#endif /* _WIN32 */

#define CASE_INSENSITIVE 1

#ifdef _WIN32
const char slash = '\\';
#else
const char slash = '/';
#endif

using namespace std;

/* That function is adapted from Rich Salz. */
bool fl_file_match(const char *s, const char *p)
{
   int nesting;

   for (;;)
   {
      switch(*p++)
      {

         case '?' :           // match any single character
            if (!*s++) return false;
            break;

         case '*' :           // match 0-n of any characters
            // do trailing * quickly
            if (!*p) return true;
            while (!fl_file_match(s, p)) if (!*s++) return false;
            return true;

         case '[':            // match one character in set of form [abc-d] or [^a-b]
            {
               if (!*s) return false;
               bool reverse = (*p=='^' || *p=='!'); if (reverse) p++;
               bool matched = false;
               char last = 0;
               while (*p)
               {
                  if (*p=='-' && last)
                  {
                     if (*s <= *++p && *s >= last ) matched = true;
                     last = 0;
                  }
                  else
                  {
                     if (*s == *p) matched = true;
                  }
                  last = *p++;
                  if (*p==']') break;
               }
               if (matched == reverse) return false;
               s++; p++;
            }
            break;

         case '{' :           // {pattern1|pattern2|pattern3}
NEXTCASE:
            if (fl_file_match(s,p)) return true;
            for (nesting = 0;;)
            {
               switch (*p++)
               {
                  case '\\': if (*p) p++; break;
                  case '{': nesting++; break;
                  case '}': if (!nesting--) return false; break;
                  case '|': case ',': if (nesting==0) goto NEXTCASE;
                  case 0: return false;
               }
            }
         case '|':            // skip rest of |pattern|pattern} when called recursively
         case ',':
            for (nesting = 0; *p && nesting >= 0;)
            {
               switch (*p++)
               {
                  case '\\': if (*p) p++; break;
                  case '{': nesting++; break;
                  case '}': nesting--; break;
               }
            }
            break;
         case '}':
            break;

         case 0:              // end of pattern
            return !*s;

#if CASE_INSENSITIVE
         case '\\':           // quote next character
            if (*p) p++;
            if (*s++ != *(p-1)) return false;
            break;
         default:
            if (*s != *(p-1) && tolower(*s) != *(p-1)) return false;
            s++;
#else
         case '\\':           // quote next character
            if (*p) p++;
         default  :
            if (*s++ != *(p-1)) return false;
            break;
#endif
      }
   }
}

// Returns typename
string CDirectoryDS::getFileType(const struct stat &st, const Fl_Image *&image,const char *fname) const {
   bool executable = S_ISEXEC(st.st_mode);
   bool directory = false;
   image = CSmallPixmaps::pixmaps[SXPM_DOCUMENT];

   string modeName;
   if (S_ISDIR(st.st_mode)) {
      modeName = "Directory";
      executable = false;
      directory = true;
      image = CSmallPixmaps::pixmaps[SXPM_FOLDER];
   } 
   else if (S_ISREG(st.st_mode)) {
      if (executable) modeName = "Executable";
      else modeName = "File";
   }

   if (executable) {
      image = CSmallPixmaps::pixmaps[SXPM_EXECUTABLE];
   } else {
      if (!directory) {
         const char *ext = strrchr(fname,'.');
         const char *sep = strrchr(fname,slash);
         if (ext && ext > sep) {
            ext++;
            if (strcasecmp(ext,"doc") == 0)
               image = CSmallPixmaps::pixmaps[SXPM_DOC_DOCUMENT];
            if (strcasecmp(ext,"txt") == 0)
               image = CSmallPixmaps::pixmaps[SXPM_TXT_DOCUMENT];
            if (strcasecmp(ext,"xls") == 0)
               image = CSmallPixmaps::pixmaps[SXPM_XLS_DOCUMENT];
            if (strcasecmp(ext,"csv") == 0)
               image = CSmallPixmaps::pixmaps[SXPM_XLS_DOCUMENT];
         }
      }
   }

   return modeName;
}

// Define access mode constants if they aren't already defined.
#ifndef R_OK
# define R_OK 04
#endif

// dataset navigation

string absolutePath(string path) {
   char slashStr[] = { slash, 0 };
   char currentDir[256];
   string fullPath;
   getcwd(currentDir,255);
#ifdef _WIN32
   path = replaceAll(path,"/","\\");
   if (path[1] == ':')
      fullPath = path;
   else if (path[0] == '\\') {
      fullPath = string(currentDir).substr(0,2) + path;
   }
   else fullPath = string(currentDir) + slashStr + path;
#else
   path = replaceAll(path,"\\","/");
   if (path[0] == slash)
      fullPath = path;
   else fullPath = string(currentDir) + slashStr + path;
#endif
   CStrings pathItems(fullPath,slashStr);
   for (unsigned i = 0; i < pathItems.size(); i++) {
      if ( pathItems[i] == ".." ) {
         pathItems.remove(i);
         i--;
         if (i > 0) {
            pathItems.remove(i);
            i--;
         }
      }
      if ( pathItems[i] == "." ) {
         pathItems.remove(i);
         i--;
      }
   }
   path = pathItems.asString(slashStr);
   if (!path.length()) path = slashStr;
   return path;
}

void CDirectoryDS::directory(string d) {
   m_directory = absolutePath(d); 
}

// read the directory() and move item into the first entry
bool CDirectoryDS::open() {
   clear();

   int dlen = m_directory.length()-1;
   if (dlen) {
      if (m_directory[dlen] != slash)
         m_directory += slash;
   }

   dirent **files;
   int num_files = fl_filename_list(m_directory.c_str(), &files);

   int index = 1;

   if (num_files>0) {
      struct stat st;
      int again = 2;
      if(showPolicy() & (DDS_NO_SORT|DDS_HIDE_FILES)) again--;
      do {
         for(int n=0; n < num_files; n++) {
            if(!files[n]) continue;

            char *file = (char *)files[n]->d_name;
#ifdef _WIN32
            int len = strlen(file);
            if (len && file[len-1] == '/')
               file[len-1] = 0;
#endif
            if (!strcmp(file, "..") || !strcmp(file, ".")) continue;

            CFieldList   *df = new CFieldList(false);
            bool          is_link = false;

            if (showPolicy()&DDS_HIDE_DOT_FILES && file[0]=='.') 
               continue;

            string fullName = m_directory + file;

            lstat(fullName.c_str(), &st);

#ifndef _WIN32
            if ((st.st_mode & S_IFLNK) == S_IFLNK) {
               is_link = true;
               stat(fullName.c_str(), &st);
            }
#endif
            if (!S_ISDIR(st.st_mode)) {
               unsigned patternCount = m_pattern.size();
               if (patternCount) {
                  bool matchFound = false;
                  for (unsigned i = 0; i < patternCount; i++) {
                     if (fl_file_match(file, m_pattern[i].c_str())) {
                        matchFound = true;
                        break;
                     }
                  }
                  if (!matchFound)
                     continue;
               }
               if (!(showPolicy() & DDS_NO_SORT) && again==2) 
                  continue;
               if (showPolicy() & DDS_HIDE_FILES)
                  continue;
            } else {
               if (showPolicy() & DDS_HIDE_DIRECTORIES) continue;
            }

            const Fl_Image *pixmapPtr = 0;
            string modeName = getFileType(st, pixmapPtr,file);

            if (is_link) {
               modeName += ' ';
               modeName += "link";
            }

            df->push_back(" ")        = pixmapPtr;
            df->push_back("Name")     = file;
            if (modeName == "Directory")
               df->push_back("Size")  = "";
            else df->push_back("Size")     = (int)st.st_size;
            df->push_back("Type")     = modeName;
            df->push_back("Modified") = CDateTime::convertCTime(st.st_mtime);
            df->push_back("")         = index; // Fake key value

            index++;

            if (access(fullName.c_str(), R_OK)!=0) {
               (*df)[0].flags = FL_ALIGN_LEFT;
               (*df)[1].flags = FL_ALIGN_LEFT;
            }

            m_list.push_back(df);

            free((struct dirent*)files[n]);
            files[n] = 0;
         }
      } while(--again);

      first();

      int defaultWidths[5] = { 3, 30, 10, 10, 16 };
      if(m_current) 
         for (unsigned f = 0; f < 5; f++) {
            (*this)[f].flags = FL_ALIGN_LEFT;
            (*this)[f].width = defaultWidths[f];
         }

      free((struct dirent**)files);
   }

   return m_list.size()>0;
}
