//    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
//    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., 675 Mass Ave, Cambridge, MA 02139, USA.
//

#include "pub.def"
#include "jintypes.h"
#include "autil.h"
#include "adsk.h"

DISK_TYPE g_dtypeOut = DISK_ATR;
BOOL g_bClassic = FALSE;
#define MAX_STRING 256
char g_szMessage[ MAX_STRING ] = "";

typedef struct
{
	WORD w1;
	WORD w2;
	WORD w3;
	WORD w4;
	WORD w5;
	WORD w6;
	WORD w7;
} BASHEAD;

#define BAS_HEAD_SIZE 0x0E

#define SHEADER PRG_NAME " v" PRG_VERSION " (c) " PRG_COPYRIGHT " " PRG_AUTHOR "\n"

#define HEADER SHEADER \
   PRG_DESC "\n" \
	"  Latest version can be found at " PRG_URL "\n" \
	"  Published under GPL. See GPL.TXT.\n\n"

#define USAGE HEADER "Usage:  " PRG_NAME " " PRG_USAGE

#include "switches.cpp"

int main( int argc, char* argv[] )
{
	setbuf( stdout, NULL );
	setbuf( stderr, NULL );

	if ( !SWITCHES_Init( &argc, argv ) )
		return 1;

	if ( argc < 2 )
	{
		SWFN_HELP( USAGE );
		return 1;
	}

	fprintf( stderr, SHEADER );

	char* szInFile = argv[ 1 ];
	char szOutFile[ MAX_STRING ];

	//setting the message
	char szMessage[ MAX_STRING ];
	strcpy( szMessage, g_szMessage );

	//if not present, set to 'Loading prgname'
	if ( ! *szMessage )
	{
		strcpy( szMessage, "Loading " );
		strcat( szMessage, szInFile );
	}

	//and pad it with spaces
	memset( szMessage + strlen( szMessage), 0x20, MAX_STRING - strlen( szMessage ) );
	szMessage[ MAX_STRING - 1 ] = '\0';

	//get output filename
	if ( argc >= 3 )
	{
		strcpy( szOutFile, argv[ 2 ] );
	}
	else
	{
		GuessBestFnameFromPC( szOutFile, szInFile, GetDiskTypeExt( g_dtypeOut ) );
	}

	ADisk* pDisk = NULL;
	switch( g_dtypeOut )
	{
		case DISK_ATR:
			pDisk = new CAtr();
			break;

		case DISK_XFD:
			pDisk = new CXfd();
			break;

		case DISK_SCP:
			pDisk = new CScp();
			break;

		case DISK_DCM:
			pDisk = new CDcm();
			break;

		case DISK_DI:
			pDisk = new CDi();
			break;

		default:
			break;
	}

	if ( !pDisk )
	{
		printf( "Can't create such image!\n" );
		return FALSE;
	}

	//reads boot stub
	char szBootName[ MAX_STRING ];
	strcpy( szBootName, argv[ 0 ] );
	char* szWhere = strrchr( szBootName, '/' );

	if ( szWhere )
		szWhere++;
	else
		szWhere = szBootName;
		
	strcpy( szWhere, "bas2boot.boo" );

	CFile cf;

	if ( !cf.Open( szBootName ) )
	{
		printf( "Can't open boot image: %s\n", szBootName );
		return 1;
	}

	long lBootLen = cf.GetLength();

	if ( lBootLen % 0x80 )
	{
		printf( "Invalid boot image: %s\n", szBootName );
		cf.Close();
		return 1;
	}

	BYTE* pbtBoot = new BYTE [ lBootLen ];

	cf.Read( pbtBoot, lBootLen );

	cf.Close();

	int iBootSectors = lBootLen / 0x80;

	if ( pbtBoot[ 1 ] != iBootSectors )
	{
		printf( "Invalid boot image: %s\nSize mismatch.\n", szBootName );
		cf.Close();
		delete [] pbtBoot;
		return 1;
	}

	//open basic file
	if ( !cf.Open( szInFile ) )
	{
		printf( "Can't open source file '%s'!\n", szInFile );
		return 1;
	}

	long lBasicLen = cf.GetLength();

	//set real length in header
	pbtBoot[ 8 ] = lBasicLen & 0xFF;
	pbtBoot[ 9 ] = lBasicLen >> 8;

	lBasicLen -= BAS_HEAD_SIZE;

	int iBasicSectors = ( lBasicLen + 0x7F ) / 0x80;

	//load and relocate basic header
	BASHEAD head;
	head.w1 = cf.readLEw();
	head.w2 = cf.readLEw() - head.w1;
	head.w3 = cf.readLEw() - head.w1;
	head.w4 = cf.readLEw() - head.w1;
	head.w5 = cf.readLEw() - head.w1;
	head.w6 = cf.readLEw() - head.w1;
	head.w7 = cf.readLEw() - head.w1;

	BYTE* pbtHd = pbtBoot + lBootLen - BAS_HEAD_SIZE - 2;

	*( pbtHd++ ) = ( iBasicSectors - 1 ) & 0xFF;
	*( pbtHd++ ) = ( iBasicSectors - 1 ) >>8;

	*( pbtHd++ ) = head.w1 & 0xFF;
	*( pbtHd++ ) = head.w1 >> 8;

	*( pbtHd++ ) = head.w2 & 0xFF;
	*( pbtHd++ ) = head.w2 >> 8;

	*( pbtHd++ ) = head.w3 & 0xFF;
	*( pbtHd++ ) = head.w3 >> 8;

	*( pbtHd++ ) = head.w4 & 0xFF;
	*( pbtHd++ ) = head.w4 >> 8;

	*( pbtHd++ ) = head.w5 & 0xFF;
	*( pbtHd++ ) = head.w5 >> 8;

	*( pbtHd++ ) = head.w6 & 0xFF;
	*( pbtHd++ ) = head.w6 >> 8;

	*( pbtHd++ ) = head.w7 & 0xFF;
	*( pbtHd++ ) = head.w7 >> 8;

	//find empty space at the end of boot
	BYTE* pbtString = pbtBoot + lBootLen - BAS_HEAD_SIZE - 2 - 2;
	BYTE* pbtStringEnd = pbtString;

	while( *pbtString == 0x20 )
		pbtString--;

	pbtString++;

	int iStringLen = pbtStringEnd - pbtString;

	//put the message there
	memcpy( pbtString, szMessage, iStringLen + 1 );

	//read basic program
	BYTE* pbtBas = new BYTE [ iBasicSectors * 0x80 ];
	memset( pbtBas, 0, iBasicSectors * 0x80 );

	cf.Read( pbtBas, lBasicLen );

	cf.Close();

	//format disk
	DISK_GEOMETRY dg;
	
	if ( g_bClassic )
		ForceClassicSize( iBasicSectors + iBootSectors, 0x80, &dg );
	else
		GuessClassicSizes( iBasicSectors + iBootSectors, 0x80, &dg );

	if ( !pDisk->Format( &dg ) )
	{
		delete pDisk;
		delete[] pbtBoot;
		delete [] pbtBas;
		return 1;
	}

	//write boot & body
	pDisk->WriteSectors( 1, pbtBoot, iBootSectors );
	pDisk->WriteSectors( 1 + iBootSectors, pbtBas, iBasicSectors );

	//save it to disk
	if ( !pDisk->Save( szOutFile, FALSE ) )
	{
		printf( "Error! %s\n", pDisk->GetLastError() );
		delete pDisk;
		delete [] pbtBoot;
		delete [] pbtBas;
		return 1;
	}

	delete pDisk;

	delete [] pbtBoot;
	delete [] pbtBas;

	printf( "Written to %s (%s)\n", szOutFile, GetDiskTypeName( g_dtypeOut ) );

	return 0;
}