/***************************************************************************
                              CDirectoryDS.cpp  -  description
                              -------------------
        begin                : Nov 28 2002
        copyright            : (C) 2002-2006 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 <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <ctype.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 <sptk3/filedefs.h>

#define CASE_INSENSITIVE 1

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

using namespace std;
using namespace sptk;

/* 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;

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

				bool          is_link = false;

				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";
				}

				CFieldList   *df = new CFieldList(false);
				df->push_back(" ",false)         = pixmapPtr;
				df->push_back("Name",false)      = file;
				if (modeName == "Directory")
					df->push_back("Size",false)   = "";
				else df->push_back("Size",false) = (int)st.st_size;
				df->push_back("Type",false)      = modeName;
				df->push_back("Modified",false)  = CDateTime::convertCTime(st.st_mtime);
				df->push_back("",false)          = 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;
}
