#include "src/ui/rom-settings-widget.hpp"
#include "ui_rom-settings-widget.h"

#include <QVBoxLayout>
#include "src/core/preset-controllers.hpp"
#include "src/core/file-controller.hpp"
#include "src/core/hotkeys.hpp"
#include "src/ui/manage-sd-cards-dialog.hpp"
#include "src/ui/flow-layout.hpp"
#include "src/ui/icons.hpp"
#include "src/input/keyboard.hpp"

RomSettingsWidget::RomSettingsWidget( QWidget *parent ) :
	QWidget( parent ),
	m_ui( new Ui::RomSettingsWidget ),
	m_suppressEvents( true ),
	m_accurateDepthCompare( true ),
	m_suggestOverclock( true ),
	m_supportsMouse( false ),
	m_showAdvancedOptions( FileController::loadUiState().showAdvancedOptions )
{
	m_ui->setupUi( this );
	refreshSdCards();

	m_ui->manageCardsButton->setIcon( Icon::configure() );

	m_ui->pluginSelect->addItem( "ParaLLEl", QVariant::fromValue<int>( (int)GfxPlugin::ParaLLEl ) );
	m_ui->pluginSelect->addItem( "GLideN64", QVariant::fromValue<int>( (int)GfxPlugin::GLideN64 ) );
	m_ui->pluginSelect->addItem( "OGRE", QVariant::fromValue<int>( (int)GfxPlugin::OGRE ) );
	m_ui->pluginSelect->insertSeparator( 3 );
	m_ui->pluginSelect->addItem( "Angrylion", QVariant::fromValue<int>( (int)GfxPlugin::Angrylion ) );
	m_ui->pluginSelect->addItem( "Glide64 (outdated)", QVariant::fromValue<int>( (int)GfxPlugin::Glide64 ) );
	m_ui->pluginSelect->addItem( "Rice (outdated)", QVariant::fromValue<int>( (int)GfxPlugin::Rice ) );
	m_ui->pluginSelect->addItem( tr( "gln64 (obsolete)" ), QVariant::fromValue<int>( (int)GfxPlugin::Gln64 ) );
	updatePluginLangterms( GfxPlugin::UseDefault );

	FlowLayout *stdOptLayout = new FlowLayout( m_ui->flow1, QSize( 16, 6 ) );
	m_ui->flow1->setLayout( stdOptLayout );
	stdOptLayout->addWidget( m_ui->overclockCpuCheckbox );
	stdOptLayout->addWidget( m_ui->overclockViCheckbox );
	stdOptLayout->addWidget( m_ui->emulateFramebufferCheckbox );
	stdOptLayout->addWidget( m_ui->accurateDepthCompareCheckbox );
	stdOptLayout->addWidget( m_ui->upscaleTexRectsCheckbox );
	stdOptLayout->addWidget( m_ui->removeBordersCheckbox );
	stdOptLayout->addWidget( m_ui->trueWidescreenCheckbox );
	stdOptLayout->setContentsMargins( 6, 0, 6, 0 );

	FlowLayout *advOptLayout = new FlowLayout( m_ui->flow2, QSize( 16, 6 ) );
	m_ui->flow2->setLayout( advOptLayout );
	advOptLayout->addWidget( m_ui->widescreenCheckbox );
	advOptLayout->addWidget( m_ui->useInterpreterCheckbox );
	advOptLayout->addWidget( m_ui->bigEepromCheckbox );
	advOptLayout->addWidget( m_ui->rspEmulationCheckbox );
	advOptLayout->setContentsMargins( 6, 0, 6, 0 );

#ifdef __APPLE__
	QMargins margins = stdOptLayout->contentsMargins();
	margins.setBottom( margins.bottom() + 4 );
	stdOptLayout->setContentsMargins( margins );
	advOptLayout->setContentsMargins( margins );
#endif

	const AppSettings &settings = FileController::loadAppSettings();
	setSettings(
		DefaultInputModes::Normal.id,
		settings.defaultGraphicsPlugin,
		true,
		false,
		WidescreenMode::Off,
		false,
		false,
		settings.parallelTexRectUpscaling,
		settings.parallelRemoveBorders,
		settings.glidenFramebufferEmulation,
		settings.glidenCorrectDepthCompare,
		false,
		GfxPlugin::UseDefault,
		true,
		{ N64InputType::Unknown, N64InputType::Unknown, N64InputType::Unknown, N64InputType::Unknown },
		std::nullopt,
		""
	);

	updateWarningVisibility();
	updateAdvancedOptionsVisibility();
	updatePluginSettingsVisibility();
	readjustSize();
	m_suppressEvents = false;
}

RomSettingsWidget::~RomSettingsWidget() {
	delete m_ui;
}

void RomSettingsWidget::readjustSize() {
	m_ui->flow1->setMinimumHeight( m_ui->flow1->heightForWidth( m_ui->flow1->width() ) );
	m_ui->flow2->setMinimumHeight( m_ui->flow2->heightForWidth( m_ui->flow2->width() ) );
}

static inline bool isSpecialInputType( N64InputType inputType ) {
	return(
		inputType == N64InputType::Gamecube ||
		inputType == N64InputType::GamecubeKeyboard ||
		inputType == N64InputType::Mouse
	);
}

static inline bool hasSpecialInputType( const N64InputTypes &inputTypes ) {
	return(
		isSpecialInputType( inputTypes[0] ) ||
		isSpecialInputType( inputTypes[1] ) ||
		isSpecialInputType( inputTypes[2] ) ||
		isSpecialInputType( inputTypes[3] )
	);
}

void RomSettingsWidget::setSettings(
	const Uuid &inputMode,
	GfxPlugin gfxPlugin,
	bool overclockCpu,
	bool overclockVi,
	WidescreenMode widescreen,
	bool bigEEPROM,
	bool useInterpreter,
	bool upscaleTexRects,
	bool removeBorders,
	bool emulateFramebuffer,
	bool accurateDepthCompare,
	bool rspEmulation,
	GfxPlugin recommendedPlugin,
	bool suggestOverclock,
	const N64InputTypes &inputTypes,
	const std::optional<bool> &syncSaves,
	const std::string &sdCard
) {
	m_accurateDepthCompare = accurateDepthCompare;
	m_suggestOverclock = suggestOverclock;

	if( gfxPlugin == GfxPlugin::UseDefault ) {
		gfxPlugin = FileController::loadAppSettings().defaultGraphicsPlugin;
	}

	m_ui->trueWidescreenCheckbox->setVisible( gfxPlugin == GfxPlugin::GLideN64 || gfxPlugin == GfxPlugin::OGRE );

	m_ui->overclockCpuCheckbox->setText(
		suggestOverclock ? tr( "Overclock CPU (Recommended)" ) : tr( "Overclock CPU" )
	);

	if( syncSaves.has_value() ) {
		m_ui->syncSavesCheckbox->setVisible( true );
		m_ui->syncSavesCheckbox->setChecked( syncSaves.value() );
	} else {
		m_ui->syncSavesCheckbox->setVisible( false );
	}

	m_suppressEvents = true;
	if( hasSpecialInputType( inputTypes ) ) {
		m_ui->inputModeSelect->setEnabled( false );
		m_ui->inputModeSelect->setSelected( DefaultInputModes::Normal.id );
	} else {
		m_ui->inputModeSelect->setEnabled( true );
		m_ui->inputModeSelect->setSelected( inputMode );
	}
	m_ui->pluginSelect->setCurrentIndex( m_ui->pluginSelect->findData( (int)gfxPlugin ) );
	m_ui->overclockCpuCheckbox->setChecked( overclockCpu );
	m_ui->overclockViCheckbox->setChecked( overclockVi );
	m_ui->widescreenCheckbox->setChecked( widescreen == WidescreenMode::Stretched );
	m_ui->bigEepromCheckbox->setChecked( bigEEPROM );
	m_ui->useInterpreterCheckbox->setChecked( useInterpreter );
	m_ui->trueWidescreenCheckbox->setChecked( widescreen == WidescreenMode::ViewportHack );
	m_ui->upscaleTexRectsCheckbox->setChecked( upscaleTexRects );
	m_ui->removeBordersCheckbox->setChecked( removeBorders );
	m_ui->emulateFramebufferCheckbox->setChecked( emulateFramebuffer );
	m_ui->accurateDepthCompareCheckbox->setChecked( emulateFramebuffer && accurateDepthCompare );
	m_ui->accurateDepthCompareCheckbox->setEnabled( emulateFramebuffer );
	m_ui->rspEmulationCheckbox->setChecked( rspEmulation );

	if( FileController::loadAppSettings().emulateSdCard || !sdCard.empty() ) {
		m_ui->sdCardPanel->setVisible( true );
		
		m_ui->sdCardSelect->setCurrentIndex( 0 );
		for( int i = 0; i < m_ui->sdCardSelect->count(); i++ ) {
			if( sdCard == m_ui->sdCardSelect->itemData( i, Qt::UserRole ).toString().toStdString() ) {
				m_ui->sdCardSelect->setCurrentIndex( i );
				break;
			}
		}
	} else {
		m_ui->sdCardPanel->setVisible( false );
		m_ui->sdCardSelect->setCurrentIndex( 0 );
	}

	m_suppressEvents = false;

	m_supportsMouse = (
		inputTypes[0] == N64InputType::Mouse ||
		inputTypes[1] == N64InputType::Mouse ||
		inputTypes[2] == N64InputType::Mouse ||
		inputTypes[3] == N64InputType::Mouse
	);

	updateWarningVisibility();
	updatePluginLangterms( recommendedPlugin );
	reloadHotkeyInfo();
	readjustSize();
}


const Uuid &RomSettingsWidget::getInputMode() const {
	return m_ui->inputModeSelect->getSelected().id;
}

GfxPlugin RomSettingsWidget::getGraphicsPlugin() const {
	return (GfxPlugin)m_ui->pluginSelect->currentData().toInt();
}

bool RomSettingsWidget::overclockCpu() const {
	return m_ui->overclockCpuCheckbox->isChecked();
}

bool RomSettingsWidget::overclockVi() const {
	return m_ui->overclockViCheckbox->isChecked();
}

WidescreenMode RomSettingsWidget::widescreen() const {
	if( m_ui->trueWidescreenCheckbox->isChecked() ) {
		return WidescreenMode::ViewportHack;
	} else if( m_ui->widescreenCheckbox->isChecked() ) {
		return WidescreenMode::Stretched;
	} else {
		return WidescreenMode::Off;
	}
}

bool RomSettingsWidget::bigEEPROM() const {
	return m_ui->bigEepromCheckbox->isChecked();
}

bool RomSettingsWidget::useInterpreter() const {
	return m_ui->useInterpreterCheckbox->isChecked();
}

bool RomSettingsWidget::upscaleTexRects() const {
	return m_ui->upscaleTexRectsCheckbox->isChecked();
}

bool RomSettingsWidget::removeBorders() const {
	return m_ui->removeBordersCheckbox->isChecked();
}

bool RomSettingsWidget::emulateFramebuffer() const {
	return m_ui->emulateFramebufferCheckbox->isChecked();
}

bool RomSettingsWidget::glidenRspEmulation() const {
	return m_ui->rspEmulationCheckbox->isChecked();
}

std::string RomSettingsWidget::sdCard() const {
	const int index = m_ui->sdCardSelect->currentIndex();
	if( index <= 0 || index > (int)m_sdCards.size() ) return "";
	return m_sdCards[index - 1].name;
}

void RomSettingsWidget::updateWarningVisibility() {
	m_ui->performanceWarningLabel->setVisible(
		m_suggestOverclock &&
		!m_ui->overclockCpuCheckbox->isChecked()
	);
#ifdef __APPLE__
	m_ui->macDepthWarning->setVisible(
		m_ui->accurateDepthCompareCheckbox->isChecked() &&
		m_ui->accurateDepthCompareCheckbox->isVisible()
	);
#else
	m_ui->macDepthWarning->setVisible( false );
#endif

	reloadHotkeyInfo();
}

void RomSettingsWidget::updateAdvancedOptionsVisibility() {
	m_ui->advancedSettings->setVisible( m_showAdvancedOptions );
	m_ui->advancedOptionsLink->setText(
		QString( "<a href=\"#\">" ) +
		(m_showAdvancedOptions ? tr( "Hide Advanced Options" ) : tr( "Show Advanced Options" )) +
		QString( "</a>" )
	);

	readjustSize();
}

void RomSettingsWidget::reloadHotkeyInfo() {
	if( m_supportsMouse ) {
		const KeyId hotkey = FileController::loadHotkeys().at( (int)Hotkey::GrabMouse );
		const QString keyName = Keycode::getNames( hotkey ).displayName;
		m_ui->mouseGrabHotkeyWarning->setVisible( hotkey == KeyId::INVALID );
		m_ui->mouseHintLabel->setVisible( hotkey != KeyId::INVALID );
		m_ui->mouseHintLabel->setText( tr( "This ROM supports mouse input. After launching the emulator, your mouse will be captured by the emulator. Press %1 if you need to free the mouse cursor." ).arg( keyName ) );
	} else {
		m_ui->mouseGrabHotkeyWarning->setVisible( false );
		m_ui->mouseHintLabel->setVisible( false );
	}
}

void RomSettingsWidget::updatePluginSettingsVisibility() {
	const GfxPlugin plugin = getGraphicsPlugin();

	m_ui->upscaleTexRectsCheckbox->setVisible( plugin == GfxPlugin::ParaLLEl );
	m_ui->removeBordersCheckbox->setVisible( plugin == GfxPlugin::ParaLLEl );
	m_ui->emulateFramebufferCheckbox->setVisible( plugin == GfxPlugin::GLideN64 );
	m_ui->accurateDepthCompareCheckbox->setVisible( plugin == GfxPlugin::GLideN64 );
	m_ui->rspEmulationCheckbox->setVisible( plugin == GfxPlugin::GLideN64 );
	m_ui->trueWidescreenCheckbox->setVisible( plugin == GfxPlugin::GLideN64 || plugin == GfxPlugin::OGRE );
}

void RomSettingsWidget::inputModeSelectionChanged() {
	if( m_suppressEvents ) return;
	emit inputModeChanged();
}

void RomSettingsWidget::overclockCpuToggled( bool checked ) {
	m_ui->performanceWarningLabel->setVisible( !checked );
	if( m_suppressEvents ) return;
	emit overclockCpuChanged( checked );
}

void RomSettingsWidget::overclockViToggled( bool checked ) {
	if( m_suppressEvents ) return;
	emit overclockViChanged( checked );
}

void RomSettingsWidget::widescreenToggled( bool checked ) {
	if( m_suppressEvents ) return;
	if( checked ) {
		m_suppressEvents = true;
		m_ui->trueWidescreenCheckbox->setChecked( false );
		m_suppressEvents = false;
	}
	emit widescreenChanged( checked ? (ubyte)WidescreenMode::Stretched : (ubyte)WidescreenMode::Off );
}

void RomSettingsWidget::bigEepromToggled( bool checked ) {
	if( m_suppressEvents ) return;
	emit bigEepromChanged( checked );
}

void RomSettingsWidget::useInterpreterToggled( bool checked ) {
	if( m_suppressEvents ) return;
	emit useInterpreterChanged( checked );
}

void RomSettingsWidget::trueWidescreenToggled( bool checked ) {
	if( m_suppressEvents ) return;
	if( checked ) {
		m_suppressEvents = true;
		m_ui->widescreenCheckbox->setChecked( false );
		m_suppressEvents = false;
	}
	emit widescreenChanged( checked ? (ubyte)WidescreenMode::ViewportHack : (ubyte)WidescreenMode::Off );
}

void RomSettingsWidget::upscaleTexRectsToggled( bool checked ) {
	if( m_suppressEvents ) return;
	emit upscaleTexRectsChanged( checked );
}

void RomSettingsWidget::removeBordersToggled( bool checked ) {
	if( m_suppressEvents ) return;
	emit removeBordersChanged( checked );
}

void RomSettingsWidget::emulateFramebufferToggled( bool checked ) {
	m_ui->accurateDepthCompareCheckbox->setEnabled( checked );
	if( m_suppressEvents ) {
		m_ui->accurateDepthCompareCheckbox->setChecked( checked && m_accurateDepthCompare );
	} else {
		m_suppressEvents = true;
		m_ui->accurateDepthCompareCheckbox->setChecked( checked && m_accurateDepthCompare );
		m_suppressEvents = false;
		emit emulateFramebufferChanged( checked );
	}
}

void RomSettingsWidget::accurateDepthCompareToggled( bool checked ) {
	m_accurateDepthCompare = checked;
	if( m_suppressEvents ) return;
	updateWarningVisibility();
	emit accurateDepthCompareChanged( checked );
}

void RomSettingsWidget::rspEmulationToggled( bool checked ) {
	if( m_suppressEvents ) return;
	emit rspEmulationChanged( checked );
}

void RomSettingsWidget::pluginChanged() {
	updatePluginSettingsVisibility();
	readjustSize();
	emit graphicsPluginChanged();
}

void RomSettingsWidget::saveSyncingToggled( bool checked ) {
	if( m_suppressEvents || !m_ui->syncSavesCheckbox->isVisible() ) return;
	emit saveSyncingChanged( checked );
}

void RomSettingsWidget::advancedOptionsToggled() {
	m_showAdvancedOptions = !m_showAdvancedOptions;
	updateAdvancedOptionsVisibility();

	if( m_suppressEvents ) return;
	emit showAdvancedOptionsChanged( m_showAdvancedOptions );
}

void RomSettingsWidget::sdCardChanged( int index ) {
	if( m_suppressEvents || !m_ui->sdCardPanel->isVisible() ) return;
	if( index < 0 || index > (int)m_sdCards.size() ) return;

	if( index == 0 ) {
		emit activeSdCardChanged( std::string() );
	} else {
		emit activeSdCardChanged( m_sdCards[index - 1].name );
	}
}

void RomSettingsWidget::manageSdCards() {
	ManageSdCardsDialog dialog( this );
	dialog.exec();

	const std::string selectedCard = sdCard();
	refreshSdCards();

	if( selectedCard.empty() || selectedCard == "?" ) {
		m_ui->sdCardSelect->setCurrentIndex( 0 );
		return;
	}
	
	for( int i = 0; i < m_ui->sdCardSelect->count(); i++ ) {
		if( selectedCard == m_ui->sdCardSelect->itemData( i, Qt::UserRole ).toString().toStdString() ) {
			m_ui->sdCardSelect->setCurrentIndex( i );
			return;
		}
	}

	m_ui->sdCardSelect->setCurrentIndex( 0 );
	emit activeSdCardChanged( std::string() );
}

void RomSettingsWidget::updatePluginLangterms( GfxPlugin recommendedPlugin ) {
	int index;
	
	index = m_ui->pluginSelect->findData( (int)GfxPlugin::ParaLLEl );
	if( recommendedPlugin == GfxPlugin::UseDefault ) {
		m_ui->pluginSelect->setItemText( index, tr( "ParaLLEl (Recommended - very accurate to console)" ) );
	} else if( recommendedPlugin == GfxPlugin::ParaLLEl ) {
		m_ui->pluginSelect->setItemText( index, tr( "ParaLLEl (recommended by hack author)" ) );
	} else {
		m_ui->pluginSelect->setItemText( index, "ParaLLEl" );
	}

	index = m_ui->pluginSelect->findData( (int)GfxPlugin::GLideN64 );
	if( recommendedPlugin == GfxPlugin::UseDefault ) {
		m_ui->pluginSelect->setItemText( index, tr( "GLideN64 (Recommended for lower end computers)" ) );
	} else if( recommendedPlugin == GfxPlugin::GLideN64 ) {
		m_ui->pluginSelect->setItemText( index, tr( "GLideN64 (recommended by hack author)" ) );
	} else {
		m_ui->pluginSelect->setItemText( index, "GLideN64" );
	}

	index = m_ui->pluginSelect->findData( (int)GfxPlugin::OGRE );
	if( recommendedPlugin == GfxPlugin::UseDefault ) {
		m_ui->pluginSelect->setItemText( index, tr( "OGRE (Needed by some older romhacks)" ) );
	} else if( recommendedPlugin == GfxPlugin::OGRE ) {
		m_ui->pluginSelect->setItemText( index, tr( "OGRE (recommended by hack author)" ) );
	} else {
		m_ui->pluginSelect->setItemText( index, "OGRE" );
	}

	index = m_ui->pluginSelect->findData( (int)GfxPlugin::Angrylion );
	if( recommendedPlugin == GfxPlugin::Angrylion ) {
		m_ui->pluginSelect->setItemText( index, tr( "Angrylion (recommended by hack author)" ) );
	} else {
		m_ui->pluginSelect->setItemText( index, "Angrylion" );
	}
	
	index = m_ui->pluginSelect->findData( (int)GfxPlugin::Glide64 );
	if( recommendedPlugin == GfxPlugin::Glide64 ) {
		m_ui->pluginSelect->setItemText( index, tr( "Glide64 (recommended by hack author)" ) );
	} else {
		m_ui->pluginSelect->setItemText( index, tr( "Glide64 (outdated)" ) );
	}

	index = m_ui->pluginSelect->findData( (int)GfxPlugin::Rice );
	if( recommendedPlugin == GfxPlugin::Rice ) {
		m_ui->pluginSelect->setItemText( index, tr( "Rice (recommended by hack author)" ) );
	} else {
		m_ui->pluginSelect->setItemText( index, tr( "Rice (outdated)" ) );
	}
}

static inline QString makeSdCardText( const SdCardInfo &card ) {
	QString text( card.name.c_str() );
	text.append( " (" );

	switch( card.format ) {
		case SdCardFormat::FAT12:
			text.append( "FAT12, " );
			break;
		case SdCardFormat::FAT16:
			text.append( "FAT16, " );
			break;
		case SdCardFormat::FAT32:
			text.append( "FAT32, " );
			break;
		default: break;
	}

	if( card.numBlocks >= 1073741824u ) {
		text.append( QString::number( (double)card.numBlocks / 1073741824.0, 'f', 1 ) );
		text.append( " TiB)" );
	} else if( card.numBlocks >= 1048576u ) {
		text.append( QString::number( (double)card.numBlocks / 1048576.0, 'f', 1 ) );
		text.append( " GiB)" );
	} else if( card.numBlocks >= 1024u ) {
		text.append( QString::number( card.numBlocks / 1024.0 ) );
		text.append( " MiB)" );
	} else {
		text.append( QString::number( card.numBlocks ) );
		text.append( " KiB)" );
	}

	return text;
}

void RomSettingsWidget::refreshSdCards() {
	m_sdCards = SdCardManager::fetchAll();

	m_ui->sdCardSelect->clear();
	m_ui->sdCardSelect->addItem( tr( "None" ), QVariant( QString() ) );
	for( const SdCardInfo &card : m_sdCards ) {
		m_ui->sdCardSelect->addItem(
			makeSdCardText( card ),
			QVariant( QString( card.name.c_str() ) )
		);
	}
}

bool RomSettingsWidget::getAdvancedOptionsVisibility() const {
	return m_showAdvancedOptions;
}

void RomSettingsWidget::setAdvancedOptionsVisibility( bool visible ) {
	m_showAdvancedOptions = visible;
	updateAdvancedOptionsVisibility();
}

RomSettingsWidgetSlot::RomSettingsWidgetSlot( QWidget *parent ) : QFrame( parent ) {
	setContentsMargins( 0, 0, 0, 0 );
	setFrameShape( QFrame::NoFrame );

	QVBoxLayout *layout = new QVBoxLayout( this );
	layout->setContentsMargins( 0, 0, 0, 0 );
	layout->setMargin( 0 );

	setLayout( layout );
}

void RomSettingsWidgetSlot::place( RomSettingsWidget *widget ) {
	if( hasWidget() ) return;

	if( widget->parentWidget() != nullptr ) {
		widget->parentWidget()->layout()->removeWidget( widget );
	}

	widget->setParent( this );
	layout()->addWidget( widget );
}

RomSettingsWidget *RomSettingsWidgetSlot::widget() const {
	if( layout()->children().isEmpty() ) return nullptr;
	return layout()->children().isEmpty() ? nullptr : dynamic_cast<RomSettingsWidget*>( layout()->children().first() );
}
