/**
 * The wxWrappedStaticText control 
 * implementation.
 * For more precise documentation, look
 * at the header file.
 *
 * Project code name "music".
 * 2004, GPL licensed.
 */

#include "wrapped_text.hpp"

namespace
{
	// The constructor.
	TextWrapper::TextWrapper(const wxString &text, const wxSize &size, const wxString &end)
	 : control_(0)
	 , text_(text)
	 , end_(end)
	 , height_(size.GetHeight())
	 , width_(size.GetWidth())
	{
		// empty.
	}

	// Sets the parent wxStaticText.
	void TextWrapper::setParent(wxStaticText *parent)
	{
		control_ = parent;

		tokenize();
		splitTokens();
	}

	// Splits text_ into tokens.
	void TextWrapper::tokenize()
	{
		if(!control_)
			return;

		static std::string delimiters = WXWST_DELIMITERS;
		std::string::size_type idx = 0;

		unsplit_tokens_.clear();

		while(idx != text_.size())
		{
			// The first character is a delimiter ?
			if( delimiters.find(text_[idx]) != std::string::npos )
			{
				unsplit_tokens_.push_back( wxString(text_[idx]) );

				++idx;
				continue;
			}

			// No, it`s normal text.
			wxString word;

			// While we don`t find a delimiter, append chars to the word.
			while( delimiters.find(text_[idx]) == std::string::npos )
			{
				word += text_[idx++];

				// End of the whole text ?
				if(idx == text_.size())
					break;
			}

			unsplit_tokens_.push_back(word);
		}
	}

	// Called when the control size changes.
	void TextWrapper::setSize(const wxSize &size)
	{
		if(!control_)
			return;

		width_ = size.GetWidth();
		height_ = size.GetHeight();

		// Since the size changed, some
		// words may need to be split in order to fit.
		splitTokens();
	}

	// Called when the label changes.
	void TextWrapper::setLabel(const wxString &label)
	{
		if(!control_)
			return;

		text_ = label;

		// Now we need full re-tokenization.
		tokenize();
		splitTokens();
	}

	// Returns the @word size in pixels.
	wxSize TextWrapper::getTextSize(const wxString &word)
	{
		if(!control_)
			return wxSize(0, 0);

		int x, y;
		control_->GetTextExtent(word, &x, &y);

		return wxSize(x, y);
	}

	// Performs the text wrapping.
	void TextWrapper::format(wxString &result)
	{
		if(!control_)
			return;

		result.clear();

		// What`s the size of the end delimiter ?
		static int end_delimiter_width = getTextSize(end_).GetWidth();

		// How many lines will we be able to show ?
		int line_count = static_cast<int>( height_ / getTextSize(text_.substr(0,1)).GetHeight() );
		
		int curr_line = 1;
		int curr_width = 0;
		int max_width = width_;

		for(TokenIterator token = tokens_.begin(); token != tokens_.end(); ++token)
		{
			max_width = width_;

			// We don`t want spaces at the beginning of a new line.
			if((*token == " ") && (curr_width == 0))
			{
				continue;
			}


			// A new line char needs special care.
			if(*token == "\n")
			{
				// A special case when we are in the last line.
				if(curr_line == line_count)
				{
					// If this is the last token 
					// then we end the output.
					TokenIterator next_token = token + 1;
					if(next_token == tokens_.end())
					{
						return;
					}

					// Else we will need space for the end delimiter.
					max_width -= end_delimiter_width;

					// If there is no space left
					// for the end delimiter, then 
					// we remove tokens while there is enough space.
					if(curr_width > max_width)
					{
						TokenIterator prev_token = token - 1;
						while( curr_width > max_width )
						{
							int last_token_pos = result.size() - prev_token->size();
							result.erase(last_token_pos);
							
							curr_width -= getTextSize(*prev_token).GetWidth();
							--prev_token;
						}
					}

					// Now let`s append the end delimiter and exit.
					result += end_;
					return;
				}
				// And if this is not the last line, then
				// just increase the line count and reset the width.
				else
				{
					++curr_line;
					curr_width = 0;

					result += "\n";
					continue;
				}
			}

			// All non end line tokens.

			// If this is the last line, we will need
			// space for the end delimiter.
			if(curr_line == line_count)
				max_width -= end_delimiter_width;

			int token_width = getTextSize(*token).GetWidth();

			// Is there room in the current line to append this token ?
			if( curr_width + token_width <= max_width )
			{
				result += *token;
				curr_width += token_width;
			}
			// No, we will put it in a new line, if we can.
			else
			{
				// This is the last line, append the end delimiter
				// and exit.
				if(curr_line == line_count)
				{
					result += end_;
					return;
				}

				// Just add a new line.
				++curr_line;
				curr_width = token_width;

				result += "\n";

				// We don`t want spaces at the begining of a new line.
				if(*token != " ")
					result += *token;
			}
		}
	}

	// Splits the @word to @parts if needed.
	void TextWrapper::splitIfNeeded(const wxString &word, TokenContainer &parts)
	{
		if(!control_)
			return;

		int width = getTextSize(word).GetWidth();

		// Everything`s ok.
		if(width <= width_)
		{
			parts.push_back(word);
			return;
		}

		// The word is too long, cut off chars untill it`s ok.
		for(int i = word.size() - 2; i > 0; --i)
		{
			wxString temp = word.substr(0, i);

			// Ok now ?
			if(getTextSize(temp).GetWidth() <= width_)
			{
				parts.push_back(temp);

				// Recursively call this function on the reminder of the
				// string, since it may still be too long. 
				// Append the resulting list to the current one.
				std::vector<wxString> r_tokens;
				wxString reminder = word.substr(i);

				splitIfNeeded(reminder, r_tokens);

				// Append.
				parts.insert(parts.end(), r_tokens.begin(), r_tokens.end());

				// Our work is done.
				return;
			}
		}
	}

	// Splits words in @unsplit_tokens_ if needed.
	void TextWrapper::splitTokens()
	{
		if(!control_)
			return;

		tokens_.clear();

		// Check every token.
		for(TokenIterator token = unsplit_tokens_.begin(); 
		    token != unsplit_tokens_.end(); 
		    ++token
		   )
		{
			TokenContainer parts;
			splitIfNeeded(*token, parts);

			tokens_.insert(tokens_.end(), parts.begin(), parts.end());
		}
	}

} // unnamed namespace.

//   The wxWrappedStaticText implementation ..   ------------------------ //

IMPLEMENT_CLASS(wxWrappedStaticText, wxStaticText)

BEGIN_EVENT_TABLE(wxWrappedStaticText, wxStaticText)
	EVT_SIZE(wxWrappedStaticText::OnSize)
END_EVENT_TABLE()

// The constructor.
wxWrappedStaticText::wxWrappedStaticText(wxWindow *parent, 
                                         int id, 
                                         const wxString &text, 
                                         const wxPoint &pos, 
                                         const wxSize &size,
                                         const wxString &end
                                        )
 : wxStaticText(parent, id, text, pos, size, wxST_NO_AUTORESIZE)
 , wrapper_(text, GetSize(), end)
{
	wrapper_.setParent(this);
}

// Sets the new text.
void wxWrappedStaticText::SetLabel(const wxString &label)
{
	// Inform the wrapper_ ..
	wrapper_.setLabel(label);

	// .. and wrap the text.
	wxString txt;
	wrapper_.format(txt);

	wxStaticText::SetLabel(txt);
}

// Sets the new font.
bool wxWrappedStaticText::SetFont(const wxFont& font)
{
	bool ret = wxStaticText::SetFont(font);

	// We emulate a full re-tokenization.
	wrapper_.setSize( GetSize() );

	wxString txt;
	wrapper_.format(txt);

	return ret;
}

// The size event handler.
void wxWrappedStaticText::OnSize(wxSizeEvent &event)
{
	// Inform the wrapper_ about the size change ..
	wrapper_.setSize( GetSize() );
	
	// .. and wrap the text.
	wxString txt;
	wrapper_.format(txt);

	wxStaticText::SetLabel(txt);

	event.Skip();
}

