/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/***************************************************************************
 *            directory.cc
 *
 *  Tue Apr 23 22:01:07 CEST 2013
 *  Copyright 2013 Jonas Suhr Christensen
 *  jsc@umbraculum.org
 ****************************************************************************/

/*
 *  This file is part of DrumGizmo.
 *
 *  DrumGizmo is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  DrumGizmo 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 Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with DrumGizmo; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
 */
#include "directory.h"

#include <dirent.h>
#include <stdio.h>
#include <string>
#include <algorithm>
#include <vector>
#include <string.h>
#include <unistd.h>

#include <platform.h>

#if DG_PLATFORM == DG_PLATFORM_WINDOWS
#include <direct.h>
#include <windows.h>
#endif

#include <hugin.hpp>

#define DRUMKIT_SUFFIX ".xml"

// http://en.wikipedia.org/wiki/Path_(computing)
#if DG_PLATFORM == DG_PLATFORM_WINDOWS
#define SEP "\\"
#else
#define SEP "/"
#endif

Directory::Directory(std::string path)
{
	setPath(path);
}

Directory::~Directory()
{
}

std::string Directory::seperator()
{
	return SEP;
}

void Directory::setPath(std::string path)
{
	//DEBUG(directory, "Setting path to '%s'\n", path.c_str());
	this->_path = cleanPath(path);
	refresh();
}

size_t Directory::count()
{
	return _files.size();
}

void Directory::refresh()
{
	_files = listFiles(_path, DIRECTORY_HIDDEN);
	//_files = listFiles(_path);
}

bool Directory::cd(std::string dir)
{
	//TODO: Should this return true or false?
	if(dir.empty() || dir == ".")
	{
		return true;
	}

	//DEBUG(directory, "Changing to '%s'\n", dir.c_str());
	if(exists(_path + SEP + dir))
	{
		std::string path = _path + SEP + dir;
		setPath(path);
		refresh();
		return true;
	}
	else
	{
		return false;
	}
}

bool Directory::cdUp()
{
	return this->cd("..");
}

std::string Directory::path()
{
	return cleanPath(_path);
}

Directory::EntryList Directory::entryList()
{
	return _files;
}

#define MAX_FILE_LENGTH 1024
std::string Directory::cwd()
{
	char path[MAX_FILE_LENGTH];
	char* c = getcwd(path, MAX_FILE_LENGTH);

	if(c)
	{
		return c;
	}
	else
	{
		return "";
	}
}

std::string Directory::cleanPath(std::string path)
{
	//DEBUG(directory, "Cleaning path '%s'\n", path.c_str());
	Directory::Path pathlst = parsePath(path);
	return Directory::pathToStr(pathlst);
}

Directory::EntryList Directory::listFiles(std::string path, unsigned char filter)
{
	DEBUG(directory, "Listing files in '%s'\n", path.c_str());

	Directory::EntryList entries;
	DIR *dir = opendir(path.c_str());
	if(!dir)
	{
		//DEBUG(directory, "Couldn't open directory '%s\n", path.c_str());
		return entries;
	}

	std::vector<std::string> directories;
	std::vector<std::string> files;

	struct dirent *entry;
	while((entry = readdir(dir)) != nullptr)
	{
		std::string name = entry->d_name;
		if(name == ".")
		{
			continue;
		}

		if(Directory::isRoot(path) && name == "..")
		{
			continue;
		}

		unsigned char entryinfo = 0;
		if(isHidden(path + SEP + name))
		{
			entryinfo |= DIRECTORY_HIDDEN;
		}

		std::string entrypath = path;
		entrypath += SEP;
		entrypath += entry->d_name;
		if(Directory::isDir(entrypath))
		{
			if(!(entryinfo && filter))
			{
				if(name == "..")
				{
					directories.push_back(entry->d_name);
				}
				else
				{
					directories.push_back(std::string(SEP) + entry->d_name);
				}
			}
		}
		else
		{
			int drumkit_suffix_length = strlen(DRUMKIT_SUFFIX);
			if((int)name.size() < drumkit_suffix_length)
			{
				continue;
			}

			if(name.substr(name.length() - drumkit_suffix_length,
			               drumkit_suffix_length) != DRUMKIT_SUFFIX)
			{
				continue;
			}


			//if(!(entryinfo && filter)) {
			files.push_back(entry->d_name);
			//}
		}
	}

#if DG_PLATFORM == DG_PLATFORM_WINDOWS
	//DEBUG(directory, "Root is %s\n", Directory::root(path).c_str());
	//DEBUG(directory, "Current path %s is root? %d", path.c_str(),
	//      Directory::isRoot(path));
	if(Directory::isRoot(path))
	{
		entries.push_back("..");
	}
#endif

	// sort
	for(int x = 0; x < (int)directories.size(); x++)
	{
		for(int y = 0; y < (int)directories.size(); y++)
		{
			if(directories[x] < directories[y])
			{
				std::string tmp = directories[x];
				directories[x] = directories[y];
				directories[y] = tmp;
			}
		}
	}

	for(int x = 0; x < (int)files.size(); x++)
	{
		for(int y = 0; y < (int)files.size(); y++)
		{
			if(files[x] < files[y])
			{
				std::string tmp = files[x];
				files[x] = files[y];
				files[y] = tmp;
			}
		}
	}


	for(auto directory : directories)
	{
		entries.push_back(directory);
	}

	for(auto file : files)
	{
		entries.push_back(file);
	}

	closedir(dir);
	return entries;
}

bool Directory::isRoot(std::string path)
{
#if DG_PLATFORM == DG_PLATFORM_WINDOWS
	std::transform(path.begin(), path.end(), path.begin(), ::tolower);
	std::string root_str = Directory::root(path);
	std::transform(root_str.begin(), root_str.end(), root_str.begin(), ::tolower);

	// TODO: This is not a correct root calculation, but works with partitions
	if(path.size() == 2)
	{
		if(path == root_str)
		{
			return true;
		}
		else
		{
			return false;
		}
	}
	else
	{
		if (path.size() == 3)
		{
			if(path == root_str + SEP)
			{
				return true;
			}
			return false;
		}
		else
		{
			return false;
		}
	}
#else
	if(path == SEP)
	{
		return true;
	}
	else
	{
		return false;
	}
#endif
}

std::string Directory::root()
{
	return root(cwd());
}

std::string Directory::root(std::string path)
{
#if DG_PLATFORM == DG_PLATFORM_WINDOWS
	if(path.size() < 2)
	{
		return "c:"; // just something default when input is bad
	}
	else
	{
		return path.substr(0, 2);
	}
#else
	return SEP;
#endif
}

Directory::DriveList Directory::drives()
{
	Directory::DriveList drives;
#if DG_PLATFORM == DG_PLATFORM_WINDOWS
	unsigned int d = GetLogicalDrives();
	for(int i = 0; i < 32; ++i)
	{
		if(d & (1 << i))
		{
			drive_t drive;
			char name[] = "x:";
			name[0] = i + 'a';

			drive.name = name;
			drive.number = i;
			drives.push_back(drive);
		}
	}
#endif
	return drives;
}

bool Directory::isDir()
{
	return isDir(_path);
}

bool Directory::isDir(std::string path)
{
	//DEBUG(directory, "Is '%s' a directory?\n", path.c_str());
	struct stat st;
	if(stat(path.c_str(), &st) == 0)
	{
		if((st.st_mode & S_IFDIR) != 0)
		{
			//DEBUG(directory, "\t...yes!\n");
			return true;
		}
	}
	//DEBUG(directory, "\t...no!\n");
	return false;
}

bool Directory::fileExists(std::string filename)
{
	return !isDir(_path + SEP + filename);
}

bool Directory::exists(std::string path)
{
	struct stat st;
	if(stat(path.c_str(), &st) == 0)
	{
		return true;
	}
	else
	{
		return false;
	}
}

bool Directory::isHidden(std::string path)
{
	//DEBUG(directory, "Is '%s' hidden?\n", path.c_str());
#if DG_PLATFORM == DG_PLATFORM_WINDOWS
	// We dont want to filter out '..' pointing to root of a partition
	unsigned pos = path.find_last_of("/\\");
	std::string entry = path.substr(pos+1);
	if(entry == "..")
	{
		return false;
	}

	DWORD fattribs = GetFileAttributes(path.c_str());
	if(fattribs & FILE_ATTRIBUTE_HIDDEN)
	{
		//DEBUG(directory, "\t...yes!\n");
		return true;
	}
	else
	{
		if(fattribs & FILE_ATTRIBUTE_SYSTEM)
		{
			//DEBUG(directory, "\t...yes!\n");
			return true;
		}
		else
		{
			//DEBUG(directory, "\t...no!\n");
			return false;
		}
	}
#else
	unsigned pos = path.find_last_of("/\\");
	std::string entry = path.substr(pos+1);
	if(entry.size() > 1 &&
	   entry.at(0) == '.' &&
	   entry.at(1) != '.')
	{
		//DEBUG(directory, "\t...yes!\n");
		return true;
	}
	else
	{
		//DEBUG(directory, "\t...no!\n");
		return false;
	}
#endif
}

Directory::Path Directory::parsePath(std::string path_str)
{
	//TODO: Handle "." input and propably other special cases

	//DEBUG(directory, "Parsing path '%s'", path_str.c_str());
	Directory::Path path;

	std::string current_char;
	std::string prev_char;
	std::string dir;
	for(size_t c = 0; c < path_str.size(); ++c)
	{
		current_char = path_str.at(c);

		if(current_char == SEP)
		{
			if(prev_char == SEP)
			{
				dir.clear();
				prev_char = current_char;
				continue;
			}
			else
			{
				if(prev_char == ".")
				{
					prev_char = current_char;
					continue;
				}
			}
			if(!dir.empty())
			{
				path.push_back(dir);
			}
			dir.clear();
			continue;
		}
		else
		{
			if(current_char == ".")
			{
				if(prev_char == ".")
				{
					dir.clear();
					if(!path.empty())
					{
						path.pop_back();
					}
					continue;
				}
			}
		}
		dir += current_char;
		prev_char = current_char;
	}

	if(!dir.empty())
	{
		path.push_back(dir);
	}

	return path;
}

std::string Directory::pathToStr(Directory::Path& path)
{
	std::string cleaned_path;
	//DEBUG(directory, "Number of directories in path is %d\n", (int)path.size());

	for(auto it = path.begin(); it != path.end(); ++it)
	{
		std::string dir = *it;
		//DEBUG(directory, "\tDir '%s'\n", dir.c_str());
#if DG_PLATFORM == DG_PLATFORM_WINDOWS
		if(it != path.begin())
		{
			cleaned_path += SEP;
		}
		cleaned_path += dir;
#else
		cleaned_path += SEP + dir;
#endif
	}

	//DEBUG(directory, "Cleaned path '%s'\n", cleaned_path.c_str());

	if(cleaned_path.empty())
	{
		cleaned_path = Directory::root();
#if DG_PLATFORM == DG_PLATFORM_WINDOWS
		cleaned_path += SEP;
#endif
	}

#if DG_PLATFORM == DG_PLATFORM_WINDOWS
	if(cleaned_path.size() == 2)
	{
		cleaned_path += SEP;
	}
#endif

	return cleaned_path;
}

std::string Directory::pathDirectory(std::string filepath)
{
	if(Directory::isDir(filepath))
	{
		return filepath;
	}

	Directory::Path path = parsePath(filepath);
	if(path.size() > 0)
	{
		path.pop_back();
	}

	return Directory::pathToStr(path);
}
