/*
   Copyright (C) 2001-2006, William Joseph.
   All Rights Reserved.

   This file is part of GtkRadiant.

   GtkRadiant 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.

   GtkRadiant 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 GtkRadiant; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#if !defined( INCLUDED_FS_FILESYSTEM_H )
#define INCLUDED_FS_FILESYSTEM_H

#include "string/string.h"
#include "os/path.h"

#include <map>

inline unsigned int path_get_depth( const char* path ){
	unsigned int depth = 0;
	while ( path != 0 && path[0] != '\0' )
	{
		path = strchr( path, '/' );
		if ( path != 0 ) {
			++path;
		}
		++depth;
	}
	return depth;
}

/// \brief A generic unix-style file-system which maps paths to files and directories.
/// Provides average O(log n) find and insert methods.
/// \param file_type The data type which represents a file.
template<typename file_type>
class GenericFileSystem
{
class Path
{
CopiedString m_path;
unsigned int m_depth;
public:
Path( const char* path )
	: m_path( path ), m_depth( path_get_depth( c_str() ) ){
}
Path( StringRange range )
	: m_path( range ), m_depth( path_get_depth( c_str() ) ){
}
bool operator<( const Path& other ) const {
	return string_less_nocase( c_str(), other.c_str() );
}
unsigned int depth() const {
	return m_depth;
}
const char* c_str() const {
	return m_path.c_str();
}
};

class Entry
{
file_type* m_file;
public:
Entry() : m_file( 0 ){
}
Entry( file_type* file ) : m_file( file ){
}
file_type* file() const {
	return m_file;
}
bool is_directory() const {
	return file() == 0;
}
};

typedef std::map<Path, Entry> Entries;
Entries m_entries;

public:
typedef typename Entries::iterator iterator;
typedef typename Entries::value_type value_type;
typedef Entry entry_type;

iterator begin(){
	return m_entries.begin();
}
iterator end(){
	return m_entries.end();
}

/// \brief Returns the file at \p path.
/// Creates all directories below \p path if they do not exist.
/// O(log n) on average.
entry_type& operator[]( const Path& path ){
	{
		const char* end = path_remove_directory( path.c_str() );
		while ( end[0] != '\0' )
		{
			Path dir( StringRange( path.c_str(), end ) );
			m_entries.insert( value_type( dir, Entry( 0 ) ) );
			end = path_remove_directory( end );
		}
	}

	return m_entries[path];
}

/// \brief Returns the file at \p path or end() if not found.
iterator find( const Path& path ){
	return m_entries.find( path );
}

iterator begin( const char* root ){
	if ( root[0] == '\0' ) {
		return m_entries.begin();
	}
	iterator i = m_entries.find( root );
	if ( i == m_entries.end() ) {
		return i;
	}
	return ++i;
}

/// \brief Performs a depth-first traversal of the file-system subtree rooted at \p root.
/// Traverses the entire tree if \p root is "".
/// Calls \p visitor.file() with the path to each file relative to the filesystem root.
/// Calls \p visitor.directory() with the path to each directory relative to the filesystem root.
template<typename visitor_type>
void traverse( visitor_type visitor, const char* root ){
	unsigned int start_depth = path_get_depth( root );
	unsigned int skip_depth = 0;
	for ( iterator i = begin( root ); i != end() && i->first.depth() > start_depth; ++i )
	{
		if ( i->first.depth() == skip_depth ) {
			skip_depth = 0;
		}
		if ( skip_depth == 0 ) {
			if ( !i->second.is_directory() ) {
				visitor.file( i->first.c_str() );
			}
			else if ( visitor.directory( i->first.c_str(), i->first.depth() - start_depth ) ) {
				skip_depth = i->first.depth();
			}
		}
	}
}
};

#endif
