//========= Copyright Valve Corporation, All rights reserved. ============//
//
// This is a helper class designed to help with the chains of modal dialogs
// encountered when trying to open or save a particular file
//
//=============================================================================

#include "vgui_controls/FileOpenStateMachine.h"
#include "tier1/KeyValues.h"
#include "vgui_controls/FileOpenDialog.h"
#include "vgui_controls/MessageBox.h"
#include "vgui_controls/perforcefilelistframe.h"
#include "vgui_controls/savedocumentquery.h"
#include "filesystem.h"
#include "p4lib/ip4.h"
#include "tier2/tier2.h"
#include "tier0/icommandline.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"


using namespace vgui;


//-----------------------------------------------------------------------------
// Constructor
//-----------------------------------------------------------------------------
FileOpenStateMachine::FileOpenStateMachine( vgui::Panel *pParent, IFileOpenStateMachineClient *pClient ) : BaseClass( pParent, "FileOpenStateMachine" )
{
	m_pClient = pClient;
	m_CompletionState = SUCCESSFUL;
	m_CurrentState = STATE_NONE;
	m_pContextKeyValues = NULL;
	SetVisible( false );
}

FileOpenStateMachine::~FileOpenStateMachine()
{
	CleanUpContextKeyValues();
}


//-----------------------------------------------------------------------------
// Cleans up keyvalues
//-----------------------------------------------------------------------------
void FileOpenStateMachine::CleanUpContextKeyValues()
{
	if ( m_pContextKeyValues )
	{
		m_pContextKeyValues->deleteThis();
		m_pContextKeyValues = NULL;
	}
}


//-----------------------------------------------------------------------------
// Returns the state machine completion state
//-----------------------------------------------------------------------------
FileOpenStateMachine::CompletionState_t FileOpenStateMachine::GetCompletionState()
{
	return m_CompletionState;
}


//-----------------------------------------------------------------------------
// Utility to set the completion state
//-----------------------------------------------------------------------------
void FileOpenStateMachine::SetCompletionState( FileOpenStateMachine::CompletionState_t state )
{
	m_CompletionState = state;
	if ( m_CompletionState == IN_PROGRESS )
		return;

	m_CurrentState = STATE_NONE;

	KeyValues *kv = new KeyValues( "FileStateMachineFinished" );
	kv->SetInt( "completionState", m_CompletionState );
	kv->SetInt( "wroteFile", m_bWroteFile );
	kv->SetString( "fullPath", m_FileName.Get() );
	kv->SetString( "fileType", m_bIsOpeningFile ? m_OpenFileType.Get() : m_SaveFileType.Get() );
	if ( m_pContextKeyValues )
	{
		kv->AddSubKey( m_pContextKeyValues );
		m_pContextKeyValues = NULL;
	}
	PostActionSignal( kv );
}


//-----------------------------------------------------------------------------
// Called by the message box in OverwriteFileDialog
//-----------------------------------------------------------------------------
void FileOpenStateMachine::OnOverwriteFile( )
{
	CheckOutDialog( );
}

void FileOpenStateMachine::OnCancelOverwriteFile( )
{
	SetCompletionState( FILE_NOT_OVERWRITTEN );
}


//-----------------------------------------------------------------------------
// Shows the overwrite existing file dialog
//-----------------------------------------------------------------------------
void FileOpenStateMachine::OverwriteFileDialog( )
{
	if ( !g_pFullFileSystem->FileExists( m_FileName ) )
	{
		CheckOutDialog( );
		return;
	}

	m_CurrentState = STATE_SHOWING_OVERWRITE_DIALOG;

	char pBuf[1024];
	Q_snprintf( pBuf, sizeof(pBuf), "File already exists. Overwrite it?\n\n\"%s\"\n", m_FileName.Get() ); 
	vgui::MessageBox *pMessageBox = new vgui::MessageBox( "Overwrite Existing File?", pBuf, GetParent() );
	pMessageBox->AddActionSignalTarget( this );
	pMessageBox->SetOKButtonVisible( true );
	pMessageBox->SetOKButtonText( "Yes" );
	pMessageBox->SetCancelButtonVisible( true );
	pMessageBox->SetCancelButtonText( "No" );
	pMessageBox->SetCloseButtonVisible( false ); 
	pMessageBox->SetCommand( new KeyValues( "OverwriteFile" ) );
	pMessageBox->SetCancelCommand( new KeyValues( "CancelOverwriteFile" ) );
	pMessageBox->DoModal();
}


//-----------------------------------------------------------------------------
// Used to open a particular file in perforce, and deal with all the lovely dialogs
//-----------------------------------------------------------------------------
void FileOpenStateMachine::OnFileSelectionCancelled()
{
	if ( m_CurrentState == STATE_SHOWING_SAVE_DIALOG )
	{
		SetCompletionState( FILE_SAVE_NAME_NOT_SPECIFIED );
		return;
	}

	if ( m_CurrentState == STATE_SHOWING_OPEN_DIALOG )
	{
		SetCompletionState( FILE_OPEN_NAME_NOT_SPECIFIED );
		return;
	}

	Assert(0);
}


//-----------------------------------------------------------------------------
// Used to open a particular file in perforce, and deal with all the lovely dialogs
//-----------------------------------------------------------------------------
void FileOpenStateMachine::OnFileSelected( KeyValues *pKeyValues )
{
	if ( m_CurrentState == STATE_SHOWING_SAVE_DIALOG )
	{
		m_FileName = pKeyValues->GetString( "fullpath" );
		const char *pFilterInfo = pKeyValues->GetString( "filterinfo" );
		if ( pFilterInfo )
		{
			m_SaveFileType = pFilterInfo;
		}
		OverwriteFileDialog();
		return;
	}

	if ( m_CurrentState == STATE_SHOWING_OPEN_DIALOG )
	{
		m_FileName = pKeyValues->GetString( "fullpath" );
		const char *pFilterInfo = pKeyValues->GetString( "filterinfo" );
		if ( pFilterInfo )
		{
			m_OpenFileType = pFilterInfo;
		}
		ReadFile( );
		return;
	}

	Assert(0);
}


//-----------------------------------------------------------------------------
// Writes the file out
//-----------------------------------------------------------------------------
void FileOpenStateMachine::WriteFile()
{
	m_CurrentState = STATE_WRITING_FILE;
	if ( !m_pClient->OnWriteFileToDisk( m_FileName, m_SaveFileType, m_pContextKeyValues ) )
	{
		SetCompletionState( ERROR_WRITING_FILE );
		return;
	}

	m_bWroteFile = true;
	if ( m_bShowPerforceDialogs )
	{
		m_CurrentState = STATE_SHOWING_PERFORCE_ADD_DIALOG;
		ShowPerforceQuery( GetParent(), m_FileName, this, NULL, PERFORCE_ACTION_FILE_ADD );
		return;
	}

	if ( !m_bIsOpeningFile )
	{
		SetCompletionState( SUCCESSFUL );
		return;
	}

	OpenFileDialog();
}


//-----------------------------------------------------------------------------
// Called by the message box in MakeFileWriteableDialog
//-----------------------------------------------------------------------------
void FileOpenStateMachine::OnMakeFileWriteable( )
{
	if ( !g_pFullFileSystem->SetFileWritable( m_FileName, true ) )
	{
		SetCompletionState( ERROR_MAKING_FILE_WRITEABLE );
		return;
	}

	WriteFile();
}

void FileOpenStateMachine::OnCancelMakeFileWriteable( )
{
	SetCompletionState( FILE_NOT_MADE_WRITEABLE );
}


//-----------------------------------------------------------------------------
// Shows the make file writeable dialog
//-----------------------------------------------------------------------------
void FileOpenStateMachine::MakeFileWriteableDialog( )
{
	// If the file is writeable, write it!
	if ( !g_pFullFileSystem->FileExists( m_FileName ) || g_pFullFileSystem->IsFileWritable( m_FileName ) )
	{
		WriteFile();
		return;
	}

	// If it's in perforce, and not checked out, then we must abort.
	bool bIsInPerforce = p4->IsFileInPerforce( m_FileName );
	bool bIsOpened = ( p4->GetFileState( m_FileName ) != P4FILE_UNOPENED );
	if ( bIsInPerforce && !bIsOpened )
	{
		SetCompletionState( FILE_NOT_CHECKED_OUT );
		return;
	}

	m_CurrentState = STATE_SHOWING_MAKE_FILE_WRITEABLE_DIALOG;

	char pBuf[1024];
	Q_snprintf( pBuf, sizeof(pBuf), "Encountered read-only file. Should it be made writeable?\n\n\"%s\"\n", m_FileName.Get() ); 
	vgui::MessageBox *pMessageBox = new vgui::MessageBox( "Make File Writeable?", pBuf, GetParent() );
	pMessageBox->AddActionSignalTarget( this );
	pMessageBox->SetOKButtonVisible( true );
	pMessageBox->SetOKButtonText( "Yes" );
	pMessageBox->SetCancelButtonVisible( true );
	pMessageBox->SetCancelButtonText( "No" );
	pMessageBox->SetCloseButtonVisible( false ); 
	pMessageBox->SetCommand( new KeyValues( "MakeFileWriteable" ) );
	pMessageBox->SetCancelCommand( new KeyValues( "CancelMakeFileWriteable" ) );
	pMessageBox->DoModal();
}


//-----------------------------------------------------------------------------
// Called when ShowPerforceQuery completes
//-----------------------------------------------------------------------------
void FileOpenStateMachine::OnPerforceQueryCompleted( KeyValues *pKeyValues )
{
	if ( m_CurrentState == STATE_SHOWING_CHECK_OUT_DIALOG )
	{
		if ( pKeyValues->GetInt( "operationPerformed" ) == 0 )
		{
			SetCompletionState( FILE_NOT_CHECKED_OUT );
			return;
		}

		MakeFileWriteableDialog();
		return;
	}

	if ( m_CurrentState == STATE_SHOWING_PERFORCE_ADD_DIALOG )
	{
		if ( !m_bIsOpeningFile )
		{
			SetCompletionState( SUCCESSFUL );
			return;
		}

		OpenFileDialog();
		return;
	}

	Assert(0);
}


//-----------------------------------------------------------------------------
// Used to open a particular file in perforce, and deal with all the lovely dialogs
//-----------------------------------------------------------------------------
void FileOpenStateMachine::CheckOutDialog( )
{
	if ( m_bShowPerforceDialogs )
	{
		m_CurrentState = STATE_SHOWING_CHECK_OUT_DIALOG;
		ShowPerforceQuery( GetParent(), m_FileName, this, NULL, PERFORCE_ACTION_FILE_EDIT );
		return;
	}

	WriteFile();
}


//-----------------------------------------------------------------------------
// These 3 messages come from the savedocumentquery dialog
//-----------------------------------------------------------------------------
void FileOpenStateMachine::OnSaveFile()
{
	if ( !m_FileName[0] || !Q_IsAbsolutePath( m_FileName ) )
	{
		m_CurrentState = STATE_SHOWING_SAVE_DIALOG;

		FileOpenDialog *pDialog = new FileOpenDialog( GetParent(), "Save As", false );
		m_pClient->SetupFileOpenDialog( pDialog, false, m_SaveFileType, m_pContextKeyValues );
		pDialog->SetDeleteSelfOnClose( true );
		pDialog->AddActionSignalTarget( this );
		pDialog->DoModal( );
		return;
	}

	CheckOutDialog( );
}

void FileOpenStateMachine::OnMarkNotDirty()
{
	if ( !m_bIsOpeningFile )
	{
		SetCompletionState( SUCCESSFUL );
		return;
	}

	// Jump right to opening the file
	OpenFileDialog( );
}

void FileOpenStateMachine::OnCancelSaveDocument()
{
	SetCompletionState( FILE_SAVE_CANCELLED );
}


//-----------------------------------------------------------------------------
// Show the save document query dialog
//-----------------------------------------------------------------------------
void FileOpenStateMachine::ShowSaveQuery( )
{
	m_CurrentState = STATE_SHOWING_SAVE_DIRTY_FILE_DIALOG;
	ShowSaveDocumentQuery( GetParent(), m_FileName, m_SaveFileType, 0, this, NULL );
}


//-----------------------------------------------------------------------------
// Used to save a specified file, and deal with all the lovely dialogs
//-----------------------------------------------------------------------------
void FileOpenStateMachine::SaveFile( KeyValues *pContextKeyValues, const char *pFileName, const char *pFileType, int nFlags )
{
	CleanUpContextKeyValues();
	SetCompletionState( IN_PROGRESS );
	m_pContextKeyValues = pContextKeyValues;
	m_FileName = pFileName;
	m_SaveFileType = pFileType;
	m_OpenFileType = NULL;
	m_OpenFileName = NULL;

	// Clear the P4 dialog flag for SDK users and licensees without Perforce
	if ( CommandLine()->FindParm( "-nop4" ) )
	{
		nFlags &= ~FOSM_SHOW_PERFORCE_DIALOGS;
	}

	m_bShowPerforceDialogs = ( nFlags & FOSM_SHOW_PERFORCE_DIALOGS ) != 0;
	m_bShowSaveQuery = ( nFlags & FOSM_SHOW_SAVE_QUERY ) != 0;
	m_bIsOpeningFile = false;
	m_bWroteFile = false;

	if ( m_bShowSaveQuery )
	{
		ShowSaveQuery();
		return;
	}

	OnSaveFile();
}


//-----------------------------------------------------------------------------
// Reads the file in
//-----------------------------------------------------------------------------
void FileOpenStateMachine::ReadFile()
{
	m_CurrentState = STATE_READING_FILE;
	if ( !m_pClient->OnReadFileFromDisk( m_FileName, m_OpenFileType, m_pContextKeyValues ) )
	{
		SetCompletionState( ERROR_READING_FILE );
		return;
	}

	SetCompletionState( SUCCESSFUL );
}


//-----------------------------------------------------------------------------
// Shows the open file dialog
//-----------------------------------------------------------------------------
void FileOpenStateMachine::OpenFileDialog( )
{
	m_CurrentState = STATE_SHOWING_OPEN_DIALOG;

	if ( m_OpenFileName.IsEmpty() )
	{
		FileOpenDialog *pDialog = new FileOpenDialog( GetParent(), "Open", true );
		m_pClient->SetupFileOpenDialog( pDialog, true, m_OpenFileType, m_pContextKeyValues );
		pDialog->SetDeleteSelfOnClose( true );
		pDialog->AddActionSignalTarget( this );
		pDialog->DoModal( );
	}
	else
	{
		m_FileName = m_OpenFileName;
		ReadFile();
	}
}


//-----------------------------------------------------------------------------
// Opens a file, saves an existing one if necessary
//-----------------------------------------------------------------------------
void FileOpenStateMachine::OpenFile( const char *pOpenFileType, KeyValues *pContextKeyValues, const char *pSaveFileName, const char *pSaveFileType, int nFlags )
{
	CleanUpContextKeyValues();
	SetCompletionState( IN_PROGRESS );
	m_pContextKeyValues = pContextKeyValues;
	m_FileName = pSaveFileName;
	m_SaveFileType = pSaveFileType;
	m_OpenFileType = pOpenFileType;
	m_OpenFileName = NULL;
	m_bShowPerforceDialogs = ( nFlags & FOSM_SHOW_PERFORCE_DIALOGS ) != 0;
	m_bShowSaveQuery = ( nFlags & FOSM_SHOW_SAVE_QUERY ) != 0;
	m_bIsOpeningFile = true;
	m_bWroteFile = false;

	if ( m_bShowSaveQuery )
	{
		ShowSaveQuery();
		return;
	}

	OpenFileDialog();
}


//-----------------------------------------------------------------------------
// Version of OpenFile that skips browsing for a particular file to open
//-----------------------------------------------------------------------------
void FileOpenStateMachine::OpenFile( const char *pOpenFileName, const char *pOpenFileType, KeyValues *pContextKeyValues, const char *pSaveFileName, const char *pSaveFileType, int nFlags )
{
	CleanUpContextKeyValues();
	SetCompletionState( IN_PROGRESS );
	m_pContextKeyValues = pContextKeyValues;
	m_FileName = pSaveFileName;
	m_SaveFileType = pSaveFileType;
	m_OpenFileType = pOpenFileType;
	m_bShowPerforceDialogs = ( nFlags & FOSM_SHOW_PERFORCE_DIALOGS ) != 0;
	m_bShowSaveQuery = ( nFlags & FOSM_SHOW_SAVE_QUERY ) != 0;
	m_bIsOpeningFile = true;
	m_bWroteFile = false;
	m_OpenFileName = pOpenFileName;
	if ( m_bShowSaveQuery )
	{
		ShowSaveQuery();
		return;
	}

	OpenFileDialog();
}