/* ScummVM - Scumm Interpreter
 * Dreamcast port
 * Copyright (C) 2002  Marcus Comstedt
 *
 * 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.
 *
 * $Header: /cvsroot/scummvm/scummvm/dc/vmsave.cpp,v 1.4 2002/07/02 20:57:27 fingolfin Exp $
 *
 */

#include "stdafx.h"
#include "scumm.h"
#include "mididrv.h"
#include "gameDetector.h"
#include "dc.h"
#include "icon.h"
#include "saveload.h"

#include <ronin/zlib.h>


// Savegame can not be bigger than this, even before compression
#define MAX_SAVE_SIZE (128*1024)


enum vmsaveResult {
  VMSAVE_OK,
  VMSAVE_NOVM,
  VMSAVE_NOSPACE,
  VMSAVE_WRITEERROR,  
};


static int lastvm=-1;

static vmsaveResult trySave(GameDetector *d, const char *data, int size,
			    const char *filename, class Icon &icon, int vm)
{
  struct vmsinfo info;
  struct superblock super;
  struct vms_file file;
  struct vms_file_header header;
  struct timestamp tstamp;
  struct tm tm;
  time_t t;
  unsigned char iconbuffer[512+32];

  if(!vmsfs_check_unit(vm, 0, &info))
    return VMSAVE_NOVM;
  if(!vmsfs_get_superblock(&info, &super))
    return VMSAVE_NOVM;
  int free_cnt = vmsfs_count_free(&super);
  if(vmsfs_open_file(&super, filename, &file))
    free_cnt += file.blks;
  if(((128+512+size+511)>>9) > free_cnt)
    return VMSAVE_NOSPACE;

  memset(&header, 0, sizeof(header));
  strncpy(header.shortdesc, "ScummVM savegame", 16);
  char *game_name = d->getGameName();
  strncpy(header.longdesc, game_name, 32);
  free(game_name);
  strncpy(header.id, "ScummVM", 16);
  icon.create_vmicon(iconbuffer);
  header.numicons = 1;
  memcpy(header.palette, iconbuffer, sizeof(header.palette));
  time(&t);
  tm = *localtime(&t);
  tstamp.year = tm.tm_year+1900;
  tstamp.month = tm.tm_mon+1;
  tstamp.day = tm.tm_mday;
  tstamp.hour = tm.tm_hour;
  tstamp.minute = tm.tm_min;
  tstamp.second = tm.tm_sec;
  tstamp.wkday = (tm.tm_wday+6)%7;

  vmsfs_beep(&info, 1);

  vmsfs_errno = 0;
  if(!vmsfs_create_file(&super, filename, &header,
			iconbuffer+sizeof(header.palette), NULL,
			data, size, &tstamp)) {
    fprintf(stderr, "%s\n", vmsfs_describe_error());
    vmsfs_beep(&info, 0);
    return VMSAVE_WRITEERROR;
  }

  vmsfs_beep(&info, 0);
  return VMSAVE_OK;
}

static bool tryLoad(char *&buffer, int &size, const char *filename, int vm)
{
  struct vmsinfo info;
  struct superblock super;
  struct vms_file file;
  struct vms_file_header header;
  struct timestamp tstamp;
  struct tm tm;
  time_t t;
  unsigned char iconbuffer[512+32];

  if(!vmsfs_check_unit(vm, 0, &info))
    return false;
  if(!vmsfs_get_superblock(&info, &super))
    return false;
  if(!vmsfs_open_file(&super, filename, &file))
    return false;
  
  buffer = new char[size = file.size];

  if(vmsfs_read_file(&file, (unsigned char *)buffer, size))
    return true;

  delete buffer;
  return false;
}

vmsaveResult writeSaveGame(GameDetector *d, const char *data, int size,
			   const char *filename, class Icon &icon)
{
  vmsaveResult r, res = VMSAVE_NOVM;

  if(lastvm >= 0 &&
     (res = trySave(d, data, size, filename, icon, lastvm)) == VMSAVE_OK)
    return res;

  for(int i=0; i<24; i++)
    if((r = trySave(d, data, size, filename, icon, i)) == VMSAVE_OK) {
      lastvm = i;
      return r;
    } else if(r > res)
      res = r;

  return res;
}

bool readSaveGame(char *&buffer, int &size, const char *filename)
{
  if(lastvm >= 0 &&
     tryLoad(buffer, size, filename, lastvm))
    return true;

  for(int i=0; i<24; i++)
    if(tryLoad(buffer, size, filename, i)) {
      lastvm = i;
      return true;
    }

  return false;
}


struct vmStreamContext {
  bool issave;
  char *buffer;
  int pos, size;
  char filename[16];
};

bool SerializerStream::fopen(const char *filename, const char *mode)
{
  vmStreamContext *c = new vmStreamContext;
  context = c;
  if(strchr(mode, 'w')) {
    c->issave = true;
    strncpy(c->filename, filename, 16);
    c->pos = 0;
    c->buffer = new char[c->size = MAX_SAVE_SIZE];
    return true;
  } else if(readSaveGame(c->buffer, c->size, filename)) {
    if(c->size > 0 && c->buffer[0] != 'S') {
      // Data does not start with "SCVM".  Maybe compressed?
      char *expbuf = new char[MAX_SAVE_SIZE];
      unsigned long destlen = MAX_SAVE_SIZE;
      if(!uncompress((Bytef*)expbuf, &destlen, (Bytef*)c->buffer, c->size)) {
	delete(c->buffer);
	c->buffer = expbuf;
	c->size = destlen;
      } else delete expbuf;
    }
    c->issave = false;
    c->pos = 0;
    return true;
  } else {
    delete c;
    context = NULL;
    return false;
  }
}

void SerializerStream::fclose()
{
  extern GameDetector detector;
  extern Icon icon;

  if(context) {
    vmStreamContext *c = (vmStreamContext *)context;
    if(c->issave) {
      if(c->pos) {
	// Try compression
	char *compbuf = new char[c->pos];
	unsigned long destlen = c->pos;
	if(!compress((Bytef*)compbuf, &destlen, (Bytef*)c->buffer, c->pos)) {
	  delete c->buffer;
	  c->buffer = compbuf;
	  c->pos = destlen;
	} else delete compbuf;
      }
      writeSaveGame(&detector, c->buffer, c->pos,
		    c->filename, icon);
    }
    delete c->buffer;
    delete c;
    context = NULL;
  }
}

int SerializerStream::fread(void *buf, int size, int cnt)
{
  vmStreamContext *c = (vmStreamContext *)context;

  if (!c || c->issave)
    return -1; 

  int nbyt = size*cnt;
  if (c->pos + nbyt > c->size) {
    cnt = (c->size - c->pos)/size;
    nbyt = size*cnt;
  }
  if (nbyt)
    memcpy(buf, c->buffer + c->pos, nbyt);
  c->pos += nbyt;
  return cnt;
}

int SerializerStream::fwrite(void *buf, int size, int cnt)
{
  vmStreamContext *c = (vmStreamContext *)context;

  if (!c || !c->issave)
    return -1;

  int nbyt = size*cnt;
  if (c->pos + nbyt > c->size) {
    cnt = (c->size - c->pos)/size;
    nbyt = size*cnt;
  }
  if (nbyt)
    memcpy(c->buffer + c->pos, buf, nbyt);
  c->pos += nbyt;
  return cnt;
}

