/* snapshot.c: snapshot handling routines
   Copyright (c) 1999-2004 Philip Kendall

   $Id: snapshot.c,v 1.105.2.1 2007/05/09 19:31:02 pak21 Exp $

   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

   Author contact information:

   E-mail: philip-fuse@shadowmagic.org.uk

*/

#include <config.h>

#include <libspectrum.h>

#include "ay.h"
#include "fuse.h"
#include "if2.h"
#include "joystick.h"
#include "machine.h"
#include "memory.h"
#include "scld.h"
#include "slt.h"
#include "snapshot.h"
#include "trdos.h"
#include "ui/ui.h"
#include "ula.h"
#include "utils.h"
#include "z80/z80.h"
#include "zxatasp.h"
#include "zxcf.h"

int snapshot_read( const char *filename )
{
  utils_file file;
  libspectrum_snap *snap;
  int error;

  error = libspectrum_snap_alloc( &snap ); if( error ) return error;

  error = utils_read_file( filename, &file );
  if( error ) { libspectrum_snap_free( snap ); return error; }

  error = libspectrum_snap_read( snap, file.buffer, file.length,
				 LIBSPECTRUM_ID_UNKNOWN, filename );
  if( error ) {
    utils_close_file( &file ); libspectrum_snap_free( snap );
    return error;
  }

  if( utils_close_file( &file ) ) {
    libspectrum_snap_free( snap );
    return 1;
  }

  error = snapshot_copy_from( snap );
  if( error ) { libspectrum_snap_free( snap ); return error; }

  error = libspectrum_snap_free( snap ); if( error ) return error;

  return 0;
}

int
snapshot_read_buffer( const unsigned char *buffer, size_t length,
		      libspectrum_id_t type )
{
  libspectrum_snap *snap; int error;

  error = libspectrum_snap_alloc( &snap ); if( error ) return error;

  error = libspectrum_snap_read( snap, buffer, length, type, NULL );
  if( error ) { libspectrum_snap_free( snap ); return error; }
    
  error = snapshot_copy_from( snap );
  if( error ) { libspectrum_snap_free( snap ); return error; }

  error = libspectrum_snap_free( snap ); if( error ) return error;

  return 0;
}

static int
try_fallback_machine( libspectrum_machine machine )
{
  int error;

  /* If we failed on a +3 or +3e snapshot, try falling back to +2A (in
     case we were compiled without lib765) */
  if( machine == LIBSPECTRUM_MACHINE_PLUS3  ||
      machine == LIBSPECTRUM_MACHINE_PLUS3E    ) {

    error = machine_select( LIBSPECTRUM_MACHINE_PLUS2A );
    if( error ) {
      ui_error( UI_ERROR_ERROR,
		"Loading a %s snapshot, but neither that nor %s available",
		libspectrum_machine_name( machine ),
		libspectrum_machine_name( LIBSPECTRUM_MACHINE_PLUS2A )    );
      return 1;
    } else {
      ui_error( UI_ERROR_WARNING,
		"Loading a %s snapshot, but that's not available. "
		"Using %s instead",
		libspectrum_machine_name( machine ),
		libspectrum_machine_name( LIBSPECTRUM_MACHINE_PLUS2A )  );
    }

  } else {			/* Not trying a +3 or +3e snapshot */
    ui_error( UI_ERROR_ERROR,
	      "Loading a %s snapshot, but that's not available",
	      libspectrum_machine_name( machine ) );
  }
  
  return 0;
}

int
snapshot_copy_from( libspectrum_snap *snap )
{
  int capabilities, error;
  libspectrum_machine machine;

  machine = libspectrum_snap_machine( snap );

  if( machine != machine_current->machine ) {
    error = machine_select( machine );
    if( error ) {
      error = try_fallback_machine( machine ); if( error ) return error;
    }
  } else {
    machine_reset();
  }

  capabilities = machine_current->capabilities;

  error = z80_from_snapshot( snap ); if( error ) return error;
  error = ula_from_snapshot( snap ); if( error ) return error;
  error = ay_from_snapshot( snap, capabilities ); if( error ) return error;
  error = trdos_from_snapshot( snap, capabilities ); if( error ) return error;
  error = memory_ram_from_snapshot( snap, capabilities );
  if( error ) return error;
  error = slt_from_snapshot( snap ); if( error ) return error;
  error = zxatasp_from_snapshot( snap ); if( error ) return error;
  error = zxcf_from_snapshot( snap ); if( error ) return error;
  error = if2_from_snapshot( snap ); if( error ) return error;
  error = scld_from_snapshot( snap, capabilities ); if( error ) return error;
  error = joystick_from_snapshot( snap ); if( error ) return error;

  periph_update();

  return 0;
}

int snapshot_write( const char *filename )
{
  libspectrum_id_t type;
  libspectrum_class_t class;
  libspectrum_snap *snap;
  unsigned char *buffer; size_t length;
  int flags;

  int error;

  /* Work out what sort of file we want from the filename; default to
     .szx if we couldn't guess */
  error = libspectrum_identify_file_with_class( &type, &class, filename, NULL,
						0 );
  if( error ) return error;

  if( class != LIBSPECTRUM_CLASS_SNAPSHOT || type == LIBSPECTRUM_ID_UNKNOWN )
    type = LIBSPECTRUM_ID_SNAPSHOT_SZX;

  error = libspectrum_snap_alloc( &snap ); if( error ) return error;

  error = snapshot_copy_to( snap );
  if( error ) { libspectrum_snap_free( snap ); return error; }

  flags = 0;
  length = 0;
  error = libspectrum_snap_write( &buffer, &length, &flags, snap, type,
				  fuse_creator, 0 );
  if( error ) { libspectrum_snap_free( snap ); return error; }

  if( flags & LIBSPECTRUM_FLAG_SNAPSHOT_MAJOR_INFO_LOSS ) {
    ui_error(
      UI_ERROR_WARNING,
      "A large amount of information has been lost in conversion; the snapshot probably won't work"
    );
  } else if( flags & LIBSPECTRUM_FLAG_SNAPSHOT_MINOR_INFO_LOSS ) {
    ui_error(
      UI_ERROR_WARNING,
      "Some information has been lost in conversion; the snapshot may not work"
    );
  }

  error = libspectrum_snap_free( snap );
  if( error ) { free( buffer ); return 1; }

  error = utils_write_file( filename, buffer, length );
  if( error ) { free( buffer ); return error; }

  free( buffer );

  return 0;

}

int
snapshot_copy_to( libspectrum_snap *snap )
{
  int error;

  libspectrum_snap_set_machine( snap, machine_current->machine );

  error = z80_to_snapshot( snap ); if( error ) return error;
  error = ula_to_snapshot( snap ); if( error ) return error;
  error = ay_to_snapshot( snap ); if( error ) return error;
  error = trdos_to_snapshot( snap ); if( error ) return error;
  error = memory_ram_to_snapshot( snap ); if( error ) return error;
  error = slt_to_snapshot( snap ); if( error ) return error;
  error = zxatasp_to_snapshot( snap ); if( error ) return error;
  error = zxcf_to_snapshot( snap ); if( error ) return error;
  error = if2_to_snapshot( snap ); if( error ) return error;
  error = scld_to_snapshot( snap ); if( error ) return error;
  error = joystick_to_snapshot( snap ); if( error ) return error;

  return 0;
}
