/**
 *  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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include "sIRC.h"
#include "commthread.h"
#include "settingswindow.h"
#include "settings.h"
#include "messages.h"
#include "chathelp.h"

//----------------------------------------------------------------------------------

// Here is where we construct the window e.g views, buttons, textviews, etc...
MainView::MainView( const os::Rect &r ):os::View( r, "MainView", os::CF_FOLLOW_ALL, os::WID_FULL_UPDATE_ON_RESIZE )
{
	//create the vertical root structure
	os::LayoutView*	pcView = new os::LayoutView( GetBounds(), "root_view" );
	os::LayoutNode*	pcRootNode = new os::VLayoutNode( "root_node" );

	//create main menu
	mainMenuBar = new os::Menu( os::Rect(), "mainMenuBar", os::ITEMS_IN_ROW );
	mainMenuBar->SetFrame( os::Rect( 0, 0, GetBounds().Width() + 1, mainMenuBar->GetPreferredSize( false ).y + 1 ) );

	// Application menu
	tempMenu = new os::Menu( os::Rect(), "Application", os::ITEMS_IN_COLUMN );
	tempMenu->AddItem( "Settings", new os::Message( M_MENU_SETTINGS ) );
	tempMenu->AddItem( new os::MenuSeparator() );
	tempMenu->AddItem( "Help", new os::Message( os::M_MENU_EVENT ) );
	tempMenu->AddItem( "About", new os::Message( os::M_ABOUT_REQUESTED ) );
	tempMenu->AddItem( "Quit", new os::Message( os::M_QUIT_REQUESTED ) );
	mainMenuBar->AddItem( tempMenu );
	// Server menu
	tempMenu = new os::Menu( os::Rect(), "Server", os::ITEMS_IN_COLUMN );
	tempMenu->AddItem( "Connect", new os::Message( M_MENU_START ) );
	tempMenu->AddItem( "Login", new os::Message( M_MENU_LOGIN ) );
	tempMenu->AddItem( "Disconnect", new os::Message( M_MENU_STOP ) );
	mainMenuBar->AddItem( tempMenu );
	// Channel menu
	tempMenu = new os::Menu( os::Rect(), "Channel", os::ITEMS_IN_COLUMN );
	tempMenu->AddItem( "Join Channel", new os::Message( M_MENU_JOINCHANNEL ) );
	tempMenu->AddItem( "Leave Channel", new os::Message( M_MENU_LEAVECHANNEL ) );
	mainMenuBar->AddItem( tempMenu );
	pcRootNode->AddChild( mainMenuBar );

	//create main node
	os::LayoutNode* pcMainHNode = new os::HLayoutNode( "main_node", 1 );
	pcMainHNode->SetBorders( os::Rect( 2, 0, 2, 0 ) );
	//create output box
	fOutputView = new os::TextView( os::Rect(), "output_view", "", os::CF_FOLLOW_ALL );
	fOutputView->SetMultiLine( true );
	fOutputView->SetReadOnly( true );
	pcMainHNode->AddChild( fOutputView, 1 );
	pcMainHNode->AddChild( new os::HLayoutSpacer( "output_user_spacer", 2, 2 ) );
	// create user box
	fUserView = new os::TextView( os::Rect(), "user_list", "" );
	fUserView->SetMultiLine( true );
	fUserView->SetReadOnly( true );
	pcMainHNode->AddChild( fUserView, 0.2 );
	pcRootNode->AddChild( pcMainHNode );

	//create input node
	os::LayoutNode *pcInputHNode = new os::HLayoutNode( "input_node", 0 );
	pcInputHNode->SetBorders( os::Rect( 2, 2, 2, 2 ) );
	//create input box
	fInputView = new os::TextView( os::Rect(), "input_view", "", os::CF_FOLLOW_ALL );
	pcInputHNode->AddChild( fInputView, 1 );
	pcRootNode->AddChild( pcInputHNode );

	//add it all to the layout structure
	pcView->SetRoot( pcRootNode );
	AddChild( pcView );
}

void MainView::AllAttached()
{
	mainMenuBar->SetTargetForItems( this );
	fInputView->SetEventMask( os::TextView::EI_ENTER_PRESSED | os::TextView::EI_ESC_PRESSED );
	fInputView->SetMessage( new os::Message( MSG_TOLOOPER_NEW_MESSAGE ) );
	fInputView->SetTarget( this );
	bInChannel = false;

	os::View::AllAttached();
}

void MainView::AttachedToWindow()
{
	//ensure messages are sent to the view and not the window
	os::View::AttachedToWindow();

	/* Load settings from the default file */
	m_pcSettings = new AppSettings();
	m_pcSettings->Load();

	/* create a protected commthread instance */
	CommThread *m_pCommThread;
	m_pCommThread = new CommThread( os::Messenger(this) );
	if( m_pCommThread == NULL )
	{
		fprintf( stderr, "Commthread: %s (%d)\n", strerror( errno ), errno );
		os::Application::GetInstance()->PostMessage( os::M_QUIT );
	}
	m_CommThread = m_pCommThread;
	m_CommThread->Run();
}

void MainView::FrameSized( const os::Point& cDelta )
{
	Invalidate( mainMenuBar->GetFrame(), true );

	os::View::FrameSized( cDelta );
}

void MainView::HandleMessage( os::Message *pcMessage )
{
	switch ( pcMessage->GetCode() )
	{
		case M_MENU_START:
		{
			if( !m_CommThread->IsConnected() )
			{
				os::String zHost, zPort, zUsername, zPassword, zRealname, zNickname, zChannel;
				m_pcSettings->LoadHistory( &zHost, &zPort, &zUsername, &zPassword, &zRealname, &zNickname, &zChannel );
				fOutputView->Clear();

				os::Message cMsg( MSG_TOLOOPER_START );
				m_CommThread->SetHost( zHost );
				m_CommThread->SetPort( zPort );
				m_CommThread->SetUsername( zUsername );
				m_CommThread->SetPassword( zPassword );
				m_CommThread->SetRealname( zRealname );
				m_CommThread->SetNickname( zNickname );
				m_CommThread->SetChannel( zChannel );
				m_CommThread->PostMessage( &cMsg, m_CommThread );
			}
			break;
		}
		case M_MENU_STOP:
		{
			if( m_CommThread->IsConnected() )
			{
				m_CommThread->Send( os::String().Format( "QUIT %s\r\n", ":-)" ) );
				sleep( 3 ); // wait for server exit message
				m_CommThread->PostMessage( MSG_TOLOOPER_STOP, m_CommThread );
			}
			break;
		}
		case MSG_TOLOOPER_NEW_MESSAGE:
		{
	    	uint32 nEvents = 0;
	    	pcMessage->FindInt( "events", &nEvents );
	    	if ( nEvents & os::TextView::EI_ESC_PRESSED )
			{
				fInputView->Clear();
				fInputView->MakeFocus();
	    	}
	    	else if ( nEvents & os::TextView::EI_ENTER_PRESSED )
			{
				if( bInChannel )
					m_CommThread->Send( os::String().Format( "PRIVMSG %s :%s\r\n", m_CommThread->GetChannel().c_str(), fInputView->GetBuffer()[0].c_str() ) );
				else
					m_CommThread->Send( os::String().Format( "%s\r\n", fInputView->GetBuffer()[0].c_str() ) );
				fInputView->Clear();
				fInputView->MakeFocus();
			}
			break;
		}
		case MSG_FROMLOOPER_NEW_MESSAGE:
		{
			const char *pzName;
			int nCount = 0;
			pcMessage->GetNameInfo( "name", NULL, &nCount );
			for( int i = 0; i < nCount ; ++i )
			{
				if( pcMessage->FindString( "name", &pzName, i ) == 0 )
				{
					Update( os::String( pzName ) );
					PopulateUserList( pzName );
					break;
				}
			}
			break;
		}
		case M_MENU_LOGIN:
		{
			m_CommThread->Send( os::String().Format( "PRIVMSG NickServ :IDENTIFY %s\r\n", m_CommThread->GetPassword().c_str() ) );
			break;
		}
		case M_MENU_JOINCHANNEL:
		{
			if( m_CommThread->IsConnected() )
			{
				if( !bInChannel )
				{
					m_CommThread->Send( os::String().Format( "JOIN %s\r\n", m_CommThread->GetChannel().c_str() ) );
					bInChannel = true;
				}
			}
			break;
		}
		case M_MENU_LEAVECHANNEL:
		{
			if( m_CommThread->IsConnected() )
			{
				if( bInChannel )
				{
					bInChannel = false;
					fUserView->Clear();
					m_CommThread->Send( os::String().Format( "PART %s\r\n", m_CommThread->GetChannel().c_str() ) );
				}
			}
			break;
		}
		case M_MENU_SETTINGS:
		{
			// Get screen width and height to centre the window
			os::Desktop *pcDesktop = new os::Desktop();
			int iWidth = pcDesktop->GetScreenMode().m_nWidth;
			int iHeight = pcDesktop->GetScreenMode().m_nHeight;
			int iLeft = (iWidth-450)/2;
			int iTop = (iHeight-290)/2;

			// Show settings window
			SettingsWindow *pcWindow = new SettingsWindow( os::Rect( iLeft, iTop, iLeft+450, iTop+170 ), m_pcSettings );
			pcWindow->Show();
			pcWindow->MakeFocus();
			break;
		}
		case os::M_MENU_EVENT:
		case os::M_ABOUT_REQUESTED:
		case os::M_QUIT_REQUESTED:
		{
			os::Application::GetInstance()->PostMessage( pcMessage, os::Application::GetInstance() );
			break;
		}
		default:
		{
			os::View::HandleMessage( pcMessage );
			break;
		}
	}
}

//this parser will only work on a single line of server stream
void MainView::PopulateUserList( const char *pzName )
{
	std::string data( pzName );

	// erase \r and \n
	data.erase( data.find_last_not_of( "\r\n" ) + 1 );
	if( data[0] == ':' )
	{
		// Find prefix
		std::string::size_type pos1 = data.find_first_of( " ", 1 );
		// Find command
		std::string::size_type pos2 = data.find_first_of( " ", pos1 + 1 );
		// Check whether there is any params
		std::string::size_type pos3 = data.find_first_of( ":", pos2 + 1 );
		if( atoi( data.substr( pos1 + 1, ( pos2 - 1 ) - pos1 ).c_str() ) == 353 )
		{
			// get whats after the ':', if there were any ':'
			if( pos3 != std::string::npos )
			{
				std::stringstream ss( data.substr( pos3 + 1 ) );
				std::string buf;
				// overload ss data into buf
				while( ss >> buf )
				{
					std::vector < std::string > vec;
					if( buf[0] == '@' || buf[0] == '+' )
					{
						vec.push_back( buf.substr( 0, 1 ) );
						vec.push_back( buf.substr( 1 ) );
					}
					else
					{
						vec.push_back( " " );
						vec.push_back( buf );
					}
					fUserView->Insert( os::String().Format( "%s\n", buf.c_str() ).c_str() );
				}
			}
		}
	}
}

void MainView::Update( const os::String cBufString )
{
	os::String cTempString( cBufString );

	if( bInChannel )
	{
		const os::String find1 = "!";
		const std::string::size_type pos1 = cBufString.find( find1, 0 );
		const os::String find2 = "#";
		const std::string::size_type pos2 = cBufString.find( find2, 0 );

		// cut between pos1 '!' and pos2 '#'
		if( pos1 != std::string::npos && pos2 != std::string::npos )
			cTempString = cTempString.erase( pos1, pos2 - pos1 - 1 );
	}

	AddStringToTextView( cTempString );
}

void MainView::AddStringToTextView( const os::String &cName ) const
{
	// add new text at the end of the textview buffer
	const os::TextView::buffer_type cBuffer = fOutputView->GetBuffer();
	const int posX = cBuffer[cBuffer.size()-1].size();
	const int posY = cBuffer.size()-1;
	const os::IPoint cPoint = os::IPoint( posX, posY );
	fOutputView->Insert( cPoint, cName.c_str() );
}

//----------------------------------------------------------------------------------

MainWindow::MainWindow( const os::Rect &r ):os::Window( r, "MainWindow", "sIRC:- Syllable Internet Relay Chat Client", 0, os::CURRENT_DESKTOP )
{
	/* Set up "view" as the main view for the window, filling up the entire */
	/* window (thus the call to GetBounds()). */
	view = new MainView( GetBounds() );
	AddChild( view );
}

void MainWindow::HandleMessage( os::Message *pcMessage )
{
	switch ( pcMessage->GetCode() )
	{
		case os::M_MENU_EVENT:
		{
			ShowHelp();
			break;
		}
		case os::M_ABOUT_REQUESTED:
		{
			ShowAbout();
			break;
		}
		case os::M_QUIT_REQUESTED:
		{
			OkToQuit();
			break;
		}
		default:
		{
			os::Window::HandleMessage( pcMessage );
			break;
		}
	}
}

void MainWindow::ShowHelp( void )
{
	( new os::Alert( "Help", ChatHelp(), os::Alert::ALERT_TIP, 0x00, "Right", NULL ) )->Go();
}

void MainWindow::ShowAbout( void )
{
retry:
	os::String cAbout = "sIRC 0.33\n";
	cAbout += "Syllable Internet Relay Chat Client\n";
	cAbout += os::String("Build: " ) + os::DateTime::Now().GetDate() + os::String( "\n\n" );
	cAbout += "sIRC 0.03 Copyright © James Coxon 2006\n";
	cAbout += "sIRC 0.33 Copyright © David Kent 2023\n\n";
	cAbout += "sIRC is released under the GNU General Public License.\n";
	cAbout += "Please see the file COPYING, distributed with sIRC,\n";
	cAbout += "or http://www.gnu.org for more information.";

	os::Alert *sAbout = new os::Alert( "About sIRC...", cAbout, os::Alert::ALERT_INFO, 0x00, "Credits", "License", "Close", NULL );
	sAbout->CenterInWindow( this );

	int32 reply = sAbout->Go();
	switch ( reply )
	{
		// the first button 'Credits'
		case 0x00:
		{
			os::Alert *sCredits = new os::Alert( "Credits",
				"James Coxon   -   For his original concept and gui layout\n"
				"    [http://www.srcf.ucam.org/~jac208/sIRC.tar] (discontinued)\n"
				"    [http://allatheos.free.fr/softs/sources/sIRC.tar] (discontinued)\n"
				"    [https://web.archive.org/web/20081213015820/http://allatheos.free.fr/softs/sources/sIRC.tar]\n"
				"    or else docs/index.html\n\n"
				"Beej's Guide to Network Programming. Version 1.5.5 (13-Jan-1999)\n"
				"    [http://www.ecst.csuchico.edu/~beej/guide/net/] (discontinued)\n"
				"    [https://web.archive.org/web/20051210035802/http://beej.us/guide/bgnet/output/html/index.html]\n\n"
				"David Kent   -   For bug fixs and code upgrades",
				0x00, "Close", NULL );
				sCredits->CenterInWindow( this );
				sCredits->Go();
				goto retry;
		}
		// the second button 'License'
		case 0x01:
		{
			os::Alert *sLicense = new os::Alert( "License",
				"This program is free software; you can redistribute it and/or modify it\n"
				"under the terms of the GNU General Public Licence as published by the\n"
				"Free Software Foundation; either version 2 of the License, or (at your\n"
				"option) any later version.\n\n"
				"This program is distributed in the hope that it will be useful, but\n"
				"WITHOUT ANY WARRANTY: without even the implied warranty of\n"
				"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
				"GNU General Public Licence for more details.\n\n"
				"You should have received a copy of the GNU General Public Licence\n"
				"along with this program; if not, write to the Free Software Foundation,\n"
				"Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA",
				0x00, "Close", NULL );
				sLicense->CenterInWindow( this );
				sLicense->Go();
				goto retry;
		}
		// the third button 'Close'
		case 0x02: break;
	}
}

bool MainWindow::OkToQuit( void )
{
	os::Application::GetInstance()->PostMessage( os::M_QUIT );
	return ( true );
}

//----------------------------------------------------------------------------------

MyApplication::MyApplication( void ):os::Application( "application/x-vnd.Syllable-sIRC" )
{
	// Remember it goes "x" then "y"
	myMainWindow = new MainWindow( os::Rect( 20, 20, 600, 400 ) );
	myMainWindow->Show();
	myMainWindow->MakeFocus();
}

void MyApplication::HandleMessage( os::Message * pcMessage )
{
	// bounce message to the window
	myMainWindow->PostMessage( pcMessage, myMainWindow );
	return ( os::Application::HandleMessage( pcMessage ) );
}

//----------------------------------------------------------------------------------

int main( void )
{
	MyApplication *thisApp;
	thisApp = new MyApplication();
	thisApp->Run();
	return ( 0 );
}