/* zxcf.c: ZXCF interface routines
   Copyright (c) 2003-2005 Garry Lancaster,
		 2004-2005 Philip Kendall
		 
   $Id: zxcf.c,v 1.16 2007/02/02 16:21:52 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 Kendall <philip-fuse@shadowmagic.org.uk>

*/

#include <config.h>

#include <string.h>

#include <libspectrum.h>

#include "ide.h"
#include "machine.h"
#include "memory.h"
#include "periph.h"
#include "settings.h"
#include "ui/ui.h"
#include "zxcf.h"

/*
  TBD: Allow memory size selection (128K/512K/1024K)
  TBD: Add secondary channel
*/


/* Private function prototype */

static void set_zxcf_bank( int bank );
static libspectrum_byte zxcf_memctl_read( libspectrum_word port,
					  int *attached );
static void zxcf_memctl_write( libspectrum_word port, libspectrum_byte data );
static libspectrum_byte zxcf_ide_read( libspectrum_word port, int *attached );
static void zxcf_ide_write( libspectrum_word port, libspectrum_byte data );

/* Data */

const periph_t zxcf_peripherals[] = {
  { 0x10f4, 0x10b4, zxcf_memctl_read, zxcf_memctl_write },
  { 0x10f4, 0x00b4, zxcf_ide_read, zxcf_ide_write },
};

const size_t zxcf_peripherals_count =
  sizeof( zxcf_peripherals ) / sizeof( periph_t );

static int zxcf_writeenable;

static libspectrum_ide_channel *zxcf_idechn;

#define ZXCF_PAGES 64
#define ZXCF_PAGE_LENGTH 0x4000
static libspectrum_byte ZXCFMEM[ ZXCF_PAGES ][ ZXCF_PAGE_LENGTH ];

static libspectrum_byte last_memctl;

/* Housekeeping functions */

int
zxcf_init( void )
{
  int error;

  last_memctl = 0x00;
                                
  error = libspectrum_ide_alloc( &zxcf_idechn, LIBSPECTRUM_IDE_DATA16 );
  if( error ) return error;

  ui_menu_activate( UI_MENU_ITEM_MEDIA_IDE_ZXCF_EJECT, 0 );

  if( settings_current.zxcf_pri_file ) {
    error = libspectrum_ide_insert( zxcf_idechn, LIBSPECTRUM_IDE_MASTER,
				    settings_current.zxcf_pri_file );
    if( error ) return error;
    ui_menu_activate( UI_MENU_ITEM_MEDIA_IDE_ZXCF_EJECT, 1 );
  }

  return 0;
}

int
zxcf_end( void )
{
  return libspectrum_ide_free( zxcf_idechn );
}

void
zxcf_reset( void )
{
  if( !settings_current.zxcf_active ) return;

  machine_current->ram.romcs = 1;

  set_zxcf_bank( 0 );
  zxcf_writeenable = 0;
  machine_current->memory_map();

  libspectrum_ide_reset( zxcf_idechn );
}

static int
zxcf_commit_wrapper( libspectrum_ide_unit unit )
{
  if( unit != LIBSPECTRUM_IDE_MASTER ) {
    ui_error( UI_ERROR_ERROR, "%s:%d: unit is %d, not LIBSPECTRUM_IDE_MASTER",
	      __FILE__, __LINE__, unit );
    abort();
  }

  return zxcf_commit();
}

int
zxcf_insert( const char *filename )
{
  return ide_insert( filename, zxcf_idechn, LIBSPECTRUM_IDE_MASTER,
		     zxcf_commit_wrapper, &settings_current.zxcf_pri_file,
		     UI_MENU_ITEM_MEDIA_IDE_ZXCF_EJECT );
}

int
zxcf_commit( void )
{
  int error;

  error = libspectrum_ide_commit( zxcf_idechn, LIBSPECTRUM_IDE_MASTER );

  return error;
}

int
zxcf_eject( void )
{
  return ide_eject( zxcf_idechn, LIBSPECTRUM_IDE_MASTER, zxcf_commit_wrapper,
		    &settings_current.zxcf_pri_file,
		    UI_MENU_ITEM_MEDIA_IDE_ZXCF_EJECT );
}

static void
set_zxcf_bank( int bank )
{
  memory_page *page;
  size_t i, offset;

  for( i = 0; i < 2; i++ ) {

    page = &memory_map_romcs[i];
    offset = i & 1 ? MEMORY_PAGE_SIZE : 0x0000;
    
    page->page = &ZXCFMEM[ bank ][ offset ];
    page->writable = zxcf_writeenable;
    page->contended = 0;
    
    page->page_num = bank;
    page->offset = offset;
  }
}  

/* Port read/writes */

static libspectrum_byte
zxcf_memctl_read( libspectrum_word port GCC_UNUSED, int *attached )
{
  if( !settings_current.zxcf_active ) return 0xff;

  *attached = 1;

  return 0xff;
}

static void
zxcf_memctl_write( libspectrum_word port GCC_UNUSED, libspectrum_byte data )
{
  if( !settings_current.zxcf_active ) return;

  last_memctl = data;

  /* Bit 7 MEMOFF: 0=mem on, 1 =mem off */
  machine_current->ram.romcs = ( data & 0x80 ) ? 0 : 1;

  /* Bit 6 /MWRPROT: 0=mem protected, 1=mem writable */
  zxcf_writeenable = ( data & 0x40 ) ? 1 : 0;
  
  /* Bits 5-0: MEMBANK */
  set_zxcf_bank( data & 0x3f );

  machine_current->memory_map();
}

libspectrum_byte
zxcf_last_memctl( void )
{
  return last_memctl;
}

static libspectrum_byte
zxcf_ide_read( libspectrum_word port, int *attached )
{
  libspectrum_ide_register idereg = ( port >> 8 ) & 0x07;
  
  if( !settings_current.zxcf_active ) return 0xff;

  *attached = 1;

  return libspectrum_ide_read( zxcf_idechn, idereg ); 
}

static void
zxcf_ide_write( libspectrum_word port, libspectrum_byte data )
{
  libspectrum_ide_register idereg;
  
  if( !settings_current.zxcf_active ) return;

  idereg = ( port >> 8 ) & 0x07;
  libspectrum_ide_write( zxcf_idechn, idereg, data ); 
}

void
zxcf_memory_map( void )
{
  if( !settings_current.zxcf_upload ) {
    memory_map_read[0] = memory_map_romcs[0];
    memory_map_read[1] = memory_map_romcs[1];
  }

  memory_map_write[0] = memory_map_romcs[0];
  memory_map_write[1] = memory_map_romcs[1];
}

int
zxcf_from_snapshot( libspectrum_snap *snap )
{
  size_t i;

  if( !libspectrum_snap_zxcf_active( snap ) ) return 0;

  settings_current.zxcf_active = 1;
  settings_current.zxcf_upload = libspectrum_snap_zxcf_upload( snap );

  zxcf_memctl_write( 0x10bf, libspectrum_snap_zxcf_memctl( snap ) );

  for( i = 0; i < libspectrum_snap_zxcf_pages( snap ); i++ )
    if( libspectrum_snap_zxcf_ram( snap, i ) )
      memcpy( ZXCFMEM[ i ], libspectrum_snap_zxcf_ram( snap, i ),
	      ZXCF_PAGE_LENGTH );
  
  return 0;
}

int
zxcf_to_snapshot( libspectrum_snap *snap )
{
  size_t i;
  libspectrum_byte *buffer;

  if( !settings_current.zxcf_active ) return 0;

  libspectrum_snap_set_zxcf_active( snap, 1 );
  libspectrum_snap_set_zxcf_upload( snap, settings_current.zxcf_upload );
  libspectrum_snap_set_zxcf_memctl( snap, last_memctl );
  libspectrum_snap_set_zxcf_pages( snap, ZXCF_PAGES );

  for( i = 0; i < ZXCF_PAGES; i++ ) {

    buffer = malloc( ZXCF_PAGE_LENGTH * sizeof( libspectrum_byte ) );
    if( !buffer ) {
      ui_error( UI_ERROR_ERROR, "Out of memory at %s:%d", __FILE__, __LINE__ );
      return 1;
    }

    memcpy( buffer, ZXCFMEM[ i ], ZXCF_PAGE_LENGTH );
    libspectrum_snap_set_zxcf_ram( snap, i, buffer );
  }

  return 0;
}
