// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Aegisub Project http://www.aegisub.org/

#include "validators.h"

#include "compat.h"

#include <libaegisub/exception.h>
#include <libaegisub/util.h>

#include <wx/combobox.h>
#include <wx/spinctrl.h>
#include <wx/textctrl.h>

namespace {
std::string new_value(wxTextCtrl *ctrl, int chr) {
	long from, to;
	ctrl->GetSelection(&from, &to);
	auto value = ctrl->GetValue();
	return from_wx(value.substr(0, from) + (wxChar)chr + value.substr(to));
}

wxChar decimal_separator() {
	auto sep = wxLocale::GetInfo(wxLOCALE_DECIMAL_POINT, wxLOCALE_CAT_NUMBER);
	return sep.empty() ? '.' : sep[0];
}
}

IntValidator::IntValidator(int val, bool allow_negative)
: value(val)
, allow_negative(allow_negative)
{
	Bind(wxEVT_CHAR, &IntValidator::OnChar, this);
}

IntValidator::IntValidator(IntValidator const& rgt)
: value(rgt.value)
, allow_negative(rgt.allow_negative)
{
	SetWindow(rgt.GetWindow());
	Bind(wxEVT_CHAR, &IntValidator::OnChar, this);
}

bool IntValidator::TransferToWindow() {
	static_cast<wxTextCtrl *>(GetWindow())->SetValue(std::to_wstring(value));
	return true;
}

void IntValidator::OnChar(wxKeyEvent& event) {
	int chr = event.GetKeyCode();
	if (chr < WXK_SPACE || chr == WXK_DELETE || chr > WXK_START) {
		event.Skip();
		return;
	}

	auto ctrl = static_cast<wxTextCtrl *>(GetWindow());
	auto str = new_value(ctrl, chr);
	int parsed;
	if (allow_negative && str == "-")
		event.Skip();
	else if (agi::util::try_parse(str, &parsed) && (allow_negative || parsed >= 0))
		event.Skip();
	else if (!wxValidator::IsSilent())
		wxBell();
}

DoubleValidator::DoubleValidator(double *val, bool allow_negative)
: value(val)
, min(allow_negative ? std::numeric_limits<double>::lowest() : 0)
, max(std::numeric_limits<double>::max())
, decimal_sep(decimal_separator())
{
	Bind(wxEVT_CHAR, &DoubleValidator::OnChar, this);
}

DoubleValidator::DoubleValidator(double *val, double min, double max)
: value(val)
, min(min)
, max(max)
, decimal_sep(decimal_separator())
{
	Bind(wxEVT_CHAR, &DoubleValidator::OnChar, this);
}

DoubleValidator::DoubleValidator(DoubleValidator const& rgt)
: value(rgt.value)
, min(rgt.min)
, max(rgt.max)
, decimal_sep(rgt.decimal_sep)
{
	Bind(wxEVT_CHAR, &DoubleValidator::OnChar, this);
	SetWindow(rgt.GetWindow());
}

void DoubleValidator::OnChar(wxKeyEvent& event) {
	int chr = event.GetKeyCode();
	if (chr < WXK_SPACE || chr == WXK_DELETE || chr > WXK_START) {
		event.Skip();
		return;
	}

	if (chr == decimal_sep)
		chr = '.';

	auto str = new_value(static_cast<wxTextCtrl *>(GetWindow()), chr);
	if (decimal_sep != '.')
		replace(begin(str), end(str), (char)decimal_sep, '.');

	double parsed;
	bool can_parse = agi::util::try_parse(str, &parsed);
	if ((min < 0 && str == "-") || str == ".")
		event.Skip();
	else if (can_parse && parsed >= min && parsed <= max)
		event.Skip();
	else if (can_parse && min < 0 && chr == '-') // allow negating an existing value even if it results in being out of range
		event.Skip();
	else if (!wxValidator::IsSilent())
		wxBell();
}

bool DoubleValidator::TransferToWindow() {
	auto str = std::to_wstring(*value);
	if (decimal_sep != '.')
		std::replace(str.begin(), str.end(), L'.', decimal_sep);
	if (str.find(decimal_sep) != str.npos) {
		while (str.back() == '0')
			str.pop_back();
	}
	static_cast<wxTextCtrl *>(GetWindow())->SetValue(str);
	return true;
}

bool DoubleValidator::TransferFromWindow() {
	auto ctrl = static_cast<wxTextCtrl *>(GetWindow());
	if (!Validate(ctrl)) return false;
	auto str = from_wx(ctrl->GetValue());
	if (decimal_sep != '.')
		replace(begin(str), end(str), (char)decimal_sep, '.');
	agi::util::try_parse(str, value);
	return true;
}

bool DoubleSpinValidator::TransferToWindow() {
	static_cast<wxSpinCtrlDouble*>(GetWindow())->SetValue(*value);
	return true;
}

bool DoubleSpinValidator::TransferFromWindow() {
	auto ctrl = static_cast<wxSpinCtrlDouble*>(GetWindow());
#ifndef wxHAS_NATIVE_SPINCTRLDOUBLE
	wxFocusEvent evt;
	ctrl->OnTextLostFocus(evt);
#endif
	*value = ctrl->GetValue();
	return true;
}

int EnumBinderBase::Get() {
	if (auto rb = dynamic_cast<wxRadioBox*>(GetWindow()))
		return rb->GetSelection();
	if (auto rb = dynamic_cast<wxComboBox*>(GetWindow()))
		return rb->GetSelection();
	throw agi::InternalError("Control type not supported by EnumBinder");
}

void EnumBinderBase::Set(int value) {
	if (auto rb = dynamic_cast<wxRadioBox*>(GetWindow()))
		rb->SetSelection(value);
	else if (auto rb = dynamic_cast<wxComboBox*>(GetWindow()))
		rb->SetSelection(value);
	else
		throw agi::InternalError("Control type not supported by EnumBinder");
}

bool StringBinder::TransferFromWindow() {
	wxWindow *window = GetWindow();
	if (wxTextCtrl *ctrl = dynamic_cast<wxTextCtrl*>(window))
		*value = from_wx(ctrl->GetValue());
	else if (wxComboBox *ctrl = dynamic_cast<wxComboBox*>(window))
		*value = from_wx(ctrl->GetValue());
	else
		throw agi::InternalError("Unsupported control type");
	return true;
}

bool StringBinder::TransferToWindow() {
	wxWindow *window = GetWindow();
	if (wxTextCtrl *ctrl = dynamic_cast<wxTextCtrl*>(window))
		ctrl->SetValue(to_wx(*value));
	else if (wxComboBox *ctrl = dynamic_cast<wxComboBox*>(window))
		ctrl->SetValue(to_wx(*value));
	else
		throw agi::InternalError("Unsupported control type");
	return true;
}
