/*
AppleWin : An Apple //e emulator for Windows

Copyright (C) 2006-2014, Tom Charlesworth, Michael Pohoreski

AppleWin 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.

AppleWin 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 AppleWin; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/* Description: Data Blocks shown in Disassembler
 *
 * Author: Copyright (C) 2009 - 2010 Michael Pohoreski
 */

#include "StdAfx.h"

#include "Debug.h"


// Disassembler Data ______________________________________________________________________________

// __ Debugger Interface ____________________________________________________________________________

//===========================================================================
WORD _CmdDefineByteRange(int nArgs,int iArg,DisasmData_t & tData_)
{
	WORD nAddress  = 0;
	WORD nAddress2 = 0;
	WORD nEnd      = 0;
	int  nLen      = 0;

	memset( (void*) &tData_, 0, sizeof(tData_) );

	// DB address
	// DB symbol
	// bool bisAddress ...

	if( nArgs < 1 )
	{
		nAddress = g_nDisasmCurAddress;
	}
	else
	{
		RangeType_t eRange = Range_Get( nAddress, nAddress2, iArg);
		if ((eRange == RANGE_HAS_END) ||
			(eRange == RANGE_HAS_LEN))
		{
			Range_CalcEndLen( eRange, nAddress, nAddress2, nEnd, nLen );
			nLen--; // Disassembly_IsDataAddress() is *inclusive* // KEEP IN SYNC: _CmdDefineByteRange() CmdDisasmDataList() _6502_GetOpmodeOpbyte() FormatNopcodeBytes()
		}
		else
		{
			if( nArgs > 1 )
				nAddress = g_aArgs[ 2 ].nValue;
			else
				nAddress = g_aArgs[ 1 ].nValue;
		}
	}

	// 2.7.0.35 DW address -- round the length up to even number for convenience.
	// Example: 'DW 6062' is equivalent to: 'DW 6062:6063'
	if( g_iCommand == CMD_DEFINE_DATA_WORD1 )
	{
		if( ~nLen & 1 )
			nLen++;
	}

	tData_.nStartAddress = nAddress;
	tData_.nEndAddress = nAddress + nLen;
//	tData_.nArraySize = 0;

	char *pSymbolName = "";
	char aSymbolName[ MAX_SYMBOLS_LEN+1 ];
	SymbolTable_Index_e eSymbolTable = SYMBOLS_ASSEMBLY;
	bool bAutoDefineName = false; // 2.7.0.34

	if( nArgs > 1 )
	{
		if( g_aArgs[ 2 ].eToken == TOKEN_COLON ) // 2.7.0.31 Bug fix: DB range, i.e. DB 174E:174F
		{
			bAutoDefineName = true;
		}
		else
		{
			pSymbolName = g_aArgs[ 1 ].sArg;
			pSymbolName[MAX_SYMBOLS_LEN] = 0;	// truncate to max symbol length
		}
	}
	else
	{
		bAutoDefineName = true;
	}

	// 2.7.0.34 Unified auto-defined name: B_, W_, T_ for byte, word, or text respectively
	// Old name: auto define D_# DB $XX
	// Example 'DB' or 'DW' with 1 arg
	//   DB 801
	if( bAutoDefineName )
	{
		if( g_iCommand == CMD_DEFINE_DATA_STR )
			sprintf( aSymbolName, "T_%04X", tData_.nStartAddress ); // ASC range
		else
		if( g_iCommand == CMD_DEFINE_DATA_WORD1 )
			sprintf( aSymbolName, "W_%04X", tData_.nStartAddress ); // DW range
		else
			sprintf( aSymbolName, "B_%04X", tData_.nStartAddress ); // DB range

			pSymbolName = aSymbolName;
	}

	// bRemoveSymbol = false // use arg[2]
	// bUpdateSymbol = true // add the symbol to the table
	SymbolUpdate( eSymbolTable, pSymbolName, nAddress, false, true ); 

	// TODO: Note: need to call ConsoleUpdate(), as may print symbol has been updated

	strcpy( tData_.sSymbol, pSymbolName );

	return nAddress;
}

// Undefine Data
//===========================================================================
Update_t CmdDisasmDataDefCode (int nArgs)
{
	// treat memory (bytes) as code
	if (! ((nArgs <= 2) || (nArgs == 4)))
	{
		return Help_Arg_1( CMD_DISASM_CODE );
	}

	DisasmData_t tData;
	int iArg = 2;
	WORD nAddress = _CmdDefineByteRange( nArgs, iArg, tData );

	// Need to iterate through all blocks
	// DB TEST1 300:320
	// DB TEST2 310:330
	// DB TEST3 320:340
	// X  TEST1

	DisasmData_t *pData = Disassembly_IsDataAddress( nAddress );
	if( pData )
	{
		// TODO: Do we need to split the data !?
		//Disassembly_DelData( tData );
		pData->iDirective = _NOP_REMOVED;

		// TODO: Remove symbol 'D_FA62' from symbol table!
	}
	else
	{
		Disassembly_DelData( tData );
	}

	return UPDATE_DISASM | ConsoleUpdate();
}

char* g_aNopcodeTypes[ NUM_NOPCODE_TYPES ] =
{
	 "-n/a-"
	,"byte1"
	,"byte2"
	,"byte4"
	,"byte8"
	,"word1"
	,"word2"
	,"word4"
	,"addr "
	,"hex  "
	,"char "
	,"ascii"
	,"apple"
	,"mixed"
	,"FAC  "
	,"bmp  "
};

// Command: B
// no args
// List the data blocks
//===========================================================================
Update_t CmdDisasmDataList (int nArgs)
{

	// Need to iterate through all blocks
	DisasmData_t* pData = NULL;

	while( pData = Disassembly_Enumerate( pData ) )
	{
		if (pData->iDirective != _NOP_REMOVED)
		{
			int nLen = strlen( pData->sSymbol );

			char sText[CONSOLE_WIDTH * 2];
			// <smbol> <type> <start>:<end>
			// `TEST `300`:`320
			ConsolePrintFormat( sText, "%s%s %s%*s %s%04X%s:%s%04X"
				, CHC_CATEGORY
				, g_aNopcodeTypes[ pData->eElementType ] 
				, (nLen > 0) ? CHC_SYMBOL     : CHC_DEFAULT
				, MAX_SYMBOLS_LEN
				, (nLen > 0) ? pData->sSymbol : "???"
				, CHC_ADDRESS
				, pData->nStartAddress
				, CHC_ARG_SEP
				, CHC_ADDRESS
				, pData->nEndAddress // Disassembly_IsDataAddress() is *inclusive* // KEEP IN SYNC:  _CmdDefineByteRange() CmdDisasmDataList() _6502_GetOpmodeOpbyte() FormatNopcodeBytes()
			);
		}
	}

	return UPDATE_DISASM | ConsoleUpdate();
}

// Common code
//===========================================================================
Update_t _CmdDisasmDataDefByteX (int nArgs)
{
	// DB
	// DB symbol // use current instruction pointer
	// DB symbol address
	// DB symbol range:range
	// DB address
	// To "return to code" use ."X" 
	int iCmd = g_aArgs[0].nValue - NOP_BYTE_1;

	if (nArgs > 4) // 2.7.0.31 Bug fix: DB range, i.e. DB 174E:174F
	{
		return Help_Arg_1( CMD_DEFINE_DATA_BYTE1 + iCmd );
	}

	DisasmData_t tData;
	int iArg = 2;

	if (nArgs == 3 ) // 2.7.0.31 Bug fix: DB range, i.e. DB 174E:175F
	{
		if ( g_aArgs[ 2 ].eToken == TOKEN_COLON )
			iArg = 1;
	}

	WORD nAddress = _CmdDefineByteRange( nArgs, iArg, tData );

	// TODO: Allow user to select which assembler to use for displaying directives!
//	tData.iDirective = FIRST_M_DIRECTIVE + ASM_M_DEFINE_BYTE;
	tData.iDirective = g_aAssemblerFirstDirective[ g_iAssemblerSyntax ] + ASM_DEFINE_BYTE;

	tData.eElementType = (Nopcode_e)( NOP_BYTE_1 + iCmd );
	tData.bSymbolLookup = false;
	tData.nTargetAddress = 0;

	// Already exists, so update
	DisasmData_t *pData = Disassembly_IsDataAddress( nAddress );
	if( pData )
	{
		*pData = tData;
	}
	else
		Disassembly_AddData( tData );

	return UPDATE_DISASM | ConsoleUpdate();
}

/*
	Usage:
		DW
		DW symbol
		DW symbol address
		DW symbol range:range
		DW address  Auto-define W_#### where # is the address
		DW range    Auto-define W_#### where # is the address
	Examples:
		DW 3F2:3F3
*/
//===========================================================================
Update_t _CmdDisasmDataDefWordX (int nArgs)
{
	int iCmd = g_aArgs[0].nValue - NOP_WORD_1;

	if (nArgs > 4) // 2.7.0.31 Bug fix: DB range, i.e. DB 174E:174F
	{
		return Help_Arg_1( CMD_DEFINE_DATA_WORD1 + iCmd );
	}

	DisasmData_t tData;
	int iArg = 2;

	if (nArgs == 3 ) // 2.7.0.33 Bug fix: DW range, i.e. DW 3F2:3F3
	{
		if ( g_aArgs[ 2 ].eToken == TOKEN_COLON )
			iArg = 1;
	}

	WORD nAddress = _CmdDefineByteRange( nArgs, iArg, tData );

//	tData.iDirective = FIRST_M_DIRECTIVE + ASM_M_DEFINE_WORD;
	tData.iDirective = g_aAssemblerFirstDirective[ g_iAssemblerSyntax ] + ASM_DEFINE_WORD;

	tData.eElementType = (Nopcode_e)( NOP_WORD_1 + iCmd );
	tData.bSymbolLookup = false;
	tData.nTargetAddress = 0;

	// Already exists, so update
	DisasmData_t *pData = Disassembly_IsDataAddress( nAddress );
	if( pData )
	{
		*pData = tData;
	}
	else
		Disassembly_AddData( tData );

	return UPDATE_DISASM | ConsoleUpdate();
}

//===========================================================================
Update_t CmdDisasmDataDefAddress8H (int nArgs)
{
	return UPDATE_DISASM;
}

//===========================================================================
Update_t CmdDisasmDataDefAddress8L (int nArgs)
{
	return UPDATE_DISASM;
}

//===========================================================================
Update_t CmdDisasmDataDefAddress16 (int nArgs)
{
	int iCmd = NOP_WORD_1 - g_aArgs[0].nValue;

	if (! ((nArgs <= 2) || (nArgs == 4)))
	{
		return Help_Arg_1( CMD_DEFINE_DATA_WORD1 + iCmd );
	}

	DisasmData_t tData;
	int iArg = 2;
	WORD nAddress = _CmdDefineByteRange( nArgs, iArg, tData );

//	tData.iDirective = FIRST_M_DIRECTIVE + ASM_M_DEFINE_WORD;
	tData.iDirective = g_aAssemblerFirstDirective[ g_iAssemblerSyntax ] + ASM_DEFINE_ADDRESS_16;

	tData.eElementType = NOP_ADDRESS;
	tData.bSymbolLookup = true;
	tData.nTargetAddress = 0; // dynamic -- will be filled in ...

	// Already exists, so update
	DisasmData_t *pData = Disassembly_IsDataAddress( nAddress );
	if( pData )
	{
		*pData = tData;
	}
	else
		Disassembly_AddData( tData );

	return UPDATE_DISASM | ConsoleUpdate();
}

// DB
Update_t CmdDisasmDataDefByte1 ( int nArgs )
{
	g_aArgs[0].nValue = NOP_BYTE_1;
	return _CmdDisasmDataDefByteX( nArgs );	
}

// DB2
Update_t CmdDisasmDataDefByte2 ( int nArgs )
{
	g_aArgs[0].nValue = NOP_BYTE_2;
	return _CmdDisasmDataDefByteX( nArgs );	
}

Update_t CmdDisasmDataDefByte4 ( int nArgs )
{
	g_aArgs[0].nValue = NOP_BYTE_4;
	return _CmdDisasmDataDefByteX( nArgs );	
}

Update_t CmdDisasmDataDefByte8 ( int nArgs )
{
	g_aArgs[0].nValue = NOP_BYTE_8;
	return _CmdDisasmDataDefByteX( nArgs );	
}

// DW
Update_t CmdDisasmDataDefWord1 ( int nArgs )
{
	g_aArgs[0].nValue = NOP_WORD_1;
	return _CmdDisasmDataDefWordX( nArgs );	
}

// DW2
Update_t CmdDisasmDataDefWord2 ( int nArgs )
{
	g_aArgs[0].nValue = NOP_WORD_2;
	return _CmdDisasmDataDefWordX( nArgs );	
}

Update_t CmdDisasmDataDefWord4 ( int nArgs )
{
	g_aArgs[0].nValue = NOP_WORD_4;
	return _CmdDisasmDataDefWordX( nArgs );	
}

// Command: DS
//		ASC range    Auto-define T_#### where # is the address
Update_t CmdDisasmDataDefString ( int nArgs )
{
	int iCmd = 0; // Define Ascii, AppleText, MixedText (DOS3.3)

	if (nArgs > 4) // 2.7.0.31 Bug fix: DB range, i.e. DB 174E:174F
	{
		return Help_Arg_1( CMD_DEFINE_DATA_STR + iCmd );
	}

	DisasmData_t tData;
	int iArg = 2;

	if (nArgs == 3 ) // 2.7.0.32 Bug fix: ASC range, i.e. ASC 174E:175F
	{
		if ( g_aArgs[ 2 ].eToken == TOKEN_COLON )
			iArg = 1;
	}

	WORD nAddress = _CmdDefineByteRange( nArgs, iArg, tData );

//	tData.iDirective = g_aAssemblerFirstDirective[ g_iAssemblerSyntax ] + ASM_DEFINE_APPLE_TEXT;
	tData.iDirective = FIRST_M_DIRECTIVE + ASM_M_ASCII; // ASM_MERLIN

	tData.eElementType = (Nopcode_e)( NOP_STRING_APPLE + iCmd );
	tData.bSymbolLookup = false;
	tData.nTargetAddress = 0;

	// Already exists, so update
	DisasmData_t *pData = Disassembly_IsDataAddress( nAddress );
	if( pData )
	{
		*pData = tData;
	}
	else
		Disassembly_AddData( tData );

	return UPDATE_DISASM | ConsoleUpdate();
}

// __ Disassembler View Interface ____________________________________________________________________

/// @param pCurrent NULL start a new serch, or continue enumerating
//===========================================================================
DisasmData_t* Disassembly_Enumerate( DisasmData_t *pCurrent )
{
	DisasmData_t *pData = NULL; // bIsNopcode = false
	int nDataTargets = g_aDisassemblerData.size();

	if( nDataTargets )
	{
		DisasmData_t *pBegin = & g_aDisassemblerData[ 0 ];
		DisasmData_t *pEnd   = & g_aDisassemblerData[ nDataTargets - 1 ];

		if( pCurrent )
		{
			pCurrent++;
			if (pCurrent <= pEnd)
				pData = pCurrent;
		} else {
			pData = pBegin;
		}
	}
	return pData;
}

// returns NULL if address has no data associated with it
//===========================================================================
DisasmData_t* Disassembly_IsDataAddress ( WORD nAddress )
{
	DisasmData_t *pData = NULL; // bIsNopcode = false
	int nDataTargets = g_aDisassemblerData.size();

	if( nDataTargets )
	{
		// TODO: Replace with binary search -- should store data in sorted order, via start address
		pData = & g_aDisassemblerData[ 0 ];
		for( int iTarget = 0; iTarget < nDataTargets; iTarget++ )
		{
			if( pData->iDirective != _NOP_REMOVED )
			{
				if( (nAddress >= pData->nStartAddress) && (nAddress <= pData->nEndAddress) )
				{
					return pData;
				}
			}
			pData++;
		}
		pData = NULL; // bIsNopCode = false
	}
	return pData;
}

// Notes: tData.iDirective should not be _NOP_REMOVED !
//===========================================================================
void Disassembly_AddData( DisasmData_t tData)
{
	g_aDisassemblerData.push_back( tData );
}

// DEPRECATED ! Inlined in _6502_GetOpmodeOpbyte() !
//===========================================================================
void Disassembly_GetData ( WORD nBaseAddress, const DisasmData_t *pData, DisasmLine_t & line_ )
{
	if( !pData )
	{
#if _DEBUG
		ConsoleDisplayError( "Disassembly_GetData() but we don't have a valid DisasmData_t *" );
#endif
		return;
	}
}

//===========================================================================
void Disassembly_DelData( DisasmData_t tData)
{
	// g_aDisassemblerData.erase( );
	WORD nAddress = tData.nStartAddress;

	DisasmData_t *pData = NULL; // bIsNopcode = false
	int nDataTargets = g_aDisassemblerData.size();

	if( nDataTargets )
	{
		// TODO: Replace with binary search -- should store data in sorted order, via start address
		pData = & g_aDisassemblerData[ 0 ];
		for( int iTarget = 0; iTarget < nDataTargets; iTarget++ )
		{
			if (pData->iDirective != _NOP_REMOVED)
			{
				if ((nAddress >= pData->nStartAddress) && (nAddress < pData->nEndAddress))
				{
					pData->iDirective = _NOP_REMOVED;
				}
			}
			pData++;
		}
		pData = NULL; // bIsNopCode = false
	}
}

