/*
 * Copyright 2021 - 2023 gary@drinkingtea.net
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <QApplication>
#include <QHBoxLayout>
#include <QMenuBar>
#include <QPushButton>
#include <QStatusBar>

#include "settingsdialog.hpp"
#include "slideview.hpp"
#include "mainwindow.hpp"

MainWindow::MainWindow(QWidget *parent): QMainWindow(parent) {
	move(0, 0);
	setFixedSize(610, 555);
	setWindowTitle(tr("Slide Controller 9000"));
	setupMenu();
	const auto mainWidget = new QWidget(this);
	const auto rootLyt = new QVBoxLayout;
	const auto controlsLayout = new QGridLayout;
	m_slideView = new SlideView(this);
	setCentralWidget(mainWidget);
	mainWidget->setLayout(rootLyt);
	rootLyt->addWidget(m_slideView);
	rootLyt->addLayout(controlsLayout);
	// setup slide controls
	const auto btnPrevSong = new QPushButton(tr("Previous Song (Left)"), this);
	const auto btnPrevSlide = new QPushButton(tr("Previous Slide (Up)"), this);
	const auto btnNextSlide = new QPushButton(tr("Next Slide (Down)"), this);
	const auto btnNextSong = new QPushButton(tr("Next Song (Right)"), this);
	controlsLayout->addWidget(btnPrevSlide, 0, 1);
	controlsLayout->addWidget(btnNextSlide, 0, 2);
	controlsLayout->addWidget(btnPrevSong, 0, 0);
	controlsLayout->addWidget(btnNextSong, 0, 3);
	controlsLayout->setSpacing(2);
	btnNextSong->setShortcut(Qt::Key_Right);
	btnPrevSong->setShortcut(Qt::Key_Left);
	btnNextSlide->setShortcut(Qt::Key_Down);
	btnPrevSlide->setShortcut(Qt::Key_Up);
	btnNextSong->setFixedWidth(135);
	btnPrevSong->setFixedWidth(135);
	btnNextSlide->setFixedWidth(135);
	btnPrevSlide->setFixedWidth(135);
	setupViewControls(rootLyt);
	connect(btnNextSlide, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::nextSlide);
	connect(btnPrevSlide, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::prevSlide);
	connect(btnNextSong, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::nextSong);
	connect(btnPrevSong, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::prevSong);
	connect(&m_openlpClient, &OpenLPClient::pollUpdate, m_slideView, &SlideView::pollUpdate);
	connect(&m_openlpClient, &OpenLPClient::songListUpdate, m_slideView, &SlideView::songListUpdate);
	connect(&m_openlpClient, &OpenLPClient::slideListUpdate, m_slideView, &SlideView::slideListUpdate);
	connect(&m_openlpClient, &OpenLPClient::pollFailed, m_slideView, &SlideView::reset);
	connect(m_slideView, &SlideView::songChanged, &m_openlpClient, &OpenLPClient::changeSong);
	connect(m_slideView, &SlideView::slideChanged, &m_openlpClient, &OpenLPClient::changeSlide);
	// setup status bar
	setStatusBar(new QStatusBar(this));
	connect(&m_cameraClient, &CameraClient::pollUpdate, this, &MainWindow::cameraConnectionInit);
	connect(&m_openlpClient, &OpenLPClient::songChanged, this, &MainWindow::refreshStatusBar);
	connect(&m_openlpClient, &OpenLPClient::pollUpdate, this, &MainWindow::openLpConnectionInit);
	connect(&m_obsClient, &OBSClient::pollUpdate, this, &MainWindow::obsConnectionInit);
	refreshStatusBar();
	connect(statusBar(), &QStatusBar::messageChanged, this, [this](QStringView const&msg) {
		if (msg.empty()) {
			refreshStatusBar();
		}
	});
}

void MainWindow::setupMenu() {
	// file menu
	auto const fileMenu = menuBar()->addMenu(tr("&File"));
	auto const settingsAct = new QAction(tr("&Settings"), this);
	auto const quitAct = new QAction(tr("E&xit"), this);
	settingsAct->setShortcuts(QKeySequence::Preferences);
	connect(settingsAct, &QAction::triggered, this, &MainWindow::openSettings);
	quitAct->setShortcuts(QKeySequence::Quit);
	quitAct->setStatusTip(tr("Exit application"));
	connect(quitAct, &QAction::triggered, &QApplication::quit);
	fileMenu->addAction(settingsAct);
	fileMenu->addAction(quitAct);
}

void MainWindow::setupDefaultViewControls(QHBoxLayout *viewCtlLyt) {
	auto const mainWidget = viewCtlLyt->parentWidget();
	auto const btnHideSlides = new QPushButton(tr("1. Hide"), mainWidget);
	auto const btnOpenLpShowSlides = new QPushButton(tr("2. Show in OpenLP Only"), mainWidget);
	auto const btnShowSlides = new QPushButton(tr("3. Show"), mainWidget);
	viewCtlLyt->addWidget(btnHideSlides);
	viewCtlLyt->addWidget(btnOpenLpShowSlides);
	viewCtlLyt->addWidget(btnShowSlides);
	btnHideSlides->setShortcut(Qt::Key_1);
	btnOpenLpShowSlides->setShortcut(Qt::Key_2);
	btnHideSlides->setToolTip(tr("Also hides slides in OBS"));
	btnShowSlides->setShortcut(Qt::Key_3);
	connect(btnHideSlides, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::blankScreen);
	connect(btnHideSlides, &QPushButton::clicked, &m_obsClient, &OBSClient::hideSlides);
	connect(btnOpenLpShowSlides, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::showSlides);
	connect(btnOpenLpShowSlides, &QPushButton::clicked, &m_obsClient, &OBSClient::hideSlides);
	connect(btnShowSlides, &QPushButton::clicked, &m_obsClient, &OBSClient::showSlides);
	connect(btnShowSlides, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::showSlides);
}

void MainWindow::setupCustomViewControls(QVector<View> const&views, QGridLayout *viewCtlLyt) {
	constexpr auto columns = 3;
	auto const parent = viewCtlLyt->parentWidget();
	for (auto i = 0; auto const&view : views) {
		auto const x = i % columns;
		auto const y = i / columns;
		auto const name = QString("%1. %2").arg(i + 1).arg(view.name);
		auto const btn = new QPushButton(name, parent);
		btn->setShortcut(Qt::Key_1 + i);
		viewCtlLyt->addWidget(btn, y, x);
		auto const slides = view.slides;
		auto const obsSlides = view.obsSlides;
		auto const cameraPreset = view.cameraPreset;
		connect(btn, &QPushButton::clicked, this, [this, slides, obsSlides, cameraPreset] {
			m_cameraClient.setPreset(cameraPreset);
			m_openlpClient.setSlidesVisible(slides);
			m_obsClient.setSlidesVisible(obsSlides);
		});
		++i;
	}
}

void MainWindow::setupViewControls(QVBoxLayout *rootLyt) {
	auto views = getViews();
	if (!m_viewControlsParent) {
		m_viewControlsParent = new QWidget(rootLyt->parentWidget());
		m_viewControlsParentLyt = new QHBoxLayout(m_viewControlsParent);
		m_viewControlsParentLyt->setContentsMargins(0, 0, 0, 0);
		rootLyt->addWidget(m_viewControlsParent);
	}
	delete m_viewControls;
	m_viewControls = new QWidget(m_viewControlsParent);
	m_viewControlsParentLyt->addWidget(m_viewControls);
	auto const viewCtlLyt = new QGridLayout(m_viewControls);
	viewCtlLyt->setSpacing(5);
	if (views.empty()) {
		views.emplace_back(View{
				.name = tr("Hide"),
				.slides = false,
				.obsSlides = false,
		});
		views.emplace_back(View{
				.name = tr("Hide in OBS"),
				.slides = false,
				.obsSlides = false,
		});
		views.emplace_back(View{
			.name = tr("Show"),
			.slides = false,
			.obsSlides = false,
		});
	}
	setupCustomViewControls(views, viewCtlLyt);
}

void MainWindow::openSettings() {
	SettingsDialog d(this);
	auto const result = d.exec();
	if (result == QDialog::Accepted) {
		m_cameraClient.setBaseUrl();
		m_obsClient.setBaseUrl();
		m_openlpClient.setBaseUrl();
	}
}

void MainWindow::cameraConnectionInit() {
	disconnect(&m_cameraClient, &CameraClient::pollUpdate, this, &MainWindow::cameraConnectionInit);
	connect(&m_cameraClient, &CameraClient::pollFailed, this, &MainWindow::cameraConnectionLost);
	m_cameraConnected = true;
	refreshStatusBar();
}

void MainWindow::cameraConnectionLost() {
	disconnect(&m_cameraClient, &CameraClient::pollFailed, this, &MainWindow::cameraConnectionLost);
	connect(&m_cameraClient, &CameraClient::pollUpdate, this, &MainWindow::cameraConnectionInit);
	m_cameraConnected = false;
	refreshStatusBar();
}

void MainWindow::openLpConnectionInit() {
	disconnect(&m_openlpClient, &OpenLPClient::pollUpdate, this, &MainWindow::openLpConnectionInit);
	connect(&m_openlpClient, &OpenLPClient::pollFailed, this, &MainWindow::openLpConnectionLost);
	m_openLpConnected = true;
	refreshStatusBar();
}

void MainWindow::openLpConnectionLost() {
	disconnect(&m_openlpClient, &OpenLPClient::pollFailed, this, &MainWindow::openLpConnectionLost);
	connect(&m_openlpClient, &OpenLPClient::pollUpdate, this, &MainWindow::openLpConnectionInit);
	m_openLpConnected = false;
	refreshStatusBar();
}

void MainWindow::obsConnectionInit() {
	disconnect(&m_obsClient, &OBSClient::pollUpdate, this, &MainWindow::obsConnectionInit);
	connect(&m_obsClient, &OBSClient::pollFailed, this, &MainWindow::obsConnectionLost);
	m_obsConnected = true;
	refreshStatusBar();
}

void MainWindow::obsConnectionLost() {
	disconnect(&m_obsClient, &OBSClient::pollFailed, this, &MainWindow::obsConnectionLost);
	connect(&m_obsClient, &OBSClient::pollUpdate, this, &MainWindow::obsConnectionInit);
	m_obsConnected = false;
	refreshStatusBar();
}

void MainWindow::refreshStatusBar() {
	auto const cameraStatus = m_cameraConnected ? tr("Camera: Connected") : tr("Camera: Not Connected");
	auto const openLpStatus = m_openLpConnected ? tr("OpenLP: Connected") : tr("OpenLP: Not Connected");
	auto const obsStatus = m_obsConnected ? tr("OBS: Connected") : tr("OBS: Not Connected");
	auto const nextSong = m_openlpClient.getNextSong();
	auto const nextSongTxt = m_openLpConnected ? " | Next Song: " + nextSong : "";
	statusBar()->showMessage(cameraStatus + " | " + openLpStatus + " | " + obsStatus + nextSongTxt);
}