/*
 * Copyright (C) 2003, 2006-2008 by the Widelands Development Team
 *
 * This program 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.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "ui_editbox.h"

#include "ui_constants.h"

#include "graphic.h"
#include "helper.h"

#include <limits>
#include <SDL_keysym.h>

namespace UI {

struct EditBoxImpl {
	/// Background tile style.
	uint32_t background;

	/// ID. Only used for the id-flavoured signals.
	int32_t id;

	/// Maximum number of characters in the input
	uint32_t maxLength;

	/// Current text in the box.
	std::string text;

	/// Position of the caret.
	uint32_t caret;

	/// Alignment of the text. Vertical alignment is always centered.
	Align align;
};

EditBox::EditBox
	(Panel * const parent,
	 const int32_t x, const int32_t y, const uint32_t w, const uint32_t h,
	 const uint32_t background,
	 const int32_t id,
	 Align _align)
	:
	Panel(parent, x, y, w, h),
	m(new EditBoxImpl)
{
	set_think(false);

	char buf[256];
	snprintf(buf, sizeof(buf), "pics/but%u.png", background);
	m->background = g_gr->get_picture(PicMod_UI,  buf);

	m->id = id;
	m->align = static_cast<Align>((_align & Align_Horizontal) | Align_VCenter);
	// yes, use *signed* max as maximum length; just a small safe-guard.
	m->caret = 0;
	m->maxLength = std::numeric_limits<int32_t>::max();

	set_handle_mouse(true);
	set_can_focus(true);
}

EditBox::~EditBox()
{
	// place a destructor where the compiler can find the EditBoxImpl destructor
}

/**
 * \return the current text entered in the edit box
 */
const std::string& EditBox::text() const
{
	return m->text;
}


/**
 * Set the current text in the edit box.
 *
 * The text is truncated if it is longer than the maximum length set by
 * \ref setMaxLength().
 */
void EditBox::setText(const std::string& t)
{
	if (t == m->text)
		return;

	bool caretatend = m->caret == m->text.size();

	m->text = t;
	if (m->text.size() > m->maxLength)
		m->text.erase(m->text.begin() + m->maxLength, m->text.end());
	if (caretatend || m->caret > m->text.size())
		m->caret = m->text.size();

	update();
}


/**
 * \return the maximum length of the input string
 */
uint32_t EditBox::maxLength() const
{
	return m->maxLength;
}


/**
 * Set the maximum length of the input string.
 *
 * If the current string is longer than the new maximum length,
 * its end is cut off to fit into the maximum length.
 */
void EditBox::setMaxLength(uint32_t n)
{
	m->maxLength = n;

	if (m->text.size() > m->maxLength) {
		m->text.erase(m->text.begin() + m->maxLength, m->text.end());
		if (m->caret > m->text.size())
			m->caret = m->text.size();

		update();
	}
}


/**
 * \return the text alignment
 */
Align EditBox::align() const
{
	return m->align;
}


/**
 * Set the new alignment.
 *
 * Note that vertical alignment is always centered, independent of what
 * you select here.
 */
void EditBox::setAlign(Align _align)
{
	_align = static_cast<Align>((_align & Align_Horizontal) | Align_VCenter);
	if (_align != m->align) {
		m->align = _align;
		update();
	}
}


/**
 * The mouse was clicked on this editbox
*/
bool EditBox::handle_mousepress(const Uint8 btn, int32_t, int32_t)
{
	if (btn == SDL_BUTTON_LEFT && get_can_focus()) {
		focus();
		update();
		return true;
	}

	return false;
}
bool EditBox::handle_mouserelease(const Uint8 btn, int32_t, int32_t)
{
	return btn == SDL_BUTTON_LEFT && get_can_focus();
}

/**
 * Handle keypress/release events
 *
 * \todo Text input works only because code.unicode happens to map to ASCII for
 * ASCII characters (--> //HERE). Instead, all user editable strings should be
 * real unicode.
*/
bool EditBox::handle_key(bool down, SDL_keysym code)
{
	if (down) {
		switch (code.sym) {
		case SDLK_ESCAPE:
			cancel.call();
			cancelid.call(m->id);
			return true;

		case SDLK_RETURN:
			ok.call();
			okid.call(m->id);
			return true;

		case SDLK_BACKSPACE:
			if (m->caret > 0) {
				--m->caret;
				m->text.erase(m->text.begin() + m->caret);
				changed.call();
				changedid.call(m->id);
				update();
			}
			return true;

		case SDLK_DELETE:
			if (m->caret < m->text.size()) {
				m->text.erase(m->text.begin() + m->caret);
				changed.call();
				changedid.call(m->id);
				update();
			}
			return true;

		case SDLK_LEFT:
			if (m->caret > 0) {
				--m->caret;
				if (code.mod & (KMOD_LCTRL | KMOD_RCTRL))
					for (uint32_t new_caret = m->caret;; m->caret = new_caret)
						if (0 == new_caret or isspace(m->text[--new_caret]))
							break;
				update();
			}
			return true;

		case SDLK_RIGHT:
			if (m->caret < m->text.size()) {
				++m->caret;
				if (code.mod & (KMOD_LCTRL | KMOD_RCTRL))
					for (uint32_t new_caret = m->caret;; ++new_caret)
						if
							(new_caret == m->text.size()
							 or
							 isspace(m->text[new_caret - 1]))
						{
							m->caret = new_caret;
							break;
						}
				update();
			}
			return true;

		case SDLK_HOME:
			if (m->caret != 0) {
				m->caret = 0;
				update();
			}
			return true;

		case SDLK_END:
			if (m->caret != m->text.size()) {
				m->caret = m->text.size();
				update();
			}
			return true;

		default:
			if (is_printable(code)) {
				if (m->text.size() < m->maxLength) {
					m->text.insert(m->text.begin() + m->caret, code.unicode);
					++m->caret;
					changed.call();
					changedid.call(m->id);
					update();
				}
				return true;
			}
		}
	}

	return Panel::handle_key(down, code);
}


void EditBox::draw(RenderTarget* dst)
{
	// Draw the background
	dst->tile
		(Rect(Point(0, 0), get_w(), get_h()),
		 m->background,
		 Point(get_x(), get_y()));

	// Draw border.
	if (get_w() >= 4 && get_h() >= 4) {
		static const RGBColor black(0, 0, 0);

		// bottom edge
		dst->brighten_rect
			(Rect(Point(0, get_h() - 2), get_w(), 2),
			 BUTTON_EDGE_BRIGHT_FACTOR);
		// right edge
		dst->brighten_rect
			(Rect(Point(get_w() - 2, 0), 2, get_h() - 2),
			 BUTTON_EDGE_BRIGHT_FACTOR);
		// top edge
		dst->fill_rect(Rect(Point(0, 0), get_w() - 1, 1), black);
		dst->fill_rect(Rect(Point(0, 1), get_w() - 2, 1), black);
		// left edge
		dst->fill_rect(Rect(Point(0, 0), 1, get_h() - 1), black);
		dst->fill_rect(Rect(Point(1, 0), 1, get_h() - 2), black);
	}

	if (has_focus())
		dst->brighten_rect
			(Rect(Point(0, 0), get_w(), get_h()), MOUSE_OVER_BRIGHT_FACTOR);

	Point pos(4, get_h() >> 1);

	switch (m->align & Align_Horizontal) {
	case Align_HCenter:
		pos.x = get_w() >> 1;
		break;
	case Align_Right:
		pos.x = get_w() - 4;
		break;
	default:
		break;
	}

	g_fh->draw_string
		(*dst,
		 UI_FONT_SMALL, UI_FONT_SMALL_CLR,
		 pos,
		 m->text,
		 align(),
		 -1,
		 Widget_Cache_None,
		 0,
		 has_focus() ? static_cast<int32_t>(m->caret) : -1);
}


};
