/*
 * ted-snapshot.c - Snapshot functionality for the TED emulation.
 *
 * Written by
 *  Andreas Boose <viceteam@t-online.de>
 *  Ettore Perazzoli <ettore@comm2000.it>
 *
 * This file is part of VICE, the Versatile Commodore Emulator.
 * See README for copyright notice.
 *
 *  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.
 *
 */

#include "vice.h"

#include <stdio.h>

#include "alarm.h"
#include "interrupt.h"
#include "log.h"
#include "mem.h"
#include "snapshot.h"
#include "raster-snapshot.h"
#include "raster-sprite-status.h"
#include "raster-sprite.h"
#include "ted-irq.h"
#include "ted-snapshot.h"
#include "ted.h"
#include "tedtypes.h"
#include "types.h"

/* #define DEBUGTED */

#ifdef DEBUGTED
#define DBG(x) printf x
#else
#define DBG(x)
#endif

/* Make sure all the TED alarms are removed.  This just makes it easier to
   write functions for loading snapshot modules in other video chips without
   caring that the TED alarms are dispatched when they really shouldn't
   be.  */

void ted_snapshot_prepare(void)
{
    ted.fetch_clk = CLOCK_MAX;
    alarm_unset(ted.raster_fetch_alarm);
    ted.draw_clk = CLOCK_MAX;
    alarm_unset(ted.raster_draw_alarm);
    ted.raster_irq_clk = CLOCK_MAX;
    alarm_unset(ted.raster_irq_alarm);
}

/*

    FIXME: this snapshot module is severely broken:
    - video stuff is incomplete/buggy
    - sound stuff is completely missing
    - timer are completely missing

    NOTE: if you run into problems when testing, try running with a clean config and sound disabled

    This is the format of the TED snapshot module:

    Name                        Type   Size   Description

    last_emulate_line_clk       DWORD  1
    AllowBadLines               BYTE   1      flag: if true, bad lines can happen
    BadLine                     BYTE   1      flag: this is a bad line
    Blank                       BYTE   1      flag: draw lines in border color
    ColorBuf                    BYTE   40     character memory buffer (loaded at bad line)
    IdleState                   BYTE   1      flag: idle state enabled
    MatrixBuf                   BYTE   40     video matrix buffer (loaded at bad line)
    RamBase                     DWORD  1      pointer to the start of RAM seen by the TED
    RasterCycle                 BYTE   1      current ted.raster cycle
    RasterLine                  WORD   1      current ted.raster line
    Registers                   BYTE   64     TED registers
    ted_raster_counter          DWORD  1
    Vc                          WORD   1      internal TED counter
    mem_counter_inc             BYTE   1      (VcInc) value to add to Vc at the end of this line
    VcBase                      WORD   1      internal TED memory pointer
    VideoInt                    BYTE   1      status of TED IRQ (ted.irq_status)

    [Alarm section]
    FetchEventTick              DWORD  1      ticks for the next "fetch" (DMA) event

 */

static char snap_module_name[] = "TED";
#define SNAP_MAJOR 1
#define SNAP_MINOR 5

int ted_snapshot_write_module(snapshot_t *s)
{
    int i;
    snapshot_module_t *m;

    /* FIXME: Dispatch all events?  */

    m = snapshot_module_create (s, snap_module_name, SNAP_MAJOR, SNAP_MINOR);
    if (m == NULL) {
        return -1;
    }

    DBG(("TED write snapshot at clock: %d cycle: %d tedline: %d rasterline: %d\n", maincpu_clk, TED_RASTER_CYCLE(maincpu_clk), TED_RASTER_Y(maincpu_clk), ted.raster.current_line));

    if (0
        || SMW_CLOCK(m, ted.last_emulate_line_clk) < 0
        /* AllowBadLines */
        || SMW_B(m, (uint8_t)ted.allow_bad_lines) < 0
        /* BadLine */
        || SMW_B(m, (uint8_t)ted.bad_line) < 0
        /* Blank */
        || SMW_B(m, (uint8_t)ted.raster.blank_enabled) < 0
        /* ColorBuf */
        || SMW_BA(m, ted.cbuf, 40) < 0
        /* IdleState */
        || SMW_B(m, (uint8_t)ted.idle_state) < 0
        /* MatrixBuf */
        || SMW_BA(m, ted.vbuf, 40) < 0
        /* RasterCycle */
        || SMW_B(m, (uint8_t)TED_RASTER_CYCLE(maincpu_clk)) < 0
        /* RasterLine */
        || SMW_W(m, (uint16_t)(TED_RASTER_Y(maincpu_clk))) < 0
        ) {
        goto fail;
    }

    for (i = 0; i < 0x40; i++) {
        /* Registers */
        if (SMW_B(m, ted.regs[i]) < 0) {
            goto fail;
        }
    }

    if (0
        || SMW_DW(m, ted.tv_current_line) < 0
        || SMW_DW(m, ted.screen_height) < 0
        || SMW_DW(m, ted.first_displayed_line) < 0
        || SMW_DW(m, ted.last_displayed_line) < 0
        || SMW_DW(m, (uint32_t)ted.ted_raster_counter) < 0
        /* Vc */
        || SMW_W(m, (uint16_t)ted.mem_counter) < 0
        /* VcInc */
        || SMW_B(m, (uint8_t)ted.mem_counter_inc) < 0
        /* VcBase */
        || SMW_W(m, (uint16_t)ted.memptr) < 0
        /* VideoInt */
        || SMW_B(m, (uint8_t)ted.irq_status) < 0
        ) {
        goto fail;
    }

    if (0
        /* FetchEventTick */
        || SMW_CLOCK(m, ted.fetch_clk - maincpu_clk) < 0
        ) {
        goto fail;
    }

    if (raster_snapshot_write(m, &ted.raster)) {
        goto fail;
    }

    DBG(("TED snapshot written.\n"));
    return snapshot_module_close(m);

fail:
    if (m != NULL) {
        snapshot_module_close(m);
    }
    DBG(("error writing TED snapshot.\n"));
    return -1;
}

int ted_snapshot_read_module(snapshot_t *s)
{
    uint8_t major_version, minor_version;
    int i;
    uint16_t RasterLine;
    uint8_t RasterCycle;
    snapshot_module_t *m;

    m = snapshot_module_open(s, snap_module_name,
                             &major_version, &minor_version);
    if (m == NULL) {
        return -1;
    }

    if (snapshot_version_is_bigger(major_version, minor_version, SNAP_MAJOR, SNAP_MINOR)) {
        log_error(ted.log,
                  "Snapshot module version (%d.%d) newer than %d.%d.",
                  major_version, minor_version,
                  SNAP_MAJOR, SNAP_MINOR);
        goto fail;
    }

    /* FIXME: initialize changes?  */

    if (0
        || SMR_CLOCK(m, &ted.last_emulate_line_clk) < 0
        /* AllowBadLines */
        || SMR_B_INT(m, &ted.allow_bad_lines) < 0
        /* BadLine */
        || SMR_B_INT(m, &ted.bad_line) < 0
        /* Blank */
        || SMR_B_INT(m, &ted.raster.blank_enabled) < 0
        /* ColorBuf */
        || SMR_BA(m, ted.cbuf, 40) < 0
        /* IdleState */
        || SMR_B_INT(m, &ted.idle_state) < 0
        /* MatrixBuf */
        || SMR_BA(m, ted.vbuf, 40) < 0
        || SMR_B(m, &RasterCycle) < 0
        || SMR_W(m, &RasterLine) < 0
        ) {
        goto fail;
    }

    for (i = 0; i < 0x40; i++) {
        if (SMR_B(m, &ted.regs[i]) < 0 /* Registers */) {
            goto fail;
        }
    }

    if (0
        || SMR_DW(m, &ted.tv_current_line) < 0
        || SMR_DW(m, &ted.screen_height) < 0
        || SMR_DW_INT(m, &ted.first_displayed_line) < 0
        || SMR_DW_INT(m, &ted.last_displayed_line) < 0
        || SMR_DW_INT(m, (int*)&ted.ted_raster_counter) < 0
        /* Vc */
        || SMR_W_INT(m, &ted.mem_counter) < 0
        /* VcInc */
        || SMR_B_INT(m, &ted.mem_counter_inc) < 0
        /* VcBase */
        || SMR_W_INT(m, &ted.memptr) < 0
        /* VideoInt */
        || SMR_B_INT(m, &ted.irq_status) < 0) {
        goto fail;
    }

    /* Sanity check the current raster line and the current raster cycle */
    DBG(("TED read snapshot at clock: %d cycle: %d (%d) tedline: %d (%d) rasterline: %d\n",
         maincpu_clk, TED_RASTER_CYCLE(maincpu_clk), RasterCycle, TED_RASTER_Y(maincpu_clk),
         RasterLine, ted.raster.current_line));

    if (RasterCycle != (uint8_t)TED_RASTER_CYCLE(maincpu_clk)) {
        log_error(ted.log,
                  "Not matching raster cycle (%d) in snapshot; should be %u.",
                  RasterCycle, TED_RASTER_CYCLE(maincpu_clk));
        goto fail;
    }

    if (RasterLine != (uint16_t)TED_RASTER_Y(maincpu_clk)) {
        log_error(ted.log,
                  "Not matching raster line (%d) in snapshot; should be %u.",
                  RasterLine, TED_RASTER_Y(maincpu_clk));
        goto fail;
    }

    /* FIXME: Recalculate alarms and derived values.  */

    ted_irq_set_raster_line(ted.regs[0x0b] | ((ted.regs[0x0a] & 1) << 8));

    ted_update_memory_ptrs(TED_RASTER_CYCLE(maincpu_clk));

    ted.raster.xsmooth = ted.regs[0x07] & 0x7;
    ted.raster.ysmooth = ted.regs[0x06] & 0x7;
    ted.raster.current_line = TED_RASTER_Y(maincpu_clk); /* FIXME? */

    /* Update colors.  */
    ted.raster.border_color = ted.regs[0x19];
    ted.raster.background_color = ted.regs[0x15];
    ted.ext_background_color[0] = ted.regs[0x16];
    ted.ext_background_color[1] = ted.regs[0x17];
    ted.ext_background_color[2] = ted.regs[0x18];

    ted.raster.blank = !(ted.regs[0x06] & 0x10);

    if (TED_IS_ILLEGAL_MODE (ted.raster.video_mode)) {
        ted.raster.idle_background_color = 0;
        ted.force_black_overscan_background_color = 1;
    } else {
        ted.raster.idle_background_color = ted.raster.background_color;
        ted.force_black_overscan_background_color = 0;
    }

    if (ted.regs[0x06] & 0x8) {
        ted.raster.display_ystart = ted.row_25_start_line;
        ted.raster.display_ystop = ted.row_25_stop_line;
    } else {
        ted.raster.display_ystart = ted.row_24_start_line;
        ted.raster.display_ystop = ted.row_24_stop_line;
    }

    if (ted.regs[0x07] & 0x8) {
        ted.raster.display_xstart = TED_40COL_START_PIXEL;
        ted.raster.display_xstop = TED_40COL_STOP_PIXEL;
    } else {
        ted.raster.display_xstart = TED_38COL_START_PIXEL;
        ted.raster.display_xstop = TED_38COL_STOP_PIXEL;
    }

    /* `ted.raster.draw_idle_state', `ted.raster.open_right_border' and
       `ted.raster.open_left_border' should be needed, but they would only
       affect the current ted.raster line, and would not cause any
       difference in timing.  So who cares.  */

    /* FIXME: `ted.ycounter_reset_checked'?  */
    /* FIXME: `ted.force_display_state'?  */

    ted.memory_fetch_done = 0; /* FIXME? */

    ted_update_video_mode(TED_RASTER_CYCLE(maincpu_clk));

    ted.draw_clk = maincpu_clk + (ted.draw_cycle - TED_RASTER_CYCLE(maincpu_clk));
    ted.last_emulate_line_clk = ted.draw_clk - ted.cycles_per_line;
    alarm_set(ted.raster_draw_alarm, ted.draw_clk);

    {
        CLOCK qw;

        if (SMR_CLOCK(m, &qw) < 0) {  /* FetchEventTick */
            goto fail;
        }

        ted.fetch_clk = maincpu_clk + qw;

        alarm_set(ted.raster_fetch_alarm, ted.fetch_clk);
    }

    if (ted.irq_status & 0x80) {
        interrupt_restore_irq(maincpu_int_status, ted.int_num, 1);
    }


    if (raster_snapshot_read(m, &ted.raster)) {
        goto fail;
    }

    raster_force_repaint(&ted.raster);
    DBG(("TED: snapshot loaded.\n"));
    return 0;

fail:
    if (m != NULL) {
        snapshot_module_close(m);
    }
    log_error(ted.log, "could not load TED snapshot.");
    return -1;
}
