//----------------------------------------------------------------------------
//
// File:        disk-serializer-hfe.cpp
// Date:        04-May-2015
// Programmer:  Marc Rousseau
//
// Description: A class to load/save hfe 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 <memory.h>
#include "common.hpp"
#include "logger.hpp"
#include "disk-media.hpp"
#include "disk-serializer-hfe.hpp"
#include "file-system-disk.hpp"
#include "disk-util.hpp"
#include "bitstream.hpp"
#include "support.hpp"

DBG_REGISTER( __FILE__ );

bool BuildTrack( const std::list<sDataFragment> &fragments, std::vector<int> *clock, std::vector<UINT8> *data )
{
	clock->clear( );
	data->clear( );

	int lastBitOffset = 0;

	for( auto &fragment : fragments )
	{
		int lostBits = fragment.bitOffsetStart - lastBitOffset;

		while( lostBits > 16 )
		{
			lostBits -= 16;
			data->push_back( 0x00 );
		}

		if( fragment.clock != -1 )
		{
			clock->push_back( data->size( ));
		}

		data->insert( data->end( ), fragment.byteData.begin( ), fragment.byteData.end( ));

		lastBitOffset = fragment.bitOffsetEnd;
	}

	return true;
}

bool DecodeFM( const std::vector<UINT8> &encodedData, std::vector<int> *clock, std::vector<UINT8> *data )
{
	cBitStreamLSB bitstream{ encodedData, encodedData.size( ) * 8, true };

	auto fragments = ProcessDataFM( bitstream );

	return BuildTrack( fragments, clock, data );
}

bool DecodeMFM( const std::vector<UINT8> &encodedData, std::vector<int> *clock, std::vector<UINT8> *data )
{
	cBitStreamLSB bitstream{ encodedData, encodedData.size( ) * 8, false };

	auto fragments = ProcessDataMFM( bitstream );

	return BuildTrack( fragments, clock, data );
}

cDiskSerializerHFE::cDiskSerializerHFE( ) :
	cBaseObject( "cDiskSerializerHFE" ),
	cDiskSerializer( )
{
}

cDiskSerializerHFE::~cDiskSerializerHFE( )
{
}

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

	fseek( file, 0, SEEK_SET );

	HxC::FileHeader header;
	if( fread( &header, sizeof( header ), 1, file ) != 1 )
	{
		return false;
	}

	// Look for the a proper HxC signature
	if( memcmp( header.signature, HxC::HEADER_SIGNATURE, 8 ) == 0 )
	{
		// Make sure it's either FM or MFM encoded
		if(( static_cast<HxC::ENCODING>( header.trackEncoding ) == HxC::ENCODING::ISOIBM_MFM ) ||
		   ( static_cast<HxC::ENCODING>( header.trackEncoding ) == HxC::ENCODING::ISOIBM_FM ))
		{
			// Last sanity check
			if(( header.numTracks <= 80 ) && ( header.numSides <= 2 ))
			{
				return true;
			}
		}
	}

	return false;
}

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

	// TODO

	return true;
}

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

	return FORMAT_HFE;
}

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

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

	std::vector<UINT8> buffer( size );
	if( fread( buffer.data( ), size, 1, file ) != 1 )
	{
		return false;
	}

	auto header = reinterpret_cast<const HxC::FileHeader *>( buffer.data( ));

	// Double check the signature
	if( memcmp( header->signature, HxC::HEADER_SIGNATURE, 8 ) == 0 )
	{
		HxC::ENCODING encoding = static_cast<HxC::ENCODING>( header->trackEncoding );

		track::Format format = ( encoding == HxC::ENCODING::ISOIBM_FM ) ? track::Format::FM : track::Format::MFM;

		auto decode = ( encoding == HxC::ENCODING::ISOIBM_FM ) ? DecodeFM : DecodeMFM;

		// Clear any old data & prepare for a new image
		image->AllocateTracks( header->numTracks, header->numSides );

		auto trackLUT = reinterpret_cast<const HxC::pictrack *>( buffer.data( ) + 0x200 );
		for( int t = 0; t < header->numTracks; t++ )
		{
			if( trackLUT[ t ].trackLength > 0 )
			{
				int chunks = ( trackLUT[ t ].trackLength + 511 ) / 512;

				std::vector<UINT8> trackS0;
				std::vector<UINT8> trackS1;

				trackS0.reserve( chunks * 256 );
				trackS1.reserve( chunks * 256 );

				for( int i = 0; i < chunks; i++ )
				{
					const UINT8 *start = buffer.data( ) + ( trackLUT[ t ].offset + i ) * 512;
					trackS0.insert( trackS0.end( ), start, start + 256 );
					start += 256;
					trackS1.insert( trackS1.end( ), start, start + 256 );
				}

				trackS0.resize( trackLUT[ t ].trackLength / 2 );
				trackS1.resize( trackLUT[ t ].trackLength / 2 );

				std::vector<int> clock;
				std::vector<UINT8> data;

				decode( trackS0, &clock, &data );
				image->GetTrack( t, 0 )->RawWrite( format, clock, data );

				if( header->numSides == 2 )
				{
					decode( trackS1, &clock, &data );
					image->GetTrack( t, 1 )->RawWrite( format, clock, data );
				}
			}
		}

		DBG_EVENT( "Disk loaded" );

		return true;
	}

	return false;
}

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

	return false;
}
