//    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 "cfs_doss.h"
#include "autil.h"

#define SP3TOLONG(dire) ( ( dire.btSizeHi << 16 ) + dire.wSizeLo )

CDosS::CDosS() : CFs()
{
	#ifdef _MEMORY_DUMP_
		printf( "CDosS constructed: %08X\n", this );
	#endif
}

CDosS::~CDosS()
{
	Dismount();
	#ifdef _MEMORY_DUMP_
		printf( "CDosS destructed: %08X\n", this );
	#endif
}

BOOL CDosS::Mount( ADisk* pDisk )
{
	m_iFilesValid = 0;
	m_iFilesInvalid = 0;

	m_pDisk = pDisk;
	m_pRoot = NULL;

	if ( m_pDisk->GetBootSectorCount() != 3 )
	{
 		sprintf( m_szLastError, "DOSS: Strange boot sector!" );
		return FALSE;
	}

	int iSize = m_pDisk->GetBootSectorSize();

	BYTE* pBoot = new BYTE [ iSize ];

	if ( !pBoot )
	{
 		sprintf( m_szLastError, "DOSS: Not enough memory to allocate boot sector!" );
		return FALSE;
	}

	if ( !m_pDisk->GetBootSector( pBoot ) )
	{
		sprintf( m_szLastError, "DOSS: Can't read boot sector because\n%s", m_pDisk->GetLastError() );
		delete [] pBoot;
		return FALSE;
	}

	BYTE btSpDosIdent = pBoot[ 7 ];

	if ( btSpDosIdent != 0x80 )
	{
		sprintf( m_szLastError, "DOSS: Mismatched boot." );
		delete [] pBoot;
		return FALSE;
	}

	m_wMainDirStart = pBoot[ 9 ] + ( pBoot[ 10 ] << 8 );
	//WORD wSectors = pBoot[ 11 ] + ( pBoot[ 12 ] << 8 );
	//WORD wFreeSectors = pBoot[ 13 ] + ( pBoot[ 14 ] << 8 );
	//BYTE btBitmapSectors = pBoot[ 15 ];
	//WORD wFirstBitmapSector = pBoot[ 16 ] + ( pBoot[ 17 ] << 8 );
	//WORD wFreeDataSector = pBoot[ 18 ] + ( pBoot[ 19 ] << 8 );
	//WORD wFreeDirSector = pBoot[ 20 ] + ( pBoot[ 21 ] << 8 );
	//22-29 volume name

	switch( pBoot[ 31 ] )
	{
		case 0:
			m_wSectorSize = 0x100;
			break;

		case 0x80:
			m_wSectorSize = 0x80;
			break;

		default:
		{
 			sprintf( m_szLastError, "DOSS: Unknown sector size %02X!", pBoot[ 31 ] );
			delete [] pBoot;
			return FALSE;
		}

	}

	//BYTE btVersion = pBoot[ 32 ];
	//BYTE btSeqNumber = pBoot[ 38 ];
	//BYTE btRndNumber = pBoot[ 39 ];
	//WORD wDosBootStart = pBoot[ 40 ] + ( pBoot[ 41 ] << 8 );

	delete [] pBoot;

	return ReadDir( m_wMainDirStart, (CDosSDirEntry**)&m_pRoot );
}

int CDosS::GetSectorLinkEntry( int iLink, int iSec )
{
	WORD awSector[ 0x80 ];

	int iCurrLink = iLink;

	int iDivisor = 0;
	switch( m_wSectorSize )
	{
		case 0x80:
			iDivisor = 0x3E;
			break;

		case 0x100:
			iDivisor = 0x7E;
			break;

		default:
			return 0;
			
	}

	int iSecNeeded = ( iSec / iDivisor ) + 1;

	do
	{
		if ( !m_pDisk->ReadSector( awSector, iCurrLink ) )
		{
			sprintf( m_szLastError, "DOSS: Can't read link sector because\n%s", m_pDisk->GetLastError() );
			return 0;
		}

		iSecNeeded--;
		//not endian-safe
		//iCurrLink = awSector[ 0 ];
		iCurrLink = MREAD_LEW( (BYTE*)awSector );
		if ( iSec >= iDivisor )
			iSec -= iDivisor;

	} while( iSecNeeded );
	 
	//not endian-safe
	//return awSector[ iSec + 2 ];
	return MREAD_LEW( (BYTE*)(awSector + iSec + 2 ));
}

BOOL CDosS::ReadDir( int iSectorLink, CDosSDirEntry** ppRoot )
{
	DOSS_DIRENT dire;

	BYTE abtSector[ 0x100 ];

	int iSector = GetSectorLinkEntry( iSectorLink, 0 );

	if ( !m_pDisk->ReadSector( abtSector, iSector ) )
	{
		sprintf( m_szLastError, "DOSS: Can't read dir because\n%s", m_pDisk->GetLastError() );
		return FALSE;
	}

	//not endian safe
	//memcpy( &dire, abtSector, sizeof( DOSS_DIRENT ) );
	BYTE* pTmp = abtSector;
	dire.btFlags = MGET_B( pTmp );
	dire.wSector = MGET_LEW( pTmp );
	dire.wSizeLo = MGET_LEW( pTmp );
	dire.btSizeHi = MGET_B( pTmp );
	memcpy( dire.acAtariName, pTmp, 11 ); pTmp+= 11;
	memcpy( &dire.btDay, pTmp, 6 ); //dirty hack, copying last 6 byte values

	int iDirLen = SP3TOLONG( dire );
	BYTE* pDir = MapFile( iSectorLink, iDirLen );
	if ( !pDir )
	 	return FALSE;

	int iEntries = iDirLen / sizeof( DOSS_DIRENT );

	//if ( iDirLen != iEntries * (int)sizeof( DOSS_DIRENT ) )
	//{
	//	UnMapFile( pDir );
	//	sprintf( m_szLastError, "DOSS: Dir size mismatch! (%04X<>%04X)\n",
	//		iDirLen,
	//		iEntries * (int)sizeof( DOSS_DIRENT ) 
	//	);
	//	return FALSE;
	//}

	CDosSDirEntry* pPrev = NULL;

	for( int i = 1; i < iEntries; i++ )
	{
		
		DOSS_DIRENT dire2;
		BYTE* pTmp = pDir + i * sizeof( DOSS_DIRENT );
		dire2.btFlags = MGET_B( pTmp );
		dire2.wSector = MGET_LEW( pTmp );
		dire2.wSizeLo = MGET_LEW( pTmp );
		dire2.btSizeHi = MGET_B( pTmp );
		memcpy( dire2.acAtariName, pTmp, 11 ); pTmp+= 11;
		memcpy( &dire2.btDay, pTmp, 6 ); //dirty hack, copying last 6 byte values

		CDosSDirEntry* pE = CreateEntry( &dire2 );

		if ( pE && ! ( pE->m_dwFlags & DIRE_DELETED ) )
		{
			if ( *ppRoot )
			{
				pPrev->m_pNext = pE;
				pE->m_pPrev = pPrev;
				pPrev = pE;
			}
			else
			{
				*ppRoot = pE;
				pPrev = pE;
			}

		}

	}

	UnMapFile( pDir );

	return TRUE;
}

void CDosS::Dismount()
{
	DeleteList( m_pRoot );
}

CDosSDirEntry* CDosS::CreateEntry( DOSS_DIRENT* pDire )
{
	CDosSDirEntry* pE = new CDosSDirEntry();

	if ( !pE )
	{
		sprintf( m_szLastError, "DOSS: Can't allocate memory for directory!" );
		return NULL;
	}

	ADos2MsDos( pE->m_szFname, pDire->acAtariName );

	sprintf( pE->m_szAscData, "%02X %04X %02X%04X %02d-%02d-19%02d %2d:%02d.%02d", 
		pDire->btFlags, 
		pDire->wSector, 
		pDire->btSizeHi, 
		pDire->wSizeLo,
		pDire->btMonth,
		pDire->btDay,
		pDire->btYear,
		pDire->btHour,
		pDire->btMinute,
		pDire->btSecond
	);

	pE->m_iLength = ( pDire->btSizeHi << 16 ) + pDire->wSizeLo;
	pE->m_iLinkSector = pDire->wSector;

	if ( pDire->btFlags == 0x10 )
	{
		pE->m_dwFlags |= DIRE_DELETED;
	}

	if ( ! ( pDire->btFlags && 0x08 ) )
	{
		pE->m_dwFlags |= DIRE_DELETED;
	}

	if ( pDire->btFlags & 0x20 )
	{
		pE->m_dwFlags |= DIRE_SUBDIR;

		BOOL bRes = ReadDir( pE->m_iLinkSector, (CDosSDirEntry**)&pE->m_pSubdir );

		if ( !bRes )
		{
			delete pE;
			return NULL;
		}
	}
	else
	{
		if ( pE->m_dwFlags & DIRE_DELETED )
		{
		}
		else if ( ExportFile( NULL, pE ) )
			m_iFilesValid++;
		else
			m_iFilesInvalid++;
	}

	return pE;
}

BOOL CDosS::ExportFile( char* szOutFile, CDirEntry* pDirE )
{
	int hOutfile = -1;

	if ( szOutFile )
	{
		hOutfile = open( szOutFile, O_BINARY | O_CREAT | O_TRUNC | O_WRONLY, 0666 );

		if ( -1 == hOutfile )
		{
			sprintf( m_szLastError, "DOSS: Unable to create file '%s'!", szOutFile );
			return FALSE;
		}
	}

	int iLinkSector = ((CDosSDirEntry*)pDirE ) -> m_iLinkSector;
	int iLength = ((CDosSDirEntry*)pDirE ) -> m_iLength;

	BYTE* pDir = MapFile( iLinkSector, iLength );

	if ( !pDir )
	{
	 	return FALSE;
	}

	if ( -1 != hOutfile )
		write( hOutfile, pDir, iLength );

	UnMapFile( pDir );

	if ( -1 != hOutfile )
		close( hOutfile );

	return TRUE;
}

BYTE* CDosS::MapFile( int iLinkSector, int iLength )
{
	BYTE* pBuff = new BYTE [ iLength ];

	if ( !pBuff )
		return NULL;

	int iLogSector = 0;
	
	BYTE abtBuff[ 0x100 ];
	BYTE * p = pBuff;

	while( iLength )
	{
		int iToCopy = ( iLength > m_wSectorSize ) ? m_wSectorSize : iLength;

		int iSector = GetSectorLinkEntry( iLinkSector, iLogSector );

		if ( !m_pDisk->ReadSector( abtBuff, iSector ) )
		{
				sprintf( m_szLastError, "DOSS: Can't read file sector because\n%s", m_pDisk->GetLastError() );
				delete [] pBuff;
				return 0;
		}

		memcpy( p, abtBuff, iToCopy );
		iLength -= iToCopy;
		p += iToCopy;
		iLogSector++;
	}

	return pBuff;
}

void CDosS::UnMapFile( BYTE * pBuff )
{
	if ( pBuff )
		delete [] pBuff;
}