/*
 * Copyright 2011-2012 Arx Libertatis Team (see the AUTHORS file)
 *
 * This file is part of Arx Libertatis.
 *
 * Arx Libertatis 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 3 of the License, or
 * (at your option) any later version.
 *
 * Arx Libertatis 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 Arx Libertatis.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "io/fs/FilePath.h"

#include <algorithm>

#include "platform/Platform.h"

#include <iostream>

using std::string;
using std::copy;

namespace fs {

const char path::dir_or_ext_sep[] = "\\/.";
const char path::any_dir_sep[] = "\\/";

namespace {

inline bool is_path_up(const std::string & str, size_t pos) {
	return (str.length() == pos + 2 && str[pos] == '.' && str[pos + 1] == '.')
	       || (str.length() >= pos + 3 && str[pos] == '.' && str[pos + 1] == '.'
	           && str[pos + 2] == path::dir_sep);
}

}

path path::create(const std::string & src) {
	path result;
	result.pathstr = src;
	return result;
}

path path::resolve(const path & a, const path & b) {
	
	size_t bpos = 0;
	size_t apos = a.pathstr.length();
	while(true) {
		
		size_t dirpos = a.pathstr.find_last_of(dir_sep, apos - 1);
		
		if(dirpos == std::string::npos) {
			if(bpos + 3 >= b.pathstr.length()) {
				return create(".");
			} else {
				return b.pathstr.substr(bpos + 3);
			}
		}
		
		if(is_path_up(a.pathstr, dirpos + 1)) {
			return create(a.pathstr.substr(0, apos) + dir_sep + b.pathstr.substr(bpos));
		}
		
		if(dirpos == 0 || (dirpos == 1 && a.pathstr[0] == dir_sep)) {
			if(dirpos != apos - 1) {
				bpos += 3;
			}
			if(bpos >= b.pathstr.length()) {
				return create(a.pathstr.substr(0, dirpos) + dir_sep);
			} else {
				return create(a.pathstr.substr(0, dirpos) + dir_sep + b.pathstr.substr(bpos));
			}
		}
		
		apos = dirpos, bpos += 3;
		
		if(!is_path_up(b.pathstr, bpos)) {
			if(bpos >= b.pathstr.length()) {
				return create(a.pathstr.substr(0, apos));
			} else {
				return create(a.pathstr.substr(0, apos) + dir_sep + b.pathstr.substr(bpos));
			}
		}
	}
	
}

path path::operator/(const path & other) const {
	if(other.is_absolute() || empty() || (is_dot() && !other.empty())) {
		return other;
	} else if(other.empty() || other.is_dot()) {
		return *this;
	} else if(other.is_up()) {
		return resolve(*this, other);
	} else {
		path result = *this;
		if(result.pathstr[result.pathstr.length() - 1] != dir_sep) {
			result.pathstr += dir_sep;
		}
		result.pathstr += other.pathstr;
		return result;
	}
}

path & path::operator/=(const path & other) {
	if(other.is_absolute() || empty() || (is_dot() && !other.empty())) {
		return *this = other;
	} else if(other.empty() || other.is_dot()) {
		return *this;
	} else if(other.is_up()) {
		return *this = resolve(*this, other);
	} else {
		if(pathstr[pathstr.length() - 1] != dir_sep) {
			pathstr += dir_sep;
		}
		pathstr += other.pathstr;
		return *this;
	}
}

string path::basename() const {
	if(!has_info()) {
		return empty() ? std::string() : std::string("..");
	}
	size_t extpos = pathstr.find_last_of(dir_or_ext_sep);
	if(extpos == string::npos) {
		return pathstr;
	} else if(pathstr[extpos] != ext_sep) {
		return pathstr.substr(extpos + 1);
	} else if(extpos == 0) {
		return std::string();
	}
	size_t dirpos = pathstr.find_last_of(dir_sep, extpos - 1);
	if(dirpos == string::npos) {
		return pathstr.substr(0, extpos);
	} else {
		return pathstr.substr(dirpos + 1, extpos - dirpos - 1);
	}
}

string path::ext() const {
	if(!has_info()) {
		return std::string();
	}
	size_t extpos = pathstr.find_last_of(dir_or_ext_sep);
	if(extpos == string::npos || pathstr[extpos] != ext_sep) {
		return std::string();
	} else {
		return pathstr.substr(extpos);
	}
}

path & path::set_ext(const std::string & ext) {
	arx_assert_msg(ext.empty()
	               || (ext[0] != dir_sep
	                   && ext.find_first_of(dir_or_ext_sep, 1) == std::string::npos),
	               "bad file ext: \"%s\"", ext.c_str());
	if(!has_info() && !empty()) {
		return *this;
	}
	size_t extpos = pathstr.find_last_of(dir_or_ext_sep);
	if(extpos == string::npos || pathstr[extpos] != ext_sep) {
		return (((ext.empty() || ext[0] != ext_sep) ? (pathstr += ext_sep)
		                                            : pathstr).append(ext), *this);
	} else {
		if(ext.empty() || ext[0] != ext_sep) {
			pathstr.resize(extpos + 1 + ext.size());
			std::copy(ext.begin(), ext.end(), pathstr.begin() + extpos + 1);
		} else {
			pathstr.resize(extpos + ext.size());
			std::copy(ext.begin() +  1, ext.end(), pathstr.begin() + extpos + 1);
		}
		return *this;
	}
}

path & path::remove_ext() {
	if(!has_info()) {
		return *this;
	}
	size_t extpos = pathstr.find_last_of(dir_or_ext_sep);
	if(extpos != string::npos && pathstr[extpos] == ext_sep) {
		pathstr.resize(extpos);
	}
	return *this;
}

path & path::set_filename(const std::string & filename) {
	arx_assert_msg(!filename.empty() && filename != "." && filename != ".."
	               && filename.find_first_of(any_dir_sep) == std::string::npos,
	               "bad filename: \"%s\"", filename.c_str());
	if(!has_info()) {
		return ((empty() ? pathstr = filename : (pathstr += dir_sep).append(filename)), *this);
	}
	size_t dirpos = pathstr.find_last_of(dir_sep);
	if(dirpos == string::npos) {
		return (*this = filename);
	} else {
		pathstr.resize(dirpos + 1 + filename.size());
		std::copy(filename.begin(), filename.end(), pathstr.begin() + dirpos + 1);
		return *this;
	}
}

path & path::set_basename(const std::string & basename) {
	
	arx_assert_msg(!basename.empty() && basename != "." && basename != ".."
	               && basename.find_first_of(any_dir_sep) == std::string::npos,
	               "bad basename: \"%s\"", basename.c_str());
	
	if(!has_info()) {
		return ((empty() ? pathstr = basename : (pathstr += dir_sep).append(basename)), *this);
	}
	
	size_t extpos = pathstr.find_last_of(dir_or_ext_sep);
	
	// handle paths without an extension.
	if(extpos == string::npos) {
		return (*this = basename);
	} else if(pathstr[extpos] != ext_sep) {
		pathstr.resize(extpos + 1 + basename.size());
		std::copy(basename.begin(), basename.end(), pathstr.begin() + extpos + 1);
		return *this;
	}
	
	size_t dirpos = (extpos == 0) ? string::npos : pathstr.find_last_of(dir_sep, extpos - 1);
	
	if(dirpos == string::npos) { // no parent path
		pathstr = basename + pathstr.substr(extpos);
	} else {
		pathstr = pathstr.substr(0, dirpos + 1) + basename + pathstr.substr(extpos);
	}
	
	return *this;
}

path & path::append_basename(const std::string & basename_part) {
	
	arx_assert_msg(basename_part != "." && basename_part != ".." &&
	               basename_part.find_first_of(any_dir_sep) == std::string::npos,
	               "bad basename: \"%s\"", basename_part.c_str());
	
	if(!has_info()) {
		return ((empty() ? pathstr = basename_part : (pathstr += dir_sep).append(basename_part)), *this);
	}
	
	size_t extpos = pathstr.find_last_of(dir_or_ext_sep);
	if(extpos == string::npos || pathstr[extpos] != ext_sep) {
		return (pathstr += basename_part, *this);
	}
	size_t len = pathstr.length();
	pathstr.resize(pathstr.length() + basename_part.length());
	std::copy_backward(pathstr.begin() + extpos, pathstr.begin() + len, pathstr.end());
	std::copy(basename_part.begin(), basename_part.end(), pathstr.begin() + extpos);
	return *this;
}

path & path::append(const std::string & str) {
	
	arx_assert_msg(str != "." && str != ".."
	               && str.find_first_of(any_dir_sep) == std::string::npos,
	               "cannot append: \"%s\"", str.c_str());
	
	pathstr += str;
	return *this;
}

bool path::has_ext(const std::string & str) const {
	
	arx_assert_msg(str.empty()
	               || (str[0] != dir_sep
	                   && str.find_first_of(dir_or_ext_sep, 1) == std::string::npos),
	               "bad file ext: \"%s\"", str.c_str());
	
	if(!has_info()) {
		return false;
	}
	
	size_t extpos = pathstr.find_last_of(dir_or_ext_sep);
	if(extpos == string::npos || pathstr[extpos] != ext_sep) {
		return false;
	} else if(str.empty()) {
		return true;
	} else if(str[0] == ext_sep) {
		return !pathstr.compare(extpos + 1, pathstr.length() - extpos - 1, str, 1, str.length() - 1);
	} else {
		return !pathstr.compare(extpos + 1, pathstr.length() - extpos - 1, str);
	}
}

std::string path::load(const std::string & str) {
	
	std::string copy;
	copy.resize(str.length());
	
	size_t istart = 0, ostart = 0;
	while(istart < str.length()) {
		
		size_t pos = str.find_first_of(any_dir_sep, istart);
		if(pos == std::string::npos) {
			if(istart == 0) {
				return str;
			}
			pos = str.length();
		}
		
		size_t start = istart;
		istart = pos + 1;
		
		if(pos == start) {
			if(pos == 0) {
				// Aboslute path.
				copy[ostart++] = dir_sep;
			}
#if ARX_PLATFORM == ARX_PLATFORM_WIN32
			if(pos == 1) {
				// Network path
				copy[ostart++] = dir_sep;
			}
#endif
			// double slash
			continue;
		}
		
		if(pos - start == 1 && str[start] == '.') {
			if(pos == 1) {
				copy[ostart++] = '.';
			}
			// '.'
			continue;
		}
		
		if(ostart == 1 && copy[0] == '.') {
			ostart = 0;
		}
		
		if(pos - start == 2 && str[start] == '.' && str[start + 1] == '.') {
			// '..'
			if(ostart == 0) {
				copy[ostart++] = '.', copy[ostart++] = '.';
			} else {
				size_t last = copy.find_last_of(dir_sep, ostart - 1);
				if(last == string::npos) {
					if(ostart == 2 && copy[0] == '.' && copy[1] == '.') {
						copy[ostart++] = dir_sep, copy[ostart++] = '.', copy[ostart++] = '.';
					} else {
						copy[0] = '.', ostart = 1;
					}
				} else if(copy[ostart - 1] == dir_sep) {
					copy[ostart++] = '.', copy[ostart++] = '.';
				} else if(ostart - last - 1 == 2 && copy[last + 1] == '.' && copy[last + 2] == '.') {
					copy[ostart++] = dir_sep, copy[ostart++] = '.', copy[ostart++] = '.';
				} else {
					if(last == 0 || (last == 1 && copy[0] == dir_sep)) {
						ostart = last + 1;
					} else {
						ostart = last;
					}
				}
			}
			continue;
		}
		
		if(ostart != 0 && copy[ostart - 1] != dir_sep) {
			copy[ostart++] = dir_sep;
		}
		
		for(size_t p = start; p < pos; p++) {
			copy[ostart++] = str[p];
		}
	}
	
	copy.resize(ostart);
	
	return copy;
}

#if 0

static void test_resolve(const fs::path & left, const fs::path & right, const std::string & out) {
	
	fs::path result = left / right;
	arx_assert_msg(result.string() == out, "\"%s\" / \"%s\" -> \"%s\" != \"%s\"", 
	               left.string().c_str(), right.string().c_str(), result.string().c_str(),
	               out.c_str());
	
	fs::path temp = left;
	temp /= right;
	
	arx_assert_msg(temp.string() == out, "\"%s\" /= \"%s\" -> \"%s\" != \"%s\"",
	               left.string().c_str(), right.string().c_str(), temp.string().c_str(),
	               out.c_str());
}

static void test_path(const std::string & in, const std::string & out) {
	fs::path p(in);
	arx_assert_msg(p.string() == out, "\"%s\" -> \"%s\" != \"%s\"", in.c_str(),
	               p.string().c_str(), out.c_str());
}

static void test_parent(const fs::path & in, const std::string & out) {
	
	fs::path p = in.parent();
	arx_assert_msg(p.string() == out, "\"%s\".parent() -> \"%s\" != \"%s\"",
	               in.string().c_str(), p.string().c_str(), out.c_str());
	
	fs::path temp = in;
	temp.up();
	arx_assert_msg(temp.string() == out, "\"%s\".up() ->\"%s\" != \"%s\"",
	               in.string().c_str(), temp.string().c_str(), out.c_str());
	
}

static void path_test() {
	
	test_path(".", ".");
	test_path("./", ".");
	test_path(".////./././//././/", ".");
	test_path("./a", "a");
	test_path(".////./././//././/a", "a");
	test_path("./..", "..");
	test_path(".////./././//././/..", "..");
	test_path("./a/..", ".");
	
	test_path("/a", "/a");
	test_path("/a/b", "/a/b");
	test_path("/a/b/c", "/a/b/c");
	test_path("/a/", "/a");
	test_path("/a/b", "/a/b");
	test_path("/a/b/c/./", "/a/b/c");
	
	test_path("/", "/");
	test_path("/..", "/..");
	test_path("/../", "/..");
	test_path("/..//", "/..");
	test_path("/../..", "/../..");
	test_path("/..//..", "/../..");
	test_path("/../../", "/../..");
	test_path("/..//../", "/../..");
	test_path("/../..//", "/../..");
	test_path("/..//..//", "/../..");
	
	test_path("/a", "/a");
	test_path("/a/..", "/");
	test_path("/a/../", "/");
	test_path("/a/..//", "/");
	test_path("/a/../..", "/..");
	test_path("/a/..//..", "/..");
	test_path("/a/../../", "/..");
	test_path("/a/..//../", "/..");
	test_path("/a/../..//", "/..");
	test_path("/a/..//..//", "/..");
	
	test_path("/abcd", "/abcd");
	test_path("/abcd/..", "/");
	test_path("/abcd/../", "/");
	test_path("/abcd/..//", "/");
	test_path("/abcd/../..", "/..");
	test_path("/abcd/..//..", "/..");
	test_path("/abcd/../../", "/..");
	test_path("/abcd/..//../", "/..");
	test_path("/abcd/../..//", "/..");
	test_path("/abcd/..//..//", "/..");
	
	test_path("/../a", "/../a");
	test_path("/../a/", "/../a");
	test_path("/../a//", "/../a");
	test_path("/../a/..", "/..");
	test_path("/../a//..", "/..");
	test_path("/../a/../", "/..");
	test_path("/../a//../", "/..");
	test_path("/../a/..//", "/..");
	test_path("/../a//..//", "/..");
	
	// -------
	
	test_path("a", "a");
	test_path("a/b", "a/b");
	test_path("a/", "a");
	test_path("a/b/", "a/b");
	
	test_path("", "");
	test_path("..", "..");
	test_path("../", "..");
	test_path("..//", "..");
	test_path("../..", "../..");
	test_path("..//..", "../..");
	test_path("../../", "../..");
	test_path("..//../", "../..");
	test_path("../..//", "../..");
	test_path("..//..//", "../..");
	
	test_path("a", "a");
	test_path("a/..", ".");
	test_path("a/../", ".");
	test_path("a/..//", ".");
	test_path("a/../..", "..");
	test_path("a/..//..", "..");
	test_path("a/../../", "..");
	test_path("a/..//../", "..");
	test_path("a/../..//", "..");
	test_path("a/..//..//", "..");
	
	test_path("abcd", "abcd");
	test_path("abcd/..", ".");
	test_path("abcd/../", ".");
	test_path("abcd/..//", ".");
	test_path("abcd/../..", "..");
	test_path("abcd/..//..", "..");
	test_path("abcd/../../", "..");
	test_path("abcd/..//../", "..");
	test_path("abcd/../..//", "..");
	test_path("abcd/..//..//", "..");
	
	test_path("../a", "../a");
	test_path("../a/", "../a");
	test_path("../a//", "../a");
	test_path("../a/..", "..");
	test_path("../a//..", "..");
	test_path("../a/../", "..");
	test_path("../a//../", "..");
	test_path("../a/..//", "..");
	test_path("../a//..//", "..");
	
	test_path("/.", "/");
	
	// --- 
	
	test_resolve(".", "a", "a");
	test_resolve(".", "a/b", "a/b");
	test_resolve(".", "..", "..");
	test_resolve(".", "a", "a");
	test_resolve("a", "..", ".");
	test_resolve("a/b", "..", "a");
	test_resolve("a/b", "../..", ".");
	test_resolve("a", "..", ".");
	
	test_resolve(".", "", ".");
	test_resolve(".", "a/..", ".");
	
	test_resolve("/a", "b", "/a/b");
	test_resolve("/a", "b/c", "/a/b/c");
	test_resolve("/a/b", "c", "/a/b/c");
	
	test_resolve("/..", "..", "/../..");
	
	test_resolve("/a", "..", "/");
	test_resolve("/a", "../..", "/..");
	test_resolve("/a/..", "..", "/..");
	
	test_resolve("/abcd", "..", "/");
	test_resolve("/abcd", "../..", "/..");
	test_resolve("/abcd/..", "..", "/..");
	
	test_resolve("/" , "../a", "/../a");
	test_resolve("/..", "a", "/../a");
	test_resolve("/", "../a/..", "/..");
	test_resolve("/..", "a/..", "/..");
	test_resolve("/../a", "..", "/..");
	
	// ---
	
	test_parent("", "..");
	test_parent(".", "..");
	test_parent("a", ".");
	test_parent("a/b", "a");
	test_parent("/", "/..");
	test_parent("/a", "/");
	test_parent("/a/b", "/a");
	test_parent("..", "../..");
	test_parent("../..", "../../..");
	
}

#endif

} // namespace fs
