/* FCE Ultra - NES/Famicom Emulator
 *
 * Copyright notice for this file:
 *  Copyright (C) 2020 mjbudd77
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
// GameGenie.cpp
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>

#include <QHeaderView>
#include <QCloseEvent>
#include <QSettings>

#include "../../types.h"
#include "../../fceu.h"
#include "../../cart.h"
#include "../../cheat.h"
#include "../../debug.h"
#include "../../driver.h"

#include "Qt/main.h"
#include "Qt/dface.h"
#include "Qt/dface.h"
#include "Qt/input.h"
#include "Qt/config.h"
#include "Qt/keyscan.h"
#include "Qt/fceuWrapper.h"
#include "Qt/HexEditor.h"
#include "Qt/GameGenie.h"

static const char *GameGenieLetters = "APZLGITYEOXUKSVN";

class fceuGGCodeValidtor : public QValidator
{ 
   public:
   	fceuGGCodeValidtor( QObject *parent)
			: QValidator(parent)
		{

		}

		QValidator::State validate(QString &input, int &pos) const
		{
		   int i,j, ok;
		   //printf("Validate: %i '%s'\n", input.size(), input.toStdString().c_str() );
		
		   if ( input.size() == 0 )
		   {
		      return QValidator::Acceptable;
		   }
			input = input.toUpper();
		   std::string s = input.toStdString();
		   i=0; 

			while ( s[i] != 0 )
			{
				j=0; ok=0;
				while ( GameGenieLetters[j] != 0 )
				{
					if ( s[i] == GameGenieLetters[j] )
					{
						ok = 1; break;
					}
					j++;
				}

				if ( !ok )
				{
		   		return QValidator::Invalid;
				}
				i++;
			}
		
		   return QValidator::Acceptable;
		}

	private:
};
//----------------------------------------------------------------------------
GameGenieDialog_t::GameGenieDialog_t(QWidget *parent)
	: QDialog( parent )
{
	int charWidth;
	QVBoxLayout *mainLayout, *vbox1, *vbox;
	QHBoxLayout *hbox1, *hbox;
	QTreeWidgetItem *item;
	QGroupBox *frame;
	QFont font;
	QPushButton *closeButton;
	fceuGGCodeValidtor *ggCodeValidator;
	QSettings settings;

	font.setFamily("Courier New");
	font.setStyle( QFont::StyleNormal );
	font.setStyleHint( QFont::Monospace );

	QFontMetrics fm(font);

#if QT_VERSION > QT_VERSION_CHECK(5, 11, 0)
    charWidth = fm.horizontalAdvance(QLatin1Char('2'));
#else
    charWidth = fm.width(QLatin1Char('2'));
#endif

	setWindowTitle("Game Genie Encoder/Decoder Tool");

	mainLayout = new QVBoxLayout();
	vbox  = new QVBoxLayout();
	vbox1 = new QVBoxLayout();
	hbox1 = new QHBoxLayout();

	frame = new QGroupBox( tr("Address/Compare/Value") );
	mainLayout->addLayout( hbox1 );
	frame->setLayout( vbox );

	hbox1->addWidget( frame );
	hbox1->addLayout( vbox1 );
	addr = new QLineEdit();
	cmp  = new QLineEdit();
	val  = new QLineEdit();

	hbox = new QHBoxLayout();
	vbox->addLayout( hbox );
	hbox->addWidget( new QLabel( tr("Address:") ), 0, Qt::AlignRight );
	hbox->addWidget( addr, 0, Qt::AlignLeft );

	hbox = new QHBoxLayout();
	vbox->addLayout( hbox );
	hbox->addWidget( new QLabel( tr("Compare:") ), 0, Qt::AlignRight );
	hbox->addWidget( cmp, 0, Qt::AlignLeft );

	hbox = new QHBoxLayout();
	vbox->addLayout( hbox );
	hbox->addWidget( new QLabel( tr("Value:") ), 0, Qt::AlignRight );
	hbox->addWidget( val, 0, Qt::AlignLeft );

	frame = new QGroupBox( tr("Game Genie Code") );
	vbox  = new QVBoxLayout();
	vbox1->addWidget( frame );
	frame->setLayout( vbox );
	
	ggCode = new QLineEdit();
	vbox->addWidget( ggCode );

	addCheatBtn = new QPushButton( tr("Add To Cheat List") );
	vbox1->addWidget( addCheatBtn );

	tree = new QTreeWidget();

	tree->setColumnCount(1);

	item = new QTreeWidgetItem();
	item->setText( 0, QString::fromStdString( "Possible Affected ROM File Addresses" ) );
	item->setTextAlignment( 0, Qt::AlignLeft);

	tree->setHeaderItem( item );

	tree->header()->setSectionResizeMode( QHeaderView::ResizeToContents );

	mainLayout->addWidget( tree );

	closeButton = new QPushButton( tr("Close") );
	closeButton->setIcon(style()->standardIcon(QStyle::SP_DialogCloseButton));
	connect(closeButton, SIGNAL(clicked(void)), this, SLOT(closeWindow(void)));

	hbox = new QHBoxLayout();
	hbox->addStretch(5);
	hbox->addWidget( closeButton, 1 );

	mainLayout->addLayout( hbox );

	setLayout( mainLayout );

	addrValidator = new fceuHexIntValidtor( 0, 0xFFFF, this );
	cmpValidator  = new fceuHexIntValidtor( 0, 0x00FF, this );
	valValidator  = new fceuHexIntValidtor( 0, 0x00FF, this );

	ggCodeValidator = new fceuGGCodeValidtor( this );

	addr->setValidator( addrValidator );
	cmp->setValidator( cmpValidator );
	val->setValidator( valValidator );
	ggCode->setValidator( ggCodeValidator );

	addr->setMaxLength( 4 );
	cmp->setMaxLength( 2 );
	val->setMaxLength( 2 );
	ggCode->setMaxLength( 8 );

	addr->setMinimumWidth( 6 * charWidth );
	cmp->setMinimumWidth( 4 * charWidth );
	val->setMinimumWidth( 4 * charWidth );
	addr->setMaximumWidth( 6 * charWidth );
	cmp->setMaximumWidth( 4 * charWidth );
	val->setMaximumWidth( 4 * charWidth );

	addr->setAlignment(Qt::AlignCenter);
	cmp->setAlignment(Qt::AlignCenter);
	val->setAlignment(Qt::AlignCenter);

	addr->setFont( font );
	cmp->setFont( font );
	val->setFont( font );
	ggCode->setFont( font );

	connect( addCheatBtn, SIGNAL(clicked(void)), this, SLOT(addCheatClicked(void)));

	connect( addr  , SIGNAL(textEdited(const QString &)), this, SLOT(addrChanged(const QString &)));
	connect( cmp   , SIGNAL(textEdited(const QString &)), this, SLOT(cmpChanged(const QString &)));
	connect( val   , SIGNAL(textEdited(const QString &)), this, SLOT(valChanged(const QString &)));
	connect( ggCode, SIGNAL(textEdited(const QString &)), this, SLOT(ggChanged(const QString &)));

	connect( tree, SIGNAL(itemActivated(QTreeWidgetItem*, int)), this, SLOT(romAddrDoubleClicked(QTreeWidgetItem*, int)) );

	addCheatBtn->setEnabled( false );

	restoreGeometry(settings.value("GameGenieWindow/geometry").toByteArray());
}
//----------------------------------------------------------------------------
GameGenieDialog_t::~GameGenieDialog_t(void)
{
	QSettings settings;
	//printf("Destroy Game Genie Window\n");
	settings.setValue("GameGenieWindow/geometry", saveGeometry());
}
//----------------------------------------------------------------------------
void GameGenieDialog_t::closeEvent(QCloseEvent *event)
{
	//printf("Game Genie Close Window Event\n");
	done(0);
	deleteLater();
	event->accept();
}
//----------------------------------------------------------------------------
void GameGenieDialog_t::closeWindow(void)
{
	//printf("Close Window\n");
	done(0);
	deleteLater();
}
//----------------------------------------------------------------------------
void GameGenieDialog_t::addCheatClicked(void)
{
	int a = -1, v = -1, c = -1;
	std::string name;

	name = ggCode->text().toStdString();

	if ( addr->text().size() > 0 )
	{
		a = strtol( addr->text().toStdString().c_str(), NULL, 16 );
	}
	if ( val->text().size() > 0 )
	{
		v = strtol( val->text().toStdString().c_str(), NULL, 16 );
	}
	if ( cmp->text().size() > 0 )
	{
		c = strtol( cmp->text().toStdString().c_str(), NULL, 16 );
	}

	FCEU_WRAPPER_LOCK();
	FCEUI_AddCheat( name.c_str(), a, v, c, 1 );
	FCEU_WRAPPER_UNLOCK();

}
//----------------------------------------------------------------------------
void GameGenieDialog_t::romAddrDoubleClicked(QTreeWidgetItem *item, int column)
{
	int addr;

	addr = strtol( item->text(0).toStdString().c_str(), NULL, 16 );

	printf("ROM Addr: %06X \n", addr );

	hexEditorOpenFromDebugger( QHexEdit::MODE_NES_ROM, addr );
}
//----------------------------------------------------------------------------
void GameGenieDialog_t::addrChanged(const QString &s)
{
	int a, v, c = -1;
	char gg[12];

	a = strtol(           s.toStdString().c_str(), NULL, 16 );
	v = strtol( val->text().toStdString().c_str(), NULL, 16 );

	if ( cmp->text().size() > 0 )
	{
		c = strtol( cmp->text().toStdString().c_str(), NULL, 16 );
	}

	EncodeGG( gg, a, v, c );

	ggCode->setText( tr(gg) );

	ListGGAddresses();
}
//----------------------------------------------------------------------------
void GameGenieDialog_t::cmpChanged(const QString &s)
{
	int a, v, c = -1;
	char gg[12];

	a = strtol( addr->text().toStdString().c_str(), NULL, 16 );
	v = strtol(  val->text().toStdString().c_str(), NULL, 16 );

	if ( s.size() > 0 )
	{
		c = strtol( s.toStdString().c_str(), NULL, 16 );
	}

	EncodeGG( gg, a, v, c );

	ggCode->setText( tr(gg) );

	ListGGAddresses();
}
//----------------------------------------------------------------------------
void GameGenieDialog_t::valChanged(const QString &s)
{
	int a, v, c = -1;
	char gg[12];

	a = strtol( addr->text().toStdString().c_str(), NULL, 16 );
	v = strtol(            s.toStdString().c_str(), NULL, 16 );

	if ( cmp->text().size() > 0 )
	{
		c = strtol( cmp->text().toStdString().c_str(), NULL, 16 );
	}
	EncodeGG( gg, a, v, c );

	ggCode->setText( tr(gg) );

	ListGGAddresses();
}
//----------------------------------------------------------------------------
void GameGenieDialog_t::ggChanged(const QString &s)
{
	int a = -1, c = -1, v = -1;
	char gg[12];
	char stmp[32];

	memset( gg, 0, sizeof(gg) );

	strncpy( gg, ggCode->text().toStdString().c_str(), 8 );

	FCEUI_DecodeGG( gg, &a, &v, &c);

	if ( a >= 0 )
	{
		sprintf( stmp, "%04X", a );

		addr->setText( tr(stmp) );
	}
	else
	{
		addr->clear();
	}

	if ( v >= 0 )
	{
		sprintf( stmp, "%02X", v );

		val->setText( tr(stmp) );
	}
	else
	{
		val->clear();
	}

	if ( c >= 0 )
	{
		sprintf( stmp, "%02X", c );

		cmp->setText( tr(stmp) );
	}
	else
	{
		cmp->clear();
	}
	
	ListGGAddresses();
}
//----------------------------------------------------------------------------
//The code in this function is a modified version
//of Chris Covell's work - I'd just like to point that out
void EncodeGG(char *str, int a, int v, int c)
{
	uint8 num[8];
	int i;
	
	a&=0x7fff;

	num[0]=(v&7)+((v>>4)&8);
	num[1]=((v>>4)&7)+((a>>4)&8);
	num[2]=((a>>4)&7);
	num[3]=(a>>12)+(a&8);
	num[4]=(a&7)+((a>>8)&8);
	num[5]=((a>>8)&7);

	if (c == -1){
		num[5]+=v&8;
		for(i = 0;i < 6;i++)str[i] = GameGenieLetters[num[i]];
		str[6] = 0;
	} else {
		num[2]+=8;
		num[5]+=c&8;
		num[6]=(c&7)+((c>>4)&8);
		num[7]=((c>>4)&7)+(v&8);
		for(i = 0;i < 8;i++)str[i] = GameGenieLetters[num[i]];
		str[8] = 0;
	}
	return;
}
//----------------------------------------------------------------------------
void GameGenieDialog_t::ListGGAddresses(void)
{
	int a = -1; int v = -1; int c = -1;
	QTreeWidgetItem *item;
	char str[32];
	bool addCheatEmable;

	if ( addr->text().size() > 0 )
	{
		a = strtol( addr->text().toStdString().c_str(), NULL, 16 );
	}
	if ( val->text().size() > 0 )
	{
		v = strtol( val->text().toStdString().c_str(), NULL, 16 );
	}
	if ( cmp->text().size() > 0 )
	{
		c = strtol( cmp->text().toStdString().c_str(), NULL, 16 );
	}
	// also enable/disable the add GG button here
	addCheatEmable = (a >= 0) && ( (ggCode->text().size() == 6) || (ggCode->text().size() == 8) );

	addCheatBtn->setEnabled( addCheatEmable );
	
	tree->clear();

	if (a != -1 && v != -1)
	{
		for (unsigned int i = 0; i < PRGsize[0]; i += 0x2000)
		{
			if (c == -1 || PRGptr[0][i + (a & 0x1FFF)] == c)
			{
				item = new QTreeWidgetItem();

				sprintf(str, "%06X", i + (a & 0x1FFF) + 0x10);

				//printf("Added ROM ADDR: %s\n", str );
				item->setText( 0, tr(str) );

				item->setTextAlignment( 0, Qt::AlignCenter);
				tree->addTopLevelItem( item );
			}
		}
	}
	tree->viewport()->update();
}
//----------------------------------------------------------------------------
