/*
rkabstractoptionselector - This file is part of RKWard (https://rkward.kde.org). Created: Tue Mar 20 2007
SPDX-FileCopyrightText: 2007-2014 by Thomas Friedrichsmeier <thomas.friedrichsmeier@kdemail.net>
SPDX-FileContributor: The RKWard Team <rkward@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "rkabstractoptionselector.h"

#include <qdom.h>

#include "../debug.h"
#include "../misc/xmlhelper.h"

RKAbstractOptionSelector::RKAbstractOptionSelector(RKComponent *parent_component, QWidget *parent_widget) : RKComponent(parent_component, parent_widget) {
	RK_TRACE(PLUGIN);

	// create and register properties
	addChild(QStringLiteral("string"), string = new RKComponentPropertyBase(this, false));
	connect(string, &RKComponentPropertyBase::valueChanged, this, &RKAbstractOptionSelector::propertyChanged);
	addChild(QStringLiteral("number"), number = new RKComponentPropertyInt(this, true, -1));
	connect(number, &RKComponentPropertyBase::valueChanged, this, &RKAbstractOptionSelector::propertyChanged);
	number->setInternal(true);
}

RKAbstractOptionSelector::~RKAbstractOptionSelector() {
	RK_TRACE(PLUGIN);

	for (OptionsMap::const_iterator it = options.cbegin(); it != options.cend(); ++it) {
		delete (it.value());
	}
}

void RKAbstractOptionSelector::addOptionsAndInit(const QDomElement &element) {
	RK_TRACE(PLUGIN);

	// get xml-helper
	XMLHelper *xml = parentComponent()->xmlHelper();

	// create all the options
	XMLChildList option_elements = xml->getChildElements(element, QStringLiteral("option"), DL_ERROR);
	int selected = 0;
	int i = 0;
	for (XMLChildList::const_iterator it = option_elements.cbegin(); it != option_elements.cend(); ++it) {
		QString label = xml->i18nStringAttribute(*it, QStringLiteral("label"), QString(), DL_ERROR);
		QString value = xml->getStringAttribute(*it, QStringLiteral("value"), QString(), DL_WARNING);
		QString name = xml->getStringAttribute(*it, QStringLiteral("id"), QString(), DL_INFO);

		Option *opt = new Option;
		opt->value = value;
		opt->enabledness_prop = nullptr;

		options.insert(i, opt);
		if (!name.isNull()) named_options.insert(name, opt);

		addOptionToGUI(label, i);

		if (xml->getBoolAttribute(*it, QStringLiteral("checked"), false, DL_INFO)) {
			selected = i;
		}

		++i;
	}

	updating = false;
	number->setMin(0);
	number->setMax(i - 1);
	number->setIntValue(selected); // will also take care of activating the correct item
}

RKComponentBase *RKAbstractOptionSelector::lookupComponent(const QString &identifier, QString *remainder) {
	RK_TRACE(PLUGIN);

	if (identifier.isEmpty()) return this;

	QString name = identifier.section(u'.', 0, 0);
	if (named_options.contains(name)) {
		Option *opt = named_options[name];

		QString mod = identifier.section(u'.', 1);
		if (mod != QLatin1String("enabled")) {
			RK_DEBUG(PLUGIN, DL_DEBUG, "options do not have property '%s'", mod.toLatin1().data());
			return this;
		}

		if (!(opt->enabledness_prop)) { // requested for the first time
			opt->enabledness_prop = new RKComponentPropertyBool(this, false);
			connect(opt->enabledness_prop, &RKComponentPropertyBase::valueChanged, this, &RKAbstractOptionSelector::ItemPropertyChanged);
		}

		return (opt->enabledness_prop);
	}

	return RKComponent::lookupComponent(identifier, remainder);
}

void RKAbstractOptionSelector::propertyChanged(RKComponentPropertyBase *property) {
	RK_TRACE(PLUGIN);

	if (updating) return;

	int new_id = -1;
	if (property == string) {
		new_id = findOption(fetchStringValue(string));
	} else if (property == number) {
		new_id = number->intValue();
	} else {
		RK_ASSERT(false);
	}
	if (new_id < 0) {
		RK_DEBUG(PLUGIN, DL_ERROR, "option selector '%s' has no such option:", qPrintable(getIdInParent()));
		/*		RKComponent *p = this;
		        while (p) {
		            qDebug ("p: %s", qPrintable (p->getIdInParent ()));
		            p = p->parentComponent();
		        } */
		if (property == string) {
			RK_DEBUG(PLUGIN, DL_ERROR, "'%s'", qPrintable(fetchStringValue(string)));
		} else {
			RK_DEBUG(PLUGIN, DL_ERROR, "index %d", number->intValue());
		}
		return;
	}

	updating = true;
	setItemInGUI(new_id);
	itemSelected(new_id); // slot not called automatically on programmed changes!
	updating = false;

	changed();
}

void RKAbstractOptionSelector::ItemPropertyChanged(RKComponentPropertyBase *property) {
	RK_TRACE(PLUGIN);

	const Option *opt = nullptr;
	int id = -1;
	for (OptionsMap::const_iterator it = options.cbegin(); it != options.cend(); ++it) {
		RK_ASSERT(it.value());
		if (it.value()->enabledness_prop == property) {
			opt = it.value();
			id = it.key();
			break;
		}
	}

	if ((!opt) || (id < 0)) {
		RK_ASSERT(false);
		return;
	}

	RK_ASSERT(property->type() == RKComponent::PropertyBool);
	bool enabled = static_cast<RKComponentPropertyBool *>(property)->boolValue();

	if (!enabled) {
		if (id == number->intValue()) { // current item was disabled
			int settable_opt = -1;
			for (OptionsMap::const_iterator it = options.cbegin(); it != options.cend(); ++it) {
				RK_ASSERT(it.value());

				if ((!(it.value()->enabledness_prop)) || (it.value()->enabledness_prop->boolValue())) {
					settable_opt = it.key();
					break;
				}
			}
			if (settable_opt >= 0) itemSelected(settable_opt);
			else RK_DEBUG(PLUGIN, DL_ERROR, "No option left enabled. Disable the entire component '%s', instead!", qPrintable(getIdInParent()));
			// TODO: Error message below can also trigger, if items first get disabled, then others enabled. Delay it, instead.
		}
	}

	setItemEnabledInGUI(id, enabled);
}

void RKAbstractOptionSelector::itemSelected(int id) {
	RK_TRACE(PLUGIN);

	Option *opt = options[id];
	RK_ASSERT(opt);

	string->setValue(opt->value);
	number->setIntValue(id);
}

int RKAbstractOptionSelector::findOption(const QString &option_string) {
	RK_TRACE(PLUGIN);

	for (OptionsMap::const_iterator it = options.cbegin(); it != options.cend(); ++it) {
		RK_ASSERT(it.value());
		if (it.value()->value == option_string) return (it.key());
	}
	return -1;
}
