
// Nes_Emu 0.7.0. http://www.slack.net/~ant/

#include "Nes_File.h"

#include "blargg_endian.h"

/* Copyright (C) 2004-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for
more details. You should have received a copy of the GNU Lesser General
Public License along with this module; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */

#include "blargg_source.h"

// Nes_File_Writer

Nes_File_Writer::Nes_File_Writer()
{
	write_remain = 0;
	depth_ = 0;
}

Nes_File_Writer::~Nes_File_Writer()
{
}
	
blargg_err_t Nes_File_Writer::begin( Auto_File_Writer dw, nes_tag_t tag )
{
	require( !out );
	out = dw;
	RETURN_ERR( out.open_comp() );
	return begin_group( tag );
}

blargg_err_t Nes_File_Writer::begin_group( nes_tag_t tag )
{
	depth_++;
	return write_header( tag, group_begin_size );
}

blargg_err_t Nes_File_Writer::write_header( nes_tag_t tag, long size )
{
	nes_block_t h;
	h.tag = tag;
	h.size = size;
	h.swap();
	return out->write( &h, sizeof h );
}

blargg_err_t Nes_File_Writer::write_block( nes_tag_t tag, void const* data, long size )
{
	RETURN_ERR( write_block_header( tag, size ) );
	return write( data, size );
}

blargg_err_t Nes_File_Writer::write_block_header( nes_tag_t tag, long size )
{
	require( !write_remain );
	write_remain = size;
	return write_header( tag, size );
}

Nes_File_Writer::error_t Nes_File_Writer::write( void const* p, long s )
{
	write_remain -= s;
	require( write_remain >= 0 );
	return out->write( p, s );
}

blargg_err_t Nes_File_Writer::end()
{
	require( depth_ == 1 );
	return end_group();
}

blargg_err_t Nes_File_Writer::end_group()
{
	require( depth_ > 0 );
	depth_--;
	return write_header( group_end_tag, 0 );
}

// Nes_File_Reader

Nes_File_Reader::Nes_File_Reader()
{
	h.tag = 0;
	h.size = 0;
	block_type_ = invalid;
	depth_ = -1;
}

Nes_File_Reader::~Nes_File_Reader()
{
}
	
blargg_err_t Nes_File_Reader::read_block_data( void* p, long s )
{
	long extra = remain();
	if ( s > extra )
		s = extra;
	extra -= s;
	RETURN_ERR( read( p, s ) );
	if ( extra )
		RETURN_ERR( skip( extra ) );
	return 0;
}

blargg_err_t Nes_File_Reader::begin( Auto_File_Reader dr )
{
	require( !in );
	RETURN_ERR( dr.open() );
	in = dr;
	RETURN_ERR( read_header() );
	if ( block_type() != group_begin )
		return "File is wrong type";
	return enter_group();
}

blargg_err_t Nes_File_Reader::read_header()
{
	RETURN_ERR( in->read( &h, sizeof h ) );
	h.swap();
	block_type_ = data_block;
	if ( h.size == group_begin_size )
	{
		block_type_ = group_begin;
		h.size = 0;
	}
	if ( (long) h.tag == group_end_tag )
	{
		block_type_ = group_end;
		h.tag = 0;
	}
	set_remain( h.size );
	return 0;
}

blargg_err_t Nes_File_Reader::next_block()
{
	require( depth() >= 0 );
	switch ( block_type() )
	{
		case group_end:
			require( false );
			return "Tried to go past end of blocks";
		
		case group_begin: {
			int d = 1;
			do
			{
				RETURN_ERR( skip( h.size ) );
				RETURN_ERR( read_header() );
				if ( block_type() == group_begin )
					d++;
				if ( block_type() == group_end )
					d--;
			}
			while ( d > 0);
			break;
		}
		
		case data_block:
			RETURN_ERR( skip( h.size ) );
			break;
		
		case invalid:
			break;
	}
	return read_header();
}

blargg_err_t Nes_File_Reader::enter_group()
{
	require( block_type() == group_begin );
	block_type_ = invalid; // cause next_block() not to skip group
	depth_++;
	return 0;
}

blargg_err_t Nes_File_Reader::exit_group()
{
	require( depth() > 0 );
	int d = 1;
	while ( true )
	{
		if ( block_type() == group_end )
			d--;
		if ( block_type() == group_begin )
			d++;
		if ( d == 0 )
			break;
		RETURN_ERR( skip( h.size ) );
		RETURN_ERR( read_header() );
	}
	
	block_type_ = invalid; // cause next_block() to read past end block
	depth_--;
	return 0;
}

blargg_err_t Nes_File_Reader::skip_v( int s )
{
	require( block_type() == data_block );
	if ( (unsigned long) s > h.size )
		return "Tried to skip past end of data";
	h.size -= s;
	set_remain( h.size );
	return in->skip( s );
}

blargg_err_t Nes_File_Reader::read_v( void* p, int n )
{
	require( block_type() == data_block );
	if ( (unsigned long) n > h.size )
		n = h.size;
	h.size -= n;
	set_remain( h.size );
	return in->read( p, n );
}
