//----------------------------------------------------------------------------
//
// File:        disk-serializer-v9t9.cpp
// Date:        04-May-2015
// Programmer:  Marc Rousseau
//
// Description: A class to load/save v9t9 disk images
//
// Copyright (c) 2015 Marc Rousseau, All Rights Reserved.
//
// 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
// (at your option) 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307, USA.
//
// Revision History:
//
//----------------------------------------------------------------------------

#include <cstring>
#include "common.hpp"
#include "logger.hpp"
#include "disk-media.hpp"
#include "disk-serializer-v9t9.hpp"
#include "idisk-track.hpp"
#include "file-system-disk.hpp"

DBG_REGISTER( __FILE__ );

constexpr int DISK_SIZE( int cylinders, int heads, int sectors )
{
	return cylinders * heads * sectors * DEFAULT_SECTOR_SIZE;
}

cDiskSerializerV9T9::cDiskSerializerV9T9( ) :
	cBaseObject( "cDiskSerializerV9T9" )
{
}

cDiskSerializerV9T9::~cDiskSerializerV9T9( )
{
}

bool cDiskSerializerV9T9::MatchesFormat( FILE *file )
{
	FUNCTION_ENTRY( nullptr, "cDiskSerializerV9T9::MatchesFormat", true );

	fseek( file, 0L, SEEK_END );
	UINT32 size = ftell( file );
	fseek( file, 0L, SEEK_SET );

	char buffer[ DEFAULT_SECTOR_SIZE ];

	// Read the 1st sector from the disk to get the VIB
	if( fread( buffer, sizeof( buffer ), 1, file ) != 1 )
	{
		DBG_ERROR( "Error reading from file" );
		return false;
	}

	if( strncmp( buffer + 0x0D, "DSK", 3 ) != 0 )
	{
		return false;
	}

	int totalSectors = size / DEFAULT_SECTOR_SIZE;

	VIB *vib      = reinterpret_cast< VIB * >( buffer );
	int noSectors = ( vib->SectorsPerTrack != 0 ) ? vib->SectorsPerTrack : 9;

	// Make sure it's an integral number of sectors
	if((( size % DEFAULT_SECTOR_SIZE ) != 0 ) ||
	   (( totalSectors % noSectors ) != 0 ))
	{
		return false;
	}

	return true;
}

bool cDiskSerializerV9T9::SupportsFeatures( const cDiskImage &image )
{
	FUNCTION_ENTRY( this, "cDiskSerializerV9T9::SupportsFeatures", true );

	// Look for features that will get lost/destroyed if saved in raw sector format
	for( int h = 0; h < image.GetNumHeads( ); h++ )
	{
		for( int t = 0; t < image.GetNumTracks( ); t++ )
		{
			int c = ( h == 0 ) ? t : image.GetNumTracks( ) - ( t + 1 );

			const iDiskTrack *track = image.GetTrack( c, h );

			size_t sectors = 0;

			for( int s = 0; s < MAX_SECTORS; s++ )
			{
				const iDiskSector *sector = track->GetSector( c, h, s );
				if( sector == nullptr )
				{
					break;
				}
				sectors++;
				// Look for non-standard data address marks
				if(( sector->LogicalSize( ) != 1 ) || ( sector->DataMark( ) != MARK_DAM ))
				{
					return false;
				}
			}

			// Look for raw track without valid index or data address marks or extra/missing sectors
			if(( sectors == 0 ) && ( sectors != track->GetSectors( ).size( )))
			{
				return false;
			}
		}
	}

	return true;
}

eDiskFormat cDiskSerializerV9T9::GetFormat( ) const
{
	FUNCTION_ENTRY( this, "cDiskSerializerV9T9::GetFormat", true );

	return FORMAT_RAW_SECTOR;
}

//----------------------------------------------------------------------------
//
// Read disk files that contain raw sector data.  This is the format used by
// v9t9.  This code assumes that these files are 'well behaved'.  This means
// that the assumption is made that they are standard disks with no irregular
// properties (i.e. it contains 9/16/18 256 byte sectors per track).
//
//----------------------------------------------------------------------------

bool cDiskSerializerV9T9::ReadFile( FILE *file, cDiskImage *image )
{
	FUNCTION_ENTRY( this, "cDiskSerializerV9T9::ReadFile", true );

	fseek( file, 0L, SEEK_END );
	UINT32 size = ftell( file );
	fseek( file, 0L, SEEK_SET );

	int totalSectors = size / DEFAULT_SECTOR_SIZE;

	char buffer[ DEFAULT_SECTOR_SIZE ];

	// Read the 1st sector from the disk to get the VIB
	if( fread( buffer, sizeof( buffer ), 1, file ) != 1 )
	{
		DBG_ERROR( "Error reading from file" );
		return false;
	}

	VIB *vib      = reinterpret_cast< VIB * >( buffer );
	int noSectors = ( vib->SectorsPerTrack != 0 ) ? vib->SectorsPerTrack : 9;
	int noTracks  = totalSectors / noSectors;

	// Some TI disks don't have accurate # sides & format
	int noSides          = ( noTracks > MAX_TRACKS_LO ) ? 2 : 1;
	track::Format format = ( noSectors == 9 ) ? track::Format::FM : track::Format::MFM;

	// Correct the number of tracks
	noTracks /= noSides;

	// Clear any old data & prepare for a new image
	image->FormatDisk( noTracks, noSides, noSectors, format );

	fseek( file, 0L, SEEK_SET );

	for( int h = 0; h < noSides; h++ )
	{
		for( int t = 0; t < noTracks; t++ )
		{
			int c = ( h == 0 ) ? t : noTracks - ( t + 1 );

			iDiskTrack *track = image->GetTrack( c, h );

			for( int s = 0; s < noSectors; s++ )
			{
				if( fread( buffer, sizeof( buffer ), 1, file ) != 1 )
				{
					DBG_ERROR( "Error reading from file" );
					return false;
				}

				iDiskSector *sector = track->GetSector( c, h, s );

				sector->Write( { buffer, buffer + DEFAULT_SECTOR_SIZE } );
			}
		}
	}

	DBG_EVENT( "Disk loaded" );

	return true;
}

bool cDiskSerializerV9T9::WriteFile( const cDiskImage &image, FILE *file )
{
	FUNCTION_ENTRY( this, "cDiskSerializerV9T9::WriteFile", true );

	for( int h = 0; h < image.GetNumHeads( ); h++ )
	{
		for( int t = 0; t < image.GetNumTracks( ); t++ )
		{
			int c = ( h == 0 ) ? t : image.GetNumTracks( ) - ( t + 1 );

			auto track = image.GetTrack( c, h );

			for( int s = 0; s < MAX_SECTORS; s++ )
			{
				const iSector *sector = track->GetSector( -1, -1, s );
				if( sector == nullptr )
				{
					continue;
				}
				if( sector->GetData( ) == nullptr )
				{
					break;
				}
					if( fwrite( sector->GetData( ), sector->Size( ), 1, file ) != 1 )
				{
					DBG_ERROR( "Error writing to file" );
					return false;
				}
			}
		}
	}

	return true;
}
