/*
	File                 : DateTimeSpinBox.cpp
	Project              : LabPlot
	Description          : widget for setting datetimes with a spinbox
	--------------------------------------------------------------------
	SPDX-FileCopyrightText: 2019 Martin Marmsoler <martin.marmsoler@gmail.com>
	SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "DateTimeSpinBox.h"

#include <QKeyEvent>
#include <QLineEdit>
#include <QRegularExpressionValidator>

DateTimeSpinBox::DateTimeSpinBox(QWidget* parent)
	: QAbstractSpinBox(parent) {
	lineEdit()->setText(QStringLiteral("0000.00.00 00:00:00.001"));
	DateTimeSpinBox::stepEnabled();

	m_regularExpressionValidator = new QRegularExpressionValidator();

	QRegularExpression regExp(QStringLiteral(
		R"(([0-9]+)\.(0[0-9]|1[0-2]|[0-9])\.(0[0-9]|[0-2][0-9]|30|[0-9]) ([0-1][0-9]|2[0-3]|[0-9])\:([0-5][0-9]|[0-9])\:([0-5][0-9]|[0-9])\.[0-9]{0,3})"));
	m_regularExpressionValidator->setRegularExpression(regExp);

	lineEdit()->setValidator(m_regularExpressionValidator);
}

void DateTimeSpinBox::keyPressEvent(QKeyEvent* event) {
	if (event->key() >= Qt::Key_0 && event->key() <= Qt::Key_9) {
		int cursorPos = lineEdit()->cursorPosition();
		int textLenght = lineEdit()->text().length();
		QAbstractSpinBox::keyPressEvent(event);
		getValue();
		if (lineEdit()->text().length() != textLenght)
			lineEdit()->setCursorPosition(cursorPos + 1);
		else
			lineEdit()->setCursorPosition(cursorPos);
	} else if (event->key() == Qt::Key_Up) {
		Type type = determineType(lineEdit()->cursorPosition());
		increaseValue(type, 1);
		writeValue();
		setCursorPosition(type);
	} else if (event->key() == Qt::Key_Down) {
		Type type = determineType(lineEdit()->cursorPosition());
		increaseValue(type, -1);
		writeValue();
		setCursorPosition(type);
	} else {
		QAbstractSpinBox::keyPressEvent(event);
		getValue();
	}
}

QAbstractSpinBox::StepEnabled DateTimeSpinBox::stepEnabled() const {
	return QAbstractSpinBox::StepEnabledFlag::StepUpEnabled | QAbstractSpinBox::StepEnabledFlag::StepDownEnabled; // for testing
}

void DateTimeSpinBox::stepBy(int steps) {
	Type type = determineType(lineEdit()->cursorPosition());
	increaseValue(type, steps);
	writeValue();
	setCursorPosition(type);
}

/*!
 * Write value to lineEdit of the spinbox
 */
void DateTimeSpinBox::writeValue() {
	lineEdit()->setText(
		QString::number(m_year) + QLatin1Char('.') + QStringLiteral("%1").arg(m_month, 2, 10, QLatin1Char('0')) + QLatin1Char('.')
		+ QStringLiteral("%1").arg(m_day, 2, 10, QLatin1Char('0')) + QLatin1Char(' ') + QStringLiteral("%1").arg(m_hour, 2, 10, QLatin1Char('0'))
		+ QLatin1Char(':') + QStringLiteral("%1").arg(m_minute, 2, 10, QLatin1Char('0')) + QLatin1Char(':')
		+ QStringLiteral("%1").arg(m_second, 2, 10, QLatin1Char('0')) + QLatin1Char('.') + QStringLiteral("%1").arg(m_millisecond, 3, 10, QLatin1Char('0')));
	Q_EMIT valueChanged();
}

void DateTimeSpinBox::setValue(qint64 increment) {
	qint64 divisor = qint64(12) * 30 * 24 * 60 * 60 * 1000;
	qint64 rest;
	m_year = increment / divisor;
	rest = increment - m_year * divisor;
	divisor = qint64(30) * 24 * 60 * 60 * 1000;
	m_month = rest / divisor;
	rest = rest - m_month * divisor;
	divisor = qint64(24) * 60 * 60 * 1000;
	m_day = rest / divisor;
	rest = rest - m_day * divisor;
	divisor = qint64(60) * 60 * 1000;
	m_hour = rest / divisor;
	rest -= m_hour * divisor;
	divisor = qint64(60) * 1000;
	m_minute = rest / divisor;
	rest -= m_minute * divisor;
	divisor = qint64(1000);
	m_second = rest / divisor;
	rest -= m_second * divisor;
	m_millisecond = rest;

	writeValue();
}

qint64 DateTimeSpinBox::value() {
	return m_millisecond + 1000 * (m_second + 60 * (m_minute + 60 * (m_hour + 24 * (m_day + 30 * (m_month + 12 * m_year)))));
}

/*!
 * Read value from lineEdit of the spinbox
 */
void DateTimeSpinBox::getValue() {
	QString text = lineEdit()->text();

	int counter = 0;
	int startIndex = 0;
	for (int i = 0; i < text.length(); i++) {
		if (text[i] == QLatin1Char('.') || text[i] == QLatin1Char(':') || text[i] == QLatin1Char(' ') || i == text.length() - 1) {
			switch (counter) {
			case Type::year:
				m_year = text.midRef(startIndex, i - startIndex).toInt();
				break;
			case Type::month:
				m_month = text.midRef(startIndex, i - startIndex).toInt();
				break;
			case Type::day:
				m_day = text.midRef(startIndex, i - startIndex).toInt();
				break;
			case Type::hour:
				m_hour = text.midRef(startIndex, i - startIndex).toInt();
				break;
			case Type::minute:
				m_minute = text.midRef(startIndex, i - startIndex).toInt();
				break;
			case Type::second:
				m_second = text.midRef(startIndex, i - startIndex).toInt();
				break;
			case Type::millisecond:
				m_millisecond = text.midRef(startIndex, i - startIndex + 1).toInt(); // because of the condition (i == text.length()-1)
				break;
			}
			startIndex = i + 1;
			counter++;
		}
	}

	Q_EMIT valueChanged();
}

void DateTimeSpinBox::setCursorPosition(Type type) {
	QString text = lineEdit()->text();
	int counter = 0;
	for (int i = 0; i < text.length(); i++) {
		if (text[i] == QLatin1Char('.') || text[i] == QLatin1Char(':') || text[i] == QLatin1Char(' '))
			counter++;

		if (counter - 1 == type) {
			lineEdit()->setCursorPosition(i);
			break;
		}
	}
}

bool DateTimeSpinBox::valid() {
	return true;
}

// step can also be negative
bool DateTimeSpinBox::increaseValue(DateTimeSpinBox::Type type, int step) {
	switch (type) {
	case Type::year: {
		if (m_year + step < 0 && step < 0) {
			m_year = 0;
			return false;
		}
		m_year += step;
		return true;
	} break;
	case Type::month:
		return changeValue(m_month, Type::year, step);
		break;
	case Type::day:
		return changeValue(m_day, Type::month, step);
		break;
	case Type::hour:
		return changeValue(m_hour, Type::day, step);
		break;
	case Type::minute:
		return changeValue(m_minute, Type::hour, step);
		break;
	case Type::second:
		return changeValue(m_second, Type::minute, step);
		break;
	case Type::millisecond:
		return changeValue(m_millisecond, Type::second, step);
		break;
	default:
		return false;
		break;
	}
}

bool DateTimeSpinBox::changeValue(qint64& thisType, DateTimeSpinBox::Type nextTypeType, int step) {
	int maxValue = 1;
	switch (nextTypeType) {
	case (Type::year):
		maxValue = 12;
		break;
	case (Type::month):
		maxValue = 30;
		break;
	case (Type::day):
		maxValue = 24;
		break;
	case (Type::hour):
		maxValue = 60;
		break;
	case (Type::minute):
		maxValue = 60;
		break;
	case (Type::second):
		maxValue = 1000;
		break;
	case (Type::millisecond):
		return false;
	}

	int nextTypeCounter = step / maxValue;
	step -= nextTypeCounter * maxValue;
	if (thisType + step < 0 && step < 0) {
		nextTypeCounter--;
		if (increaseValue(nextTypeType, nextTypeCounter)) {
			step += maxValue;
			thisType += step;
			return true;
		} else {
			thisType = 0;
			return false;
		}
	} else if (thisType + step > maxValue - 1 && step > 0) {
		step -= nextTypeCounter * maxValue;
		if (thisType + step > maxValue - 1) {
			nextTypeCounter++;
			step -= maxValue;
			thisType += step;
		} else
			thisType += step;

		return increaseValue(nextTypeType, nextTypeCounter);
	}
	thisType += step;
	return true;
}

DateTimeSpinBox::Type DateTimeSpinBox::determineType(int cursorPos) const {
	QString text = lineEdit()->text();

	if (cursorPos > text.length())
		cursorPos = text.length();

	int counter = 0;
	for (int i = 0; i < cursorPos; i++) {
		if (text[i] == QLatin1Char('.') || text[i] == QLatin1Char(':') || text[i] == QLatin1Char(' '))
			counter++;
	}

	if (counter <= Type::millisecond)
		return static_cast<Type>(counter);

	return Type::millisecond;
}
