/*  ColdFish Music Player
 *  Copyright (C) 2003 Kristian Van Der Vliet
 *  Copyright (C) 2003 Arno Klenke
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of version 2 of the GNU Library
 *  General Public License as published by the Free Software
 *  Foundation.
 *
 *  This library 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
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 *  MA 02111-1307, USA
 */

#include <iostream>
#include <fstream>
#include <queue>

#include <util/settings.h>
#include <atheos/threads.h>
#include <media/packet.h>
#include <storage/path.h>

#include "CFApp.h"
#include "CFWindow.h"
#include "SelectWin.h"
#include "messages.h"

#define DEBUG 0

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

static inline void secs_to_ms( uint64 nTime, uint32 *nM, uint32 *nS )
{
	if ( nTime > 59 )
	{
		*nM = ( nTime / 60 );
		*nS = nTime - ( *nM * 60 );
	}
	else
	{
		*nM = 0;
		*nS = (uint32)nTime;
	}
}

int32 play_thread_entry( void *pData )
{
	CFApp *pcApp = (CFApp*)pData;
	pcApp->PlayThread();
	return ( 0 );
}

int32 play_next_entry( void *pData )
{
	CFApp *pcApp = (CFApp*)pData;
	pcApp->PlayNext();
	return ( 0 );
}

int32 play_prev_entry( void *pData)
{
	CFApp *pcApp = (CFApp*)pData;
	pcApp->PlayPrevious();
	return ( 0 );
}

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

CFApp::CFApp( ):os::Looper( "cf_app" )
{
	/* Set default values */
	m_nState = CF_STATE_STOPPED;
	m_zListName = os::String ( getenv( "HOME" ) ) + "/Default Playlist.plst";
	m_pcInput = NULL;
	m_bLockedInput = false;
	m_zAudioFile = "";
	m_nAudioTrack = 0;
	m_nAudioStream = 0;
	m_pcAudioCodec = NULL;
	m_pcAudioOutput = NULL;
	m_bPlayThread = false;
	m_bListShown = true;
	m_zTrackName = "Unknown";
	m_pcInputSelector = NULL;
	m_nLastPosition = 0;
	m_zAudioName = "";
	m_bPacket = false;
	m_bStream = false;
	m_hPlayThread = 0;
}

void CFApp::Start( os::String zFileName, bool bLoad )
{
	SetPublic(true);

	/* Load settings */
	os::Settings * pcSettings = new os::Settings();
	if ( pcSettings->Load() == 0 )
	{
		m_zListName = pcSettings->GetString( "playlist", m_zListName.c_str() );
	}	
	delete( pcSettings );

	/* Create registrar manager */
	m_pcRegManager = NULL;
	try
	{
		/* Register playlist type */
		m_pcRegManager = os::RegistrarManager::Get();
		m_pcRegManager->RegisterType( "application/x-coldfish-playlist", "ColdFish Playlist" );
		m_pcRegManager->RegisterTypeIconFromRes( "application/x-coldfish-playlist", "application_coldfish_playlist.png" );
		m_pcRegManager->RegisterTypeExtension( "application/x-coldfish-playlist", "plst" );
		m_pcRegManager->RegisterAsTypeHandler( "application/x-coldfish-playlist" );
	}
	catch(...)
	{
	}
	/* Create media manager */
	m_pcManager = os::MediaManager::Get();
	if ( !m_pcManager->IsValid() )
	{
		if (DEBUG)
			std::cout << "Media server is not running" << std::endl;
		PostMessage( os::M_QUIT );
		return;
	}
	/* Create window */
	m_pcWin = new CFWindow( os::Rect( 0, 0, 500, 350 ), "cf_window", "Default Playlist.plst - ColdFish",0);
	m_pcWin->CenterInScreen();
	m_pcWin->Show();
	m_pcWin->MakeFocus( true );
	/* Open list */
	if ( bLoad )
	{
		if ( !OpenList( zFileName ) )
			OpenList( m_zListName );
	}
	else
	{
		if ( !OpenList( m_zListName ) )
		{
			OpenList( os::String ( getenv( "HOME" ) ) + "/Default Playlist.plst" );
		}
	}	
}

CFApp::~CFApp()
{	
	/* Close and delete everything */
	if (m_pcRegManager)
	{
		m_pcRegManager->Put();
	}
	CloseCurrentFile();
	m_pcWin->Terminate();
	if ( m_pcManager->IsValid() )
	{
		m_pcManager->Put();
	}
}

CFWindow* CFApp::GetWindow()
{
	return( m_pcWin );
}

/* Thread which is responsible to play the file */
void CFApp::PlayThread()
{
	bigtime_t nTime = get_system_time();
	m_bPlayThread = true;

	os::MediaPacket_s sPacket;
	os::MediaPacket_s sCurrentAudioPacket;
	std::queue cAudioPackets;
	uint64 nAudioBytes = 0;
	uint8 nErrorCount = 0;
	bool bError = false;

	if (DEBUG)
		std::cout << "Play thread running" << std::endl;

	/* Seek to last position */
	if ( !m_bStream )
		m_pcInput->Seek( m_nLastPosition );

	/* Create audio output packet */
	if ( m_bPacket )
	{
		m_pcAudioCodec->CreateAudioOutputPacket( &sCurrentAudioPacket );
	}
	while ( m_bPlayThread )
	{
		if ( m_bPacket )
		{
		    if ( m_pcAudioOutput->GetDelay() < m_pcAudioOutput->GetBufferSize() )
			{
				/* Grab data */
				if ( m_pcInput->ReadPacket( &sPacket ) == 0 )
				{
					nErrorCount = 0;
					/* Decode audio data */
					if ( sPacket.nStream == m_nAudioStream )
					{
						if ( m_pcAudioCodec->DecodePacket( &sPacket, &sCurrentAudioPacket ) == 0 )
						{
							if ( sCurrentAudioPacket.nSize[0] > 0 )
							{
								sCurrentAudioPacket.nTimeStamp = ~0;
								m_pcAudioOutput->WritePacket( 0, &sCurrentAudioPacket );
								nAudioBytes += sCurrentAudioPacket.nSize[0];
								/* Put the packet in the queue and allocate a new one */
								if( cAudioPackets.size() < 20 ) // FIX: after short time app hangs, test cAudioPackets size
								{
									cAudioPackets.push( sCurrentAudioPacket );
									m_pcAudioCodec->CreateAudioOutputPacket( &sCurrentAudioPacket );
								}
							}
						}
					}
					m_pcInput->FreePacket( &sPacket );
				}
				else
				{
					/* Increase error count */
					nErrorCount++;
					if ( m_pcAudioOutput->GetDelay( true ) > 0 ) // FIX: end of track hang bug, from GetDelay() to GetDelay( true )
						nErrorCount--;
					if ( nErrorCount > 10 && !bError )
					{
						os::Application::GetInstance()->PostMessage( CF_PLAY_NEXT, os::Application::GetInstance() );
						bError = true;
					}
				}
			}
		}
		snooze( 1000 );

		if ( !m_bStream && get_system_time() > nTime + 1000000 )
		{
			/* Move slider */
			m_pcWin->GetLCD()->SetValue( os::Variant( ( int )( m_pcInput->GetCurrentPosition() * 1000 / m_pcInput->GetLength() ) ), false );
			m_pcWin->GetLCD()->UpdateTime( m_pcInput->GetCurrentPosition() );
			//cout<<"Position "<GetCurrentPosition()<GetCurrentPosition() )
			{
				nErrorCount++;
				if ( nErrorCount > 2 && !bError )
				{
					os::Application::GetInstance()->PostMessage( CF_PLAY_NEXT, os::Application::GetInstance() );
					bError = true;
				}
			}
			else
				nErrorCount = 0;
			m_nLastPosition = m_pcInput->GetCurrentPosition();
		}
	}
	/* Stop tread */
	if (DEBUG)
		std::cout << "Stop thread" << std::endl;
	if ( !m_bPacket )
	{
		m_pcInput->StopTrack();
	}
	if ( m_bPacket )
	{
		m_pcAudioOutput->Clear();
		/* Clear packets */
		while( !cAudioPackets.empty() )
		{
			os::MediaPacket_s sPacket = cAudioPackets.front();
			m_pcAudioCodec->DeleteAudioOutputPacket( &sPacket );
			cAudioPackets.pop();
		}
		m_pcAudioCodec->DeleteAudioOutputPacket( &sCurrentAudioPacket );
	}
}

/* Open one playlist */
bool CFApp::OpenList( os::String zFileName )
{
	char zTemp[PATH_MAX];
	char zTemp2[PATH_MAX];
	m_bLockedInput = false;
	m_pcWin->GetPlaylist()->Clear();
	m_pcWin->GetLCD()->SetValue( os::Variant( 0 ) );
	m_pcWin->GetLCD()->UpdateTime( 0 );
	m_pcWin->GetLCD()->SetTrackName( "Unknown" );
	m_pcWin->GetLCD()->SetTrackNumber( 0 );

	std::ifstream hIn;
	hIn.open( zFileName.c_str() );
	if ( !hIn.is_open() )
	{
		if (DEBUG)
			std::cout << "Could not open playlist!" << std::endl;
		return ( false );
	}
	/* Read header */
	if ( hIn.getline( zTemp, 255, '\n' ) < 0 )
		return ( false );
	if ( strcmp( zTemp, "" ) )
	{
		if (DEBUG)
			std::cout << "Invalid playlist!" << std::endl;
		return ( false );
	}

	/* Read entries */
	bool bInEntry = false;
	os::String zFile;
	int nTrack = 0;
	int nStream = 0;
	m_pcWin->Lock();
	m_pcWin->SetTitle( "Loading... - ColdFish" );
	m_pcWin->Unlock();

	while ( !hIn.eof() )
	{
		if ( hIn.getline( zTemp, 255, '\n' ) < 0 )
			return ( false );
		if ( !strcmp( zTemp, "" ) )
		{
			goto finished;
		}
		if ( !strcmp( zTemp, "" ) )
		{
			bInEntry = true;
			zFile = "";
			nTrack = 0;
			nStream = 0;
			continue;
		}
		if ( !strcmp( zTemp, "" ) )
		{
			uint32 nM = 0, nS = 0;
			int64 nLength = 0;
			bInEntry = false;
			/* Try to read the length attribute */
			bool bReadLengthFromInput = true;
			try
			{
				os::FSNode cNode( zFile );
				if( cNode.ReadAttr( "Media::Length", ATTR_TYPE_INT64, &nLength, 0, sizeof( int64 ) ) == sizeof( int64 ) )
				{
					bReadLengthFromInput = false;
					secs_to_ms( nLength, &nM, &nS );
				} 
				cNode.Unset();
			} catch(...)
			{
				bReadLengthFromInput = true;
			} 
			/* Read length from input if required */
			if( bReadLengthFromInput )
			{
				os::MediaInput * pcInput = m_pcManager->GetBestInput( zFile );
				if ( pcInput == NULL )
					continue;
				pcInput->SelectTrack( nTrack );
				nLength = pcInput->GetLength();
				secs_to_ms( pcInput->GetLength(), &nM, &nS );
				try {
					os::FSNode cNode( zFile );
					cNode.WriteAttr( "Media::Length", O_TRUNC, ATTR_TYPE_INT64, &nLength, 0, sizeof( int64 ) );
					cNode.Unset();
				} catch( ... ) { }
				pcInput->Release();
			}
			/* Add new row */
			CFListItem * pcRow = new CFListItem();
			pcRow->AppendString( os::Path( zFile ).GetLeaf() );
			pcRow->zPath = zFile;
			sprintf( zTemp, "%i", ( int )nTrack + 1 );
			pcRow->AppendString( zTemp );
			pcRow->nTrack = nTrack;
			pcRow->nStream = nStream;
			sprintf( zTemp, "%.2li:%.2li", (long int)nM, (long int)nS );
			pcRow->AppendString( zTemp );
			m_pcWin->Lock();
			m_pcWin->GetPlaylist()->InsertRow( pcRow );
			if ( m_pcWin->GetPlaylist()->GetRowCount() == 1 )
				m_pcWin->GetPlaylist()->Select( 0, 0 );
			m_pcWin->Unlock();	
			continue;
		}
		/* We expect a second line with data */
		if ( hIn.getline( zTemp2, 255, '\n' ) < 0 )
			return ( false );
		if ( !strcmp( zTemp, "" ) )
		{
			zFile = zTemp2;
		}
		else if ( !strcmp( zTemp, "" ) )
			nTrack = atoi( zTemp2 ) - 1;
		else if ( !strcmp( zTemp, "" ) )
			nStream = atoi( zTemp2 ) - 1;
	}
	m_pcWin->Lock();
	m_pcWin->SetTitle( "ColdFish" );
	m_pcWin->Unlock();
	return ( false );

finished:
	hIn.close();
	m_zListName = zFileName;

	/* Set title */
	os::Path cPath( zFileName.c_str() );
	m_pcWin->Lock();
	m_pcWin->SetTitle( os::String ( cPath.GetLeaf() ) + " - ColdFish" );
	m_pcWin->Unlock();

	if (DEBUG)
		std::cout << "List openened" << std::endl;
	return ( true );

}

/* Save the opened playlist */
void CFApp::SaveList()
{
	uint32 i;

	if( m_bLockedInput || m_zListName == "" )
		return;

	/* Open output file */
	std::ofstream hOut;
	hOut.open( m_zListName.c_str() );
	if ( !hOut.is_open() )
	{
		std::cout << "Could not save playlist!" << std::endl;
		return;
	}

	/* Save files */
	hOut << "" << std::endl;
	for ( i = 0; i < m_pcWin->GetPlaylist()->GetRowCount(); i++ )
	{
		char zTemp[100];
		hOut << "" << std::endl;
		CFListItem * pcRow = ( CFListItem * ) m_pcWin->GetPlaylist()->GetRow( i );
		hOut << "" << std::endl;
		hOut << pcRow->zPath.c_str() << std::endl;
		hOut << "" << std::endl;
		sprintf( zTemp, "%i", pcRow->nTrack + 1 );
		hOut << zTemp << std::endl;
		hOut << "" << std::endl;
		sprintf( zTemp, "%i", pcRow->nStream + 1 );
		hOut << zTemp << std::endl;
		hOut << "" << std::endl;
	}
	hOut << "" << std::endl;
	hOut.close();
}

/* Open an input */
void CFApp::OpenInput( os::String zFileName, os::String zInput )
{
	uint32 i = 0;
	char zTemp[255];
	m_bLockedInput = true;
	m_pcWin->GetPlaylist()->Clear();
	m_pcWin->GetLCD()->SetValue( os::Variant( 0 ) );
	m_pcWin->GetLCD()->UpdateTime( 0 );
	m_pcWin->GetLCD()->SetTrackName( "Unknown" );
	m_pcWin->GetLCD()->SetTrackNumber( 0 );
	m_zListName = zInput;
	
	if( zFileName.empty() )
		zFileName = zInput;
	
	if (DEBUG)
		std::cout << "Open input " << zInput.c_str() << std::endl;
	
	while ( ( m_pcInput = m_pcManager->GetInput( i ) ) != NULL )
	{
		if ( m_pcInput->GetIdentifier() == zInput )
		{
			break;
		}
		m_pcInput->Release();
		m_pcInput = NULL;
		i++;
	}
	
	if ( m_pcInput == NULL )
		goto invalid;

	if ( m_pcInput->Open( zFileName ) != 0 )
		goto invalid;

	if ( m_pcInput->GetTrackCount() < 1 )
		goto invalid;

	/* Packet based ? */
	if ( !m_pcInput->PacketBased() )
		goto invalid;
		
	/* Search tracks with audio streams and add them to the list */
	for ( i = 0; i < m_pcInput->GetTrackCount(); i++ )
	{
		m_pcInput->SelectTrack( i );

		for ( uint32 j = 0; j < m_pcInput->GetStreamCount(); j++ )
		{
			if ( m_pcInput->GetStreamFormat( j ).nType == os::MEDIA_TYPE_AUDIO )
			{
				uint32 nM, nS;

				/* Found something -> add it */
				CFListItem * pcRow = new CFListItem();
				pcRow->AppendString( os::Path( zFileName ).GetLeaf() );
				pcRow->zPath = zFileName;
				sprintf( zTemp, "%i", ( int )i + 1 );
				pcRow->AppendString( zTemp );
				pcRow->nTrack = i;
				pcRow->nStream = j;
				secs_to_ms( m_pcInput->GetLength(), &nM, &nS );
				sprintf( zTemp, "%.2li:%.2li", (long int)nM, (long int)nS );
				pcRow->AppendString( zTemp );
				m_pcWin->Lock();
				m_pcWin->GetPlaylist()->InsertRow( pcRow );

				if ( m_pcWin->GetPlaylist()->GetRowCount() == 1 )
					m_pcWin->GetPlaylist()->Select( 0, 0 );
				m_pcWin->Unlock();
			}
		}
	}
	
	m_pcWin->Lock();
	m_pcWin->SetTitle( m_zListName + " - ColdFish" );
	m_pcWin->Unlock();
	return;

invalid:
	if ( m_pcInput )
	{
		m_pcInput->Close();
		m_pcInput->Release();
		m_pcInput = NULL;
	}
	os::Alert * pcAlert = new os::Alert( "ColdFish Error", "ColdFish cannot play this file.", os::Alert::ALERT_WARNING, 0, "OK", NULL );
	pcAlert->Go( new os::Invoker( 0 ) );
}

/* Check if this is a valid file */
void CFApp::AddFile( os::String zFile )
{
	os::FSNode fileNode(zFile);
	if(fileNode.IsFile())
	{
		/* Add file as normal */
		AddFileToPlaylist( zFile );
		return;
	}

	if(fileNode.IsDir())
	{
		os::Directory dirEntry(zFile);
		os::String lastFile, nextFile;
		dirEntry.GetNextEntry(&nextFile);
		while(lastFile != nextFile)
		{
			lastFile = nextFile;
			if(nextFile != "." && nextFile != "..")
			{
				os::String finalString = zFile + "/" + nextFile;
				AddFileToPlaylist( finalString );
			}
			dirEntry.GetNextEntry(&nextFile);
		}
	}
}

void CFApp::AddFileToPlaylist( os::String zFileName )
{
	uint32 i;
	char zTemp[255];

	if(DEBUG)
		std::cout << "Add File " << zFileName.c_str() << std::endl;

	os::MediaInput * pcInput = m_pcManager->GetBestInput( zFileName );
	if ( pcInput == NULL )
		goto invalid;

	if ( pcInput->Open( zFileName ) != 0 )
		goto invalid;

	if ( pcInput->GetTrackCount() < 1 )
		goto invalid;

	/* Packet based ? */
	if ( !pcInput->PacketBased() )
		goto invalid;

	/* Search tracks with audio streams and add them to the list */
	for ( i = 0; i < pcInput->GetTrackCount(); i++ )
	{
		pcInput->SelectTrack( i );

		for ( uint32 j = 0; j < pcInput->GetStreamCount(); j++ )
		{
			if ( pcInput->GetStreamFormat( j ).nType == os::MEDIA_TYPE_AUDIO )
			{
				uint32 nM, nS;

				/* Found something -> add it */
				CFListItem * pcRow = new CFListItem();
				pcRow->AppendString( os::Path( zFileName ).GetLeaf() );
				pcRow->zPath = zFileName;
				sprintf( zTemp, "%i", ( int )i + 1 );
				pcRow->AppendString( zTemp );
				pcRow->nTrack = i;
				pcRow->nStream = j;
				secs_to_ms( pcInput->GetLength(), &nM, &nS );
				sprintf( zTemp, "%.2li:%.2li", (long int)nM, (long int)nS );
				pcRow->AppendString( zTemp );
				m_pcWin->Lock();
				m_pcWin->GetPlaylist()->InsertRow( pcRow );

				if ( m_pcWin->GetPlaylist()->GetRowCount() == 1 )
					m_pcWin->GetPlaylist()->Select( 0, 0 );
				m_pcWin->Unlock();
			}
		}
	}
	
	if( pcInput->GetTrackCount() == 1 )
	{
		try {
			os::FSNode cNode( zFileName );
			uint64 nLength = pcInput->GetLength();
			cNode.WriteAttr( "Media::Length", O_TRUNC, ATTR_TYPE_INT64, &nLength, 0, sizeof( int64 ) );
			cNode.Unset();
		} catch( ... ) { }
	}
	
	pcInput->Close();
	pcInput->Release();
	SaveList();
	return;

invalid:
	if ( pcInput )
	{
		pcInput->Close();
		pcInput->Release();
	}
	os::Alert * pcAlert = new os::Alert( "ColdFish Error", "ColdFish cannot play this file.", os::Alert::ALERT_WARNING, 0, "OK", NULL );
	pcAlert->Go( new os::Invoker( 0 ) );
}

/* Play one track of a file / device */
int CFApp::OpenFile( os::String zFileName, uint32 nTrack, uint32 nStream )
{
	/* Close */
	CloseCurrentFile();
	uint32 i = 0;

	if( !m_bLockedInput )
	{
		m_pcInput = m_pcManager->GetBestInput( zFileName );
		if ( m_pcInput == NULL )
		{
			if (DEBUG)
				std::cout << "Cannot get input!" << std::endl;
			return ( -1 );
		}
		/* Open input */
		if ( m_pcInput->Open( zFileName ) != 0 )
		{
			if (DEBUG)
				std::cout << "Cannot open input!" << std::endl;
			return ( -1 );
		}
	} else {
		if ( m_pcInput == NULL )
		{
			if (DEBUG)
				std::cout << "Cannot get input!" << std::endl;
			return ( -1 );
		}
	}

	m_bPacket = m_pcInput->PacketBased();
	m_bStream = m_pcInput->StreamBased();

	/* Look if this is a packet based file / device */
	if ( !m_bPacket )
	{
		if (DEBUG)
			std::cout << "Not packet based!" << std::endl;
		CloseCurrentFile();
		return ( -1 );
	}

	/* Select track */
	if ( m_pcInput->SelectTrack( nTrack ) != nTrack )
	{
		if (DEBUG)
			std::cout << "Track selection failed!" << std::endl;
		CloseCurrentFile();
		return ( -1 );
	}

	/* Look if the stream is valid */
	if ( nStream >= m_pcInput->GetStreamCount() )
	{
		if (DEBUG)
			std::cout << "Invalid stream number!" << std::endl;
		CloseCurrentFile();
		return ( -1 );
	}

	/* Check stream */
	if ( m_pcInput->GetStreamFormat( nStream ).nType != os::MEDIA_TYPE_AUDIO )
	{
		if(DEBUG)
			std::cout << "Invalid stream format!" << std::endl;
		CloseCurrentFile();
		return ( -1 );
	}

	m_sAudioFormat = m_pcInput->GetStreamFormat( nStream );

	/* Open audio output */
	m_pcAudioOutput = m_pcManager->GetDefaultAudioOutput();
	if ( m_pcAudioOutput == NULL || ( m_pcAudioOutput && m_pcAudioOutput->FileNameRequired() ) || ( m_pcAudioOutput && m_pcAudioOutput->Open( "" ) != 0 ) )
	{
		if (DEBUG)
			std::cout << "Cannot open audio output!" << std::endl;
		CloseCurrentFile();
		return ( -1 );
	}

	/* Connect audio output with the codec */
	for ( i = 0; i < m_pcAudioOutput->GetOutputFormatCount(); i++ )
	{
		if ( ( m_pcAudioCodec = m_pcManager->GetBestCodec( m_sAudioFormat, m_pcAudioOutput->GetOutputFormat( i ), false ) ) != NULL )
			if ( m_pcAudioCodec->Open( m_sAudioFormat, m_pcAudioOutput->GetOutputFormat( i ), false ) == 0 )
				break;
			else
			{
				m_pcAudioCodec->Release();
				m_pcAudioCodec = NULL;
			}
	}
	if ( m_pcAudioCodec == NULL || m_pcAudioOutput->AddStream( os::String ( os::Path( zFileName.c_str() ).GetLeaf() ), m_pcAudioCodec->GetExternalFormat() ) != 0 )
	{
		if(DEBUG)
			std::cout << "Cannot open audio codec!" << std::endl;
		CloseCurrentFile();
		return ( -1 );
	}
	else
	{
		if(DEBUG)
			std::cout << "Using Audio codec " << m_pcAudioCodec->GetIdentifier().c_str() << std::endl;
	}

	/* Construct name */
	os::Path cPath = os::Path( zFileName.c_str() );

	/* Set title */
	if ( m_pcInput->FileNameRequired() )
		m_zAudioName = cPath.GetLeaf();
	else
		m_zAudioName = m_pcInput->GetIdentifier();

	m_pcWin->SetTitle( os::String ( os::Path( m_zListName.c_str() ).GetLeaf() ) + " - ColdFish (Playing " + m_zAudioName + ")" );

	/* Save information */
	m_nAudioTrack = nTrack;
	m_nAudioStream = nStream;
	m_zAudioFile = zFileName;

	/* Set LCD */
	m_pcWin->GetLCD()->SetTrackName( cPath.GetLeaf() );
	m_pcWin->GetLCD()->SetTrackNumber( m_nAudioTrack + 1 );
	m_pcWin->GetLCD()->UpdateTime( 0 );
	m_pcWin->GetLCD()->SetValue( os::Variant( 0 ) );

	m_zTrackName = cPath.GetLeaf();
	return ( 0 );
}

/* Close the currently opened file */
void CFApp::CloseCurrentFile()
{
	/* Stop thread */
	if ( m_bPlayThread )
	{
		m_bPlayThread = false;
		wait_for_thread( m_hPlayThread );
	}
	if ( m_pcAudioOutput )
	{
		m_pcAudioOutput->Close();
		m_pcAudioOutput->Release();
		m_pcAudioOutput = NULL;
	}
	if ( m_pcAudioCodec )
	{
		m_pcAudioCodec->Close();
		m_pcAudioCodec->Release();
		m_pcAudioCodec = NULL;
	}
	if ( m_pcInput && !m_bLockedInput )
	{
		m_pcInput->Close();
		m_pcInput->Release();
		m_pcInput = NULL;
	}

	m_pcWin->SetTitle( os::String ( os::Path( m_zListName.c_str() ).GetLeaf() ) + " - ColdFish" );
	m_zAudioFile = "";
}

/* Set the current state */
void CFApp::SetState( uint8 nState )
{
	m_nState = nState;
	m_pcWin->SetState( nState );
	m_pcWin->PostMessage( CF_STATE_CHANGED, m_pcWin );
}

/* Switch to the next track ( called as a thread to avoid sound errors ) */
void CFApp::PlayNext()
{
	uint nSelected = m_pcWin->GetPlaylist()->GetFirstSelected();
	nSelected++;
	if ( nSelected >= m_pcWin->GetPlaylist()->GetRowCount() )
		nSelected = 0;
	m_pcWin->GetPlaylist()->Select( nSelected );
	PostMessage( CF_GUI_PLAY );
}

void CFApp::PlayPrevious()
{
	int nSelected = m_pcWin->GetPlaylist()->GetFirstSelected();
	if ( nSelected <= 0 )
		nSelected = 0;
	else
		nSelected--;
	m_pcWin->GetPlaylist()->Select( nSelected );
	PostMessage( CF_GUI_PLAY );
}

void CFApp::HandleMessage( os::Message * pcMessage )
{
	switch ( pcMessage->GetCode() )
	{
		case CF_APP_STARTED:
		/* Sent by the Application class when started */
		{
			os::String zFileName;
			bool bLoad = false;
			if( pcMessage->FindString( "file/path", &zFileName ) == 0 )
			{
				bLoad = true;
			}
			Start( zFileName, bLoad );
			break;
		}
		case CF_GUI_PLAY:
		/* Play  ( sent by the CFWindow class ) */
		{
			if ( m_nState == CF_STATE_STOPPED )
			{
				/* Get Selection */
				if ( m_pcWin->GetPlaylist()->GetRowCount() == 0 )
				{
					break;
				}
				/* Stop thread */
				if ( m_bPlayThread )
				{
					m_bPlayThread = false;
					wait_for_thread( m_hPlayThread );
				}
				CloseCurrentFile();
				/* Start thread */
				uint nSelected = m_pcWin->GetPlaylist()->GetFirstSelected();
				CFListItem * pcRow = ( CFListItem* ) m_pcWin->GetPlaylist()->GetRow( nSelected );
				if ( OpenFile( pcRow->zPath, pcRow->nTrack, pcRow->nStream ) != 0 )
				{
					std::cout << "Cannot play file!" << std::endl;
					break;
				}
				SetState( CF_STATE_PLAYING );
				m_hPlayThread = spawn_thread( "play_thread", (void*)play_thread_entry, 0, 0, this );
				resume_thread( m_hPlayThread );
			}
			else if ( m_nState == CF_STATE_PAUSED )
			{
				/* Start thread */
				if ( m_bPlayThread )
				{
					m_bPlayThread = false;
					wait_for_thread( m_hPlayThread );
				}
				SetState( CF_STATE_PLAYING );
				m_nLastPosition = m_pcWin->GetLCD()->GetValue().AsInt32() * m_pcInput->GetLength() / 1000;
				m_hPlayThread = spawn_thread( "play_thread", (void*)play_thread_entry, 0, 0, this );
				resume_thread( m_hPlayThread );
			}
			break;
		}
		case CF_GUI_PAUSE:
		/* Pause ( sent by the CFWindow class ) */
		{
			if ( m_nState == CF_STATE_PLAYING )
			{
				SetState( CF_STATE_PAUSED );
				/* Start thread */
				if ( m_bPlayThread )
				{
					m_bPlayThread = false;
					wait_for_thread( m_hPlayThread );
				}
			}
			break;
		}
		case CF_PLAY_NEXT:
		case CF_PLAY_PREVIOUS:
		case CF_GUI_STOP:
		/* Stop ( sent by the CFWindow class ) */
		{
			if ( m_nState != CF_STATE_STOPPED )
			{
				SetState( CF_STATE_STOPPED );
				/* Stop thread */
				if ( m_bPlayThread )
				{
					m_bPlayThread = false;
					wait_for_thread( m_hPlayThread );
				}
				CloseCurrentFile();
				m_nLastPosition = 0;
				m_pcWin->GetLCD()->SetValue( os::Variant( 0 ) );
				m_pcWin->GetLCD()->UpdateTime( 0 );
			}
			/* Play next track */
			if ( pcMessage->GetCode() == CF_PLAY_NEXT )
				resume_thread( spawn_thread( "play_next", (void*)play_next_entry, 0, 0, this ) );
			else if (pcMessage->GetCode() == CF_PLAY_PREVIOUS)
				resume_thread( spawn_thread( "play_prev", (void*)play_prev_entry, 0, 0, this ) );
			break;
		}
		case CF_GUI_SEEK:
		/* Seek ( sent by the CFWindow class ) */
		{
			if ( m_nState == CF_STATE_PLAYING && !m_bStream )
			{
				/* Stop thread */
				if ( m_bPlayThread )
				{
					m_bPlayThread = false;
					wait_for_thread( m_hPlayThread );
				}
				/* Set new position */
				m_nLastPosition = m_pcWin->GetLCD()->GetValue().AsInt32() * m_pcInput->GetLength() / 1000;
				m_hPlayThread = spawn_thread( "play_thread", (void*)play_thread_entry, 0, 0, this );
				resume_thread( m_hPlayThread );
			} else {
				m_pcWin->GetLCD()->SetValue( os::Variant( 0 ) );
			}
			break;
		}
		case CF_GUI_REMOVE_FILE:
		/* Remove the selected file from the playlist */
		{
			if ( m_pcWin->GetPlaylist()->GetRowCount() == 0 )
			{
				break;
			}
			/* Look if this file is already played */
			uint nSelected = m_pcWin->GetPlaylist()->GetFirstSelected();
			if ( m_nState == CF_STATE_PLAYING )
			{
				CFListItem * pcRow = ( CFListItem * ) m_pcWin->GetPlaylist()->GetRow( nSelected );
				if ( pcRow->zPath == m_zAudioFile && pcRow->nTrack == ( int )m_nAudioTrack && pcRow->nStream == ( int )m_nAudioStream )
				{
					os::Alert * pcAlert = new os::Alert( "ColdFish Error", "The currently played track cannot be deleted.", os::Alert::ALERT_WARNING, 0, "OK", NULL );
					pcAlert->Go( new os::Invoker( 0 ) );
					break;
				}
			}
			/* Remove it */
			delete( m_pcWin->GetPlaylist()->RemoveRow( nSelected ) );
			if ( m_pcWin->GetPlaylist()->GetRowCount() > 0 )
				m_pcWin->GetPlaylist()->Select( 0, 0 );
			m_pcWin->GetPlaylist()->Invalidate( true );
			m_pcWin->GetPlaylist()->Sync();
			break;
		}
		case CF_GUI_SELECT_LIST:
		/* Select playlist */
		{
			/* Open window */
			os::Rect cFrame = os::Rect( 0, 0, 230, 100 );
			SelectWin *pcWin = new SelectWin( cFrame );
			pcWin->CenterInWindow( m_pcWin );
			pcWin->Show();
			pcWin->MakeFocus();
			break;
		}
		case CF_GUI_OPEN_INPUT:
		{
		/* Open ( sent by the CFWindow class ) */
			if ( m_nState != CF_STATE_STOPPED )
			{
				SetState( CF_STATE_STOPPED );
				/* Stop thread */
				if ( m_bPlayThread )
				{
					m_bPlayThread = false;
					wait_for_thread( m_hPlayThread );
				}
				m_nLastPosition = 0;
				m_pcWin->GetLCD()->SetValue( os::Variant( 0 ) );
				m_pcWin->GetLCD()->UpdateTime( 0 );
			}

			if ( m_pcInputSelector== NULL )
			{
				/* Open input selector */
				m_pcInputSelector = new os::MediaInputSelector( os::Point( 150, 150 ), "Open", new os::Messenger( this ), new os::Message( CF_IS_OPEN ), new os::Message( CF_IS_CANCEL ) );
				m_pcInputSelector->Show();
				m_pcInputSelector->MakeFocus( true );
			}
			else
			{
				m_pcInputSelector->MakeFocus( true );
			}
			break;
		}
		case CF_IS_CANCEL:
		{
			m_pcInputSelector = NULL;
			break;
		}
		case CF_IS_OPEN:
		{
			/* Message sent by the input selector */
			os::String zFile;
			os::String zInput;
			if ( pcMessage->FindString( "file/path", &zFile.str() ) == 0 && pcMessage->FindString( "input", &zInput.str() ) == 0 )
			{
				CloseCurrentFile();
				OpenInput( zFile, zInput );
			}
			m_pcInputSelector = NULL;
			break;
		}
		case CF_GUI_SHOW_LIST:
		/* Show or hide playlist / visualization */
		{
			/* Calling Show() doesn't work, so just resize the window to hide
			   the playlist under the controls */
			if ( m_pcWin->GetFlags() & os::WND_NOT_V_RESIZABLE )
			{		
				m_bListShown = true;
				os::Rect cFrame = m_pcWin->GetFrame();
				cFrame.bottom = cFrame.top + m_cSavedFrame.bottom - m_cSavedFrame.top;
				m_pcWin->Lock();
				m_pcWin->SetFrame( cFrame );
				m_pcWin->SetFlags( m_pcWin->GetFlags() & ~os::WND_NOT_V_RESIZABLE );				
				m_pcWin->SetSizeLimits( os::Point( 400,150 ), os::Point( 4096, 4096 ) );
				m_pcWin->Unlock();	
			}
			else
			{
				m_bListShown = false;
				m_pcWin->Lock();
				os::Rect cFrame = m_cSavedFrame = m_pcWin->GetFrame();
				cFrame.bottom = cFrame.top + 70 + m_pcWin->GetMenuBar()->GetBounds().Height();
				m_pcWin->SetFrame( cFrame );
				m_pcWin->SetFlags( m_pcWin->GetFlags() | os::WND_NOT_V_RESIZABLE );
				m_pcWin->SetSizeLimits( os::Point( 400,0 ), os::Point( 4096, 4096 ) );
				m_pcWin->Unlock();
			}
			break;
		}
		case CF_GUI_LIST_INVOKED:
		/* Play one item in the playlist */
		{
			if ( m_nState != CF_STATE_STOPPED )
			{
				SetState( CF_STATE_STOPPED );
				/* Stop thread */
				if ( m_bPlayThread )
				{
					m_bPlayThread = false;
					wait_for_thread( m_hPlayThread );
				}
				CloseCurrentFile();
				m_nLastPosition = 0;
				m_pcWin->GetLCD()->SetValue( os::Variant( 0 ) );
				m_pcWin->GetLCD()->UpdateTime( 0 );
			}

			/* Small hack ( select item before the invoked one ) */
			uint nSelected = m_pcWin->GetPlaylist()->GetFirstSelected();
			if ( nSelected == 0 )
				nSelected = m_pcWin->GetPlaylist()->GetRowCount() - 1;
			else
				nSelected--;
			m_pcWin->GetPlaylist()->Select( nSelected );

			/* Play next track */
			resume_thread( spawn_thread( "play_next", (void*)play_next_entry, 0, 0, this ) );
			break;
		}
		case CF_GUI_LIST_SELECTED:
		/* Open one playlist */
		{
			os::String zFilename;
			if ( pcMessage->FindString( "file/path", &zFilename.str() ) == 0 )
			{
				/* Stop playback */
				if ( m_nState != CF_STATE_STOPPED )
				{
					SetState( CF_STATE_STOPPED );
					/* Stop thread */
					if ( m_bPlayThread )
					{
						m_bPlayThread = false;
						wait_for_thread( m_hPlayThread );
					}
					CloseCurrentFile();
					m_nLastPosition = 0;
					m_pcWin->GetLCD()->SetValue( os::Variant( 0 ) );
					m_pcWin->GetLCD()->UpdateTime( 0 );
				}
				/* Save opened list */
				SaveList();
				/* Try to open the list */
				if ( !OpenList( zFilename ) )
				{
					std::ifstream hIn;
					hIn.open( zFilename.c_str() );
					if ( hIn.is_open() )
					{
						/* Do not overwrite any files */
						OpenList( m_zListName );
						os::Alert * pcAlert = new os::Alert( "ColdFish Error", "Selected file is not a playlist.", os::Alert::ALERT_WARNING, 0, "OK", NULL );
						pcAlert->Go( new os::Invoker( 0 ) );
						break;
					}
					/* Create new playlist */
					m_pcWin->GetPlaylist()->Clear();
					m_zListName = zFilename;
					m_pcWin->SetTitle( os::String ( os::Path( zFilename.c_str() ).GetLeaf() ) + " - ColdFish" );
					SaveList();
				}
			}
			break;
		}
		case os::M_LOAD_REQUESTED:
		case CF_ADD_FILE:
		{
			/* Add one file ( sent by the CFWindow class or the filerequester ) */
			os::String zFile;
			int i = 0;
			while( pcMessage->FindString( "file/path", &zFile.str(), i ) == 0 && !m_bLockedInput )
			{
				AddFile( zFile );
				i++;
			}
			break;
		}
		case CF_GUI_VIEW_LIST:
		/* Show playlist */
		{
			if( !m_pcWin->GetPlaylist()->IsVisible() )
			{
				m_pcWin->Lock();
				m_pcWin->GetPlaylist()->Show();
				m_pcWin->Unlock();
			}
			break;
		}
		case CF_GET_SONG:
		{
			os::Message cReply(0);
			if (pcMessage->IsSourceWaiting())
			{
				cReply.AddString("track_name",m_zTrackName.c_str());
				pcMessage->SendReply(&cReply);
			}	
			break;
		}
		case CF_GET_PLAYSTATE:
		{
			os::Message cReply(0);
			if (pcMessage->IsSourceWaiting())
			{
				cReply.AddInt32("play_state",m_nState);
				pcMessage->SendReply(&cReply);
			}
			break;
		}
		case CF_GUI_ADD_FILE:
		{
			m_pcWin->PostMessage(CF_GUI_ADD_FILE,m_pcWin);
			break;
		}
		case CF_GUI_ABOUT:
		{
			m_pcWin->PostMessage(CF_GUI_ABOUT,m_pcWin);
			break;	
		}
		case CF_GUI_QUIT:
		{
			os::Application::GetInstance()->PostMessage( os::M_QUIT );
			break;
		}
		default:
		{
			os::Looper::HandleMessage( pcMessage );
			break;
		}
	}
}

bool CFApp::OkToQuit()
{
	if ( m_pcManager->IsValid() )
	{
		SaveList();
		os::Settings * pcSettings = new os::Settings();
		pcSettings->AddString( "playlist", m_zListName );
		pcSettings->Save();
		delete( pcSettings );
		CloseCurrentFile();
	}
	return ( true );
}

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

CFApplication::CFApplication( const char *pzMimeType, os::String zFileName, bool bLoad ) : os::Application( pzMimeType )
{
	/* Select string catalogue */
	try {
		SetCatalog( "coldfish.catalog" );
	} catch( ... ) {
		if (DEBUG)
			std::cout << "Failed to load catalog file!" << std::endl;
	}
	
	m_pcApp = new CFApp();
	m_pcApp->Run();
	os::Message cMsg( CF_APP_STARTED );
	if( bLoad )
		cMsg.AddString( "file/path", zFileName );
	m_pcApp->PostMessage( &cMsg, m_pcApp );
}

CFApplication::~CFApplication()
{
	m_pcApp->Terminate();
}

void CFApplication::HandleMessage( os::Message * pcMessage )
{
	m_pcApp->PostMessage( pcMessage, m_pcApp );
}

bool CFApplication::OkToQuit()
{
	return( m_pcApp->OkToQuit() );
}

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

int main( int argc, char *argv[] )
{
	CFApplication *pcApp = NULL;

	if ( argc > 1 )
	{
		pcApp = new CFApplication( "application/x-vnd.syllable-ColdFish", argv[1], true );
	}
	else
	{
		pcApp = new CFApplication( "application/x-vnd.syllable-ColdFish", "", false );
	}

	pcApp->Run();
	return ( 0 );
}