// SPDX-License-Identifier: GPL-2.0

#include "profilewidget.h"
#include "profile-widget/profilewidget2.h"
#include "commands/command.h"
#include "core/color.h"
#include "core/event.h"
#include "core/sample.h"
#include "core/selection.h"
#include "core/settings/qPrefTechnicalDetails.h"
#include "core/settings/qPrefPartialPressureGas.h"
#include "core/subsurface-string.h"
#include "qt-models/diveplannermodel.h"

#include <QToolBar>
#include <QHBoxLayout>
#include <QStackedWidget>
#include <QLabel>

// A resizing display of the Subsurface logo when no dive is shown
class EmptyView : public QLabel {
public:
	EmptyView(QWidget *parent = nullptr);
	~EmptyView();
private:
	QPixmap logo;
	void update();
	void resizeEvent(QResizeEvent *) override;
};

EmptyView::EmptyView(QWidget *parent) : QLabel(parent),
	logo(":poster-icon")
{
	QPalette pal;
	pal.setColor(QPalette::Window, getColor(::BACKGROUND));
	setAutoFillBackground(true);
	setPalette(pal);
	setMinimumSize(1,1);
	setAlignment(Qt::AlignHCenter);
	update();
}

EmptyView::~EmptyView()
{
}

void EmptyView::update()
{
	setPixmap(logo.scaled(size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
}

void EmptyView::resizeEvent(QResizeEvent *)
{
	update();
}

ProfileWidget::ProfileWidget() : d(nullptr), dc(0), placingCommand(false)
{
	ui.setupUi(this);

	// what is a sane order for those icons? we should have the ones the user is
	// most likely to want towards the top so they are always visible
	// and the ones that someone likely sets and then never touches again towards the bottom
	toolbarActions = { ui.profInfobox, // show / hide the infobox
			   ui.profCalcCeiling, ui.profCalcAllTissues, // various ceilings
			   ui.profIncrement3m, ui.profDcCeiling,
			   ui.profPhe, ui.profPn2, ui.profPO2, // partial pressure graphs
			   ui.profRuler, ui.profScaled, // measuring and scaling
			   ui.profTogglePicture, ui.profTankbar,
			   ui.profMod, ui.profDeco, ui.profNdl_tts, // various values that a user is either interested in or not
			   ui.profEad, ui.profSAC,
			   ui.profHR, // very few dive computers support this
			   ui.profTissues }; // maybe less frequently used

	emptyView.reset(new EmptyView);

	view.reset(new ProfileWidget2(DivePlannerPointsModel::instance(), 1.0, this));
	QToolBar *toolBar = new QToolBar(this);
	for (QAction *a: toolbarActions)
		toolBar->addAction(a);
	toolBar->setOrientation(Qt::Vertical);
	toolBar->setIconSize(QSize(24, 24));

	stack = new QStackedWidget(this);
	stack->addWidget(emptyView.get());
	stack->addWidget(view.get());

	QHBoxLayout *layout = new QHBoxLayout(this);
	layout->setSpacing(0);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
	layout->setMargin(0);
#endif
	layout->setContentsMargins(0, 0, 0, 0);
	layout->addWidget(toolBar);
	layout->addWidget(stack);
	setLayout(layout);

	// Toolbar Connections related to the Profile Update
	auto tec = qPrefTechnicalDetails::instance();
	connect(ui.profCalcAllTissues, &QAction::triggered, tec, &qPrefTechnicalDetails::set_calcalltissues);
	connect(ui.profCalcCeiling,    &QAction::triggered, tec, &qPrefTechnicalDetails::set_calcceiling);
	connect(ui.profDcCeiling,      &QAction::triggered, tec, &qPrefTechnicalDetails::set_dcceiling);
	connect(ui.profEad,            &QAction::triggered, tec, &qPrefTechnicalDetails::set_ead);
	connect(ui.profIncrement3m,    &QAction::triggered, tec, &qPrefTechnicalDetails::set_calcceiling3m);
	connect(ui.profMod,            &QAction::triggered, tec, &qPrefTechnicalDetails::set_mod);
	connect(ui.profNdl_tts,        &QAction::triggered, tec, &qPrefTechnicalDetails::set_calcndltts);
	connect(ui.profDeco,           &QAction::triggered, tec, &qPrefTechnicalDetails::set_decoinfo);
	connect(ui.profHR,             &QAction::triggered, tec, &qPrefTechnicalDetails::set_hrgraph);
	connect(ui.profRuler,          &QAction::triggered, tec, &qPrefTechnicalDetails::set_rulergraph);
	connect(ui.profSAC,            &QAction::triggered, tec, &qPrefTechnicalDetails::set_show_sac);
	connect(ui.profScaled,         &QAction::triggered, tec, &qPrefTechnicalDetails::set_zoomed_plot);
	connect(ui.profTogglePicture,  &QAction::triggered, tec, &qPrefTechnicalDetails::set_show_pictures_in_profile);
	connect(ui.profTankbar,        &QAction::triggered, tec, &qPrefTechnicalDetails::set_tankbar);
	connect(ui.profTissues,        &QAction::triggered, tec, &qPrefTechnicalDetails::set_percentagegraph);
	connect(ui.profInfobox,        &QAction::triggered, tec, &qPrefTechnicalDetails::set_infobox);

	connect(ui.profTissues,        &QAction::triggered, this, &ProfileWidget::unsetProfHR);
	connect(ui.profHR,             &QAction::triggered, this, &ProfileWidget::unsetProfTissues);

	auto pp_gas = qPrefPartialPressureGas::instance();
	connect(ui.profPhe, &QAction::triggered, pp_gas, &qPrefPartialPressureGas::set_phe);
	connect(ui.profPn2, &QAction::triggered, pp_gas, &qPrefPartialPressureGas::set_pn2);
	connect(ui.profPO2, &QAction::triggered, pp_gas, &qPrefPartialPressureGas::set_po2);

	connect(&diveListNotifier, &DiveListNotifier::divesChanged, this, &ProfileWidget::divesChanged);
	connect(&diveListNotifier, &DiveListNotifier::settingsChanged, view.get(), &ProfileWidget2::settingsChanged);
	connect(&diveListNotifier, &DiveListNotifier::cylinderAdded, this, &ProfileWidget::cylindersChanged);
	connect(&diveListNotifier, &DiveListNotifier::cylinderRemoved, this, &ProfileWidget::cylindersChanged);
	connect(&diveListNotifier, &DiveListNotifier::cylinderEdited, this, &ProfileWidget::cylindersChanged);
	connect(view.get(), &ProfileWidget2::stopAdded, this, &ProfileWidget::stopAdded);
	connect(view.get(), &ProfileWidget2::stopRemoved, this, &ProfileWidget::stopRemoved);
	connect(view.get(), &ProfileWidget2::stopMoved, this, &ProfileWidget::stopMoved);
	connect(view.get(), &ProfileWidget2::stopEdited, this, &ProfileWidget::stopEdited);

	ui.profCalcAllTissues->setChecked(qPrefTechnicalDetails::calcalltissues());
	ui.profCalcCeiling->setChecked(qPrefTechnicalDetails::calcceiling());
	ui.profDcCeiling->setChecked(qPrefTechnicalDetails::dcceiling());
	ui.profEad->setChecked(qPrefTechnicalDetails::ead());
	ui.profIncrement3m->setChecked(qPrefTechnicalDetails::calcceiling3m());
	ui.profMod->setChecked(qPrefTechnicalDetails::mod());
	ui.profNdl_tts->setChecked(qPrefTechnicalDetails::calcndltts());
	ui.profDeco->setChecked(qPrefTechnicalDetails::decoinfo());
	ui.profPhe->setChecked(pp_gas->phe());
	ui.profPn2->setChecked(pp_gas->pn2());
	ui.profPO2->setChecked(pp_gas->po2());
	ui.profHR->setChecked(qPrefTechnicalDetails::hrgraph());
	ui.profRuler->setChecked(qPrefTechnicalDetails::rulergraph());
	ui.profSAC->setChecked(qPrefTechnicalDetails::show_sac());
	ui.profTogglePicture->setChecked(qPrefTechnicalDetails::show_pictures_in_profile());
	ui.profTankbar->setChecked(qPrefTechnicalDetails::tankbar());
	ui.profTissues->setChecked(qPrefTechnicalDetails::percentagegraph());
	ui.profScaled->setChecked(qPrefTechnicalDetails::zoomed_plot());
	ui.profInfobox->setChecked(qPrefTechnicalDetails::infobox());
}

ProfileWidget::~ProfileWidget()
{
}

void ProfileWidget::setEnabledToolbar(bool enabled)
{
	for (QAction *b: toolbarActions)
		b->setEnabled(enabled);
}

void ProfileWidget::setDive(const struct dive *d, int dcNr)
{
	stack->setCurrentIndex(1); // show profile

	bool freeDiveMode = d->get_dc(dcNr)->divemode == FREEDIVE;
	ui.profCalcCeiling->setDisabled(freeDiveMode);
	ui.profCalcCeiling->setDisabled(freeDiveMode);
	ui.profCalcAllTissues ->setDisabled(freeDiveMode);
	ui.profIncrement3m->setDisabled(freeDiveMode);
	ui.profDcCeiling->setDisabled(freeDiveMode);
	ui.profPhe->setDisabled(freeDiveMode);
	ui.profPn2->setDisabled(freeDiveMode); //TODO is the same as scuba?
	ui.profPO2->setDisabled(freeDiveMode); //TODO is the same as scuba?
	ui.profTankbar->setDisabled(freeDiveMode);
	ui.profMod->setDisabled(freeDiveMode);
	ui.profNdl_tts->setDisabled(freeDiveMode);
	ui.profDeco->setDisabled(freeDiveMode);
	ui.profEad->setDisabled(freeDiveMode);
	ui.profSAC->setDisabled(freeDiveMode);
	ui.profTissues->setDisabled(freeDiveMode);

	ui.profRuler->setDisabled(false);
	ui.profScaled->setDisabled(false); // measuring and scaling
	ui.profTogglePicture->setDisabled(false);
	ui.profHR->setDisabled(false);
	ui.profInfobox->setDisabled(false);
}

void ProfileWidget::plotCurrentDive()
{
	plotDive(d, dc);
}

void ProfileWidget::plotDive(dive *dIn, int dcIn)
{
	bool endEditMode = false;
	if (editedDive && (dIn != d || dcIn != dc))
		endEditMode = true;

	d = dIn;

	if (dcIn >= 0)
		dc = dcIn;

	// The following is valid because number_of_computers is always at least 1.
	if (d)
		dc = std::min(dc, d->number_of_computers() - 1);

	// Exit edit mode if the dive changed
	if (endEditMode)
		exitEditMode();

	// If this is a manually added dive and we are not in the planner
	// or already editing the dive, switch to edit mode.
	if (d && !editedDive &&
	    DivePlannerPointsModel::instance()->currentMode() == DivePlannerPointsModel::NOTHING) {
		struct divecomputer *comp = d->get_dc(dc);
		if (comp && is_dc_manually_added_dive(comp) && !comp->samples.empty() && comp->samples.size() <= 50)
			editDive();
	}

	setEnabledToolbar(d != nullptr);
	if (editedDive) {
		view->plotDive(editedDive.get(), dc);
		setDive(editedDive.get(), dc);
	} else if (d) {
		view->setProfileState(d, dc);
		view->resetZoom(); // when switching dive, reset the zoomLevel
		view->plotDive(d, dc);
		setDive(d, dc);
	} else {
		view->clear();
		stack->setCurrentIndex(0);
	}
}

void ProfileWidget::nextDC()
{
	rotateDC(1);
}

void ProfileWidget::prevDC()
{
	rotateDC(-1);
}

void ProfileWidget::rotateDC(int dir)
{
	if (!d)
		return;
	int numDC = d->number_of_computers();
	int newDC = (dc + dir) % numDC;
	if (newDC < 0)
		newDC += numDC;
	if (newDC == dc)
		return;

	plotDive(d, newDC);
}

void ProfileWidget::divesChanged(const QVector<dive *> &dives, DiveField field)
{
	// If the current dive is not in list of changed dives, do nothing.
	// Also, if we are currently placing a command, don't do anything.
	// Note that we cannot use Command::placingCommand(), because placing
	// a depth or time change on the maintab requires an update.
	if (!d || !dives.contains(d) || !(field.duration || field.depth) || placingCommand)
		return;

	// If we're editing the current dive and not currently
	// placing command, we have to update the edited dive.
	if (editedDive) {
		copy_dive(d, editedDive.get());
		// TODO: Holy moly that function sends too many signals. Fix it!
		DivePlannerPointsModel::instance()->loadFromDive(editedDive.get(), dc);
	}

	// Only if duration or depth changed, the profile needs to be replotted.
	if (field.duration || field.depth)
		plotCurrentDive();
}

void ProfileWidget::cylindersChanged(struct dive *changed, int pos)
{
	// If the current dive is not in list of changed dives, do nothing.
	// Only if duration or depth changed, the profile needs to be replotted.
	// Also, if we are currently placing a command, don't do anything.
	// Note that we cannot use Command::placingCommand(), because placing
	// a depth or time change on the maintab requires an update.
	if (!d || changed != d || !editedDive)
		return;

	// If we're editing the current dive we have to update the
	// cylinders of the edited dive.
	if (editedDive) {
		editedDive.get()->cylinders = d->cylinders;
		// TODO: Holy moly that function sends too many signals. Fix it!
		DivePlannerPointsModel::instance()->loadFromDive(editedDive.get(), dc);
	}
}

void ProfileWidget::setPlanState(const struct dive *d, int dcNr)
{
	dc = dcNr;
	view->setPlanState(d, dcNr);
	setDive(d, dcNr);
}

void ProfileWidget::unsetProfHR()
{
	ui.profHR->setChecked(false);
	qPrefTechnicalDetails::set_hrgraph(false);
}

void ProfileWidget::unsetProfTissues()
{
	ui.profTissues->setChecked(false);
	qPrefTechnicalDetails::set_percentagegraph(false);
}

void ProfileWidget::editDive()
{
	editedDive = std::make_unique<dive>();
	copy_dive(d, editedDive.get()); // Work on a copy of the dive
	DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::EDIT);
	DivePlannerPointsModel::instance()->loadFromDive(editedDive.get(), dc);
	view->setEditState(editedDive.get(), dc);
}

void ProfileWidget::exitEditMode()
{
	if (!editedDive)
		return;

	DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::NOTHING);
	view->setProfileState(d, dc); // switch back to original dive before erasing the copy.
	editedDive.reset();
}

// Update depths of edited dive
static void calcDepth(dive &d, int dcNr)
{
	d.maxdepth = d.get_dc(dcNr)->maxdepth = 0_m;
	divelog.dives.fixup_dive(d);
}

// Silly RAII-variable setter class: reset variable when going out of scope.
template <typename T>
struct Setter {
	T &var, old;
	Setter(T &var, T value) : var(var), old(var) {
		var = value;
	}
	~Setter() {
		var = old;
	}
};

void ProfileWidget::stopAdded()
{
	if (!editedDive)
		return;
	calcDepth(*editedDive, dc);
	Setter s(placingCommand, true);
	Command::editProfile(editedDive.get(), dc, Command::EditProfileType::ADD, 0);
}

void ProfileWidget::stopRemoved(int count)
{
	if (!editedDive)
		return;
	calcDepth(*editedDive, dc);
	Setter s(placingCommand, true);
	Command::editProfile(editedDive.get(), dc, Command::EditProfileType::REMOVE, count);
}

void ProfileWidget::stopMoved(int count)
{
	if (!editedDive)
		return;
	calcDepth(*editedDive, dc);
	Setter s(placingCommand, true);
	Command::editProfile(editedDive.get(), dc, Command::EditProfileType::MOVE, count);
}

void ProfileWidget::stopEdited()
{
	if (!editedDive)
		return;

	Setter s(placingCommand, true);
	Command::editProfile(editedDive.get(), dc, Command::EditProfileType::EDIT, 0);
}