#include "transform.h"
#include "indent.h"
#include <regexp/regexp.h>
#include <regexp/format_string.h>
#include <oak/oak.h>
#include <text/case.h>
#include <text/ctype.h>
#include <text/parse.h>
#include <text/utf8.h>
#include <cf/cf.h>

static size_t count_columns (std::string const& str, size_t tabSize)
{
	size_t col = 0;
	citerate(ch, diacritics::make_range(str.data(), str.data() + str.size()))
		col += (*ch == '\t' ? tabSize - (col % tabSize) : (text::is_east_asian_width(*ch) ? 2 : 1));
	return col;
}

static std::string justify_line (std::string const& str, size_t width, size_t tabSize)
{
	size_t currentWidth = count_columns(str, tabSize);
	if(width <= currentWidth)
		return str;

	size_t numberOfSpaces = std::count(str.begin(), str.end(), ' ');
	size_t extraSpaces    = width - currentWidth;
	size_t spaceCount     = 0;

	std::string res = "";
	for(auto const& ch : str)
	{
		res.insert(res.end(), 1, ch);
		if(ch == ' ')
		{
			size_t from = (extraSpaces * spaceCount + numberOfSpaces/2) / numberOfSpaces;
			size_t to = (extraSpaces * (spaceCount+1) + numberOfSpaces/2) / numberOfSpaces;
			res.insert(res.end(), to-from, ' ');
			++spaceCount;
		}
	}
	return res;
}

namespace transform
{
	std::string null (std::string const& src)
	{
		return "";
	}

	std::string unwrap (std::string src)
	{
		src = format_string::replace(src, "[\\s&&[^\n\xA0]]+\n", "\n");
		src = format_string::replace(src, "(\\A)?\n(\n+)?(\\z)?", "${1:?:${3:?\n:${2:?\n\n: }}}");
		src = format_string::replace(src, "(\\A\\s+)|[\\s&&[^\n\xA0]]+(\\z)?", "${1:?$1:${2:?\n: }}");
		return src;
	}

	std::string capitalize (std::string const& src)
	{
		return format_string::replace(src, "(?m).+", "${0:/capitalize}");
	}

	std::string transpose (std::string const& src)
	{
		std::vector< std::pair<char const*, char const*> > v = text::to_lines(src.data(), src.data() + src.size());
		bool hasNewline = !src.empty() && src.back() == '\n';

		std::string res("");
		if(v.size() == 1 || (v.size() == 2 && v.back().first == v.back().second))
		{
			static regexp::pattern_t pattern("\\A(?'open'[({\\[])?(?:(?'lhs'\\w+)(?'op'\\W+)(?'rhs'\\w+)|(?'lhs'[^,\\s]+?)(?'op'\\s*,\\s*)(?'rhs'[^,\\s]+?)|(?'lhs'[^:\\s]+?)(?'op'\\s*:\\s*)(?'rhs'[^:\\s]+?)|(?'lhs'[^<>!=\\s]+?)(?'op'\\s*[<>!=]\\s*)(?'rhs'[^<>!=\\s]+?))(?(<open>)(?'close'[\\]})]))\\z");
			if(regexp::match_t const& m = regexp::search(pattern, src))
				return format_string::expand("${open}${rhs}${op}${lhs}${close}", m.captures());

			std::deque<char> tmp;
			citerate(it, diacritics::make_range(src.data(), src.data() + src.size() - (hasNewline ? 1 : 0)))
				tmp.insert(tmp.begin(), &it, &it + it.length());

			std::copy(tmp.begin(), tmp.end(), back_inserter(res));
			if(hasNewline)
				res += '\n';
		}
		else
		{
			riterate(it, v)
				res += std::string(it->first, it->second);

			if(!hasNewline)
			{
				res.insert(v.back().second - v.back().first, "\n");
				res.resize(res.size()-1);
			}
		}
		return res;
	}

	// this is copy/paste from string_ranker.cc
	static std::string decompose_string (std::string const& src)
	{
		std::string res;
		if(CFMutableStringRef tmp = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, cf::wrap(src)))
		{
			CFStringNormalize(tmp, kCFStringNormalizationFormD);
			res = cf::to_s(tmp);
			CFRelease(tmp);
		}
		return res;
	}

	std::string decompose (std::string src)
	{
		src = decompose_string(src);
		return src.empty() ? src : std::string(src.begin(), &--utf8::make(src.end()));
	}

	std::string shift::operator() (std::string const& src) const
	{
		std::string res;
		for(auto const& it : text::to_lines(src.data(), src.data() + src.size()))
		{
			char const* from = it.first;
			char const* to   = it.second;

			if(amount > 0 && !text::is_blank(from, to))
			{
				res += indent::create(amount, indent.tab_size(), indent.soft_tabs());
			}
			else if(amount < 0)
			{
				for(int col = 0; from != to && col < -amount && text::is_space(*from); ++from)
					col += *from == '\t' ? indent.tab_size() - (col % indent.tab_size()) : 1;
			}

			std::copy(from, to, back_inserter(res));
		}
		return res;
	}

	static std::string fill_string (std::string const& src)
	{
		if(regexp::match_t const& m = regexp::search("\\A( *([*o•·-]) (?=\\S)|\\s{2,})", src))
			return format_string::replace(m[0], "\\S", " ");
		return "";
	}

	static size_t length_excl_whitespace (std::string const& buffer, size_t bol, size_t eol)
	{
		size_t len = eol - bol;
		while(len > 0 && strchr(" \n", buffer[bol + len - 1]))
			--len;
		return len;
	}

	std::string reformat::operator() (std::string const& src) const
	{
		std::string res;
		size_t from = 0;
		std::string const unwrapped = unwrap(src);
		std::string const fillStr = fill_string(unwrapped);
		for(auto const& offset : text::soft_breaks(unwrapped, wrap, tabSize, fillStr.size()))
		{
			res += unwrapped.substr(from, length_excl_whitespace(unwrapped, from, offset));
			res += "\n";
			if(offset != unwrapped.size())
				res += fillStr;
			from = offset;
		}
		res += unwrapped.substr(from, length_excl_whitespace(unwrapped, from, unwrapped.size()));
		return newline ? res + "\n" : res;
	}

	std::string justify::operator() (std::string const& src) const
	{
		std::string res;
		std::string const unwrapped = unwrap(src);
		for(auto const& it : text::to_lines(unwrapped.data(), unwrapped.data() + unwrapped.size()))
		{
			size_t from = 0;
			std::string const str = std::string(it.first, it.second);
			for(auto const& offset : text::soft_breaks(str, wrap, tabSize))
			{
				res += justify_line(str.substr(from, length_excl_whitespace(str, from, offset)), wrap, tabSize);
				res += "\n";
				from = offset;
			}
			res += str.substr(from, length_excl_whitespace(str, from, str.size()));
		}
		return newline ? res + "\n" : res;
	}

	std::string replace::operator() (std::string const& src) const
	{
		return text;
	}

	std::string surround::operator() (std::string const& src) const
	{
		return prefix + src + suffix;
	}

} /* transform */
