/*
 *  Display.c - Darstellung der C64-Grafik,
 *              Handhabung des Emulatorfensters
 *
 *  Copyright (C) 1994-1996 by Christian Bauer
 */

/*
 *  Anmerkungen:
 *  ------------
 *
 *  - Die Farbpalette besteht aus den 16 C64-Farben, 16mal wiederholt.
 *    Dadurch spart man sich das Ausmaskieren der unteren 4 Bit bei den
 *    VIC-Farbcodes. Allerdings muß dieses bei der Chunky->Planar-
 *    Konvertierung für die Amiga-Chips erfolgen (der Algorithmus
 *    setzt voraus, daß die oberen Nibbles Null sind).
 */

#include <exec/types.h>
#include <exec/libraries.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <intuition/screens.h>
#include <libraries/gadtools.h>
#include <devices/timer.h>
#include <clib/exec_protos.h>
#include <clib/intuition_protos.h>
#include <clib/graphics_protos.h>
#include <clib/gadtools_protos.h>
#include <clib/timer_protos.h>
#include <string.h>

#include "Display.h"
#include "SAM.h"
#include "Prefs.h"
#define CATCOMP_NUMBERS 1
#include "LocStrings.h"


// Aus Main.asm
extern struct Library *GfxBase;
extern struct Library *IntuitionBase;
extern void ShowPrefs(void);
extern void PutChProc(void);
extern char *GetStr(int strnum);
extern void ResetC64(void);

// Aus 6510.asm
extern struct Task *CPUTask;
extern void Pause6510(void);
extern void Resume6510(void);
extern ULONG InvokeSAMSet;

// Aus 6526.asm
extern void KeyPressed(char c);
extern WORD JoystickSwap;

// Aus 6569.asm
extern APTR CURRENTA5;
extern BYTE ChunkyBuf[];
extern UWORD SkipLatch;
extern UWORD LimitSpeed;

// Aus 6581.asm
extern void PauseSound(void);
extern void ResumeSound(void);

// Aus c2p4.asm
extern void c2p4(UBYTE *fBUFFER, UBYTE *fBUFFER_CMP, APTR *planes, struct Task *task, ULONG signals);
extern int Initc2p4(void);
extern void Exitc2p4(void);


// Prototypes
void open_double_buf(int type, int width, int height);
int handle_IDCMP(int done);
int handle_menu(int menu, int item, int done);


char has_gfx_39, has_gfx_40;  // Flags für Version der graphics.library

int current_type, current_width, current_height;

struct Screen *the_screen;
struct Window *the_window;
struct VisualInfo *the_visual_info;
struct RastPort *the_rast_port;
struct ViewPort *the_view_port;
struct TextFont *topaz_font;
struct Menu *the_menus;

// Für WritePixelArray8()
struct BitMap *temp_bm;
struct BitMap v37_temp_bm;
struct RastPort temp_rp;

// Double Buffering
struct ScreenBuffer *scr_buf[2];
int inv_buf_num;
char using_db;				// Flag: Double Buffering wird benutzt

// c2p4
APTR comparison_buf[2];
char c2p4_signal;			// Signal: c2p4 fertig
LONGBITS c2p4_set;
char must_wait_for_c2p4;	// Flag: Auf c2p4 muß gewartet werden

// Geschwindigkeitsanzeige
struct MsgPort *timer_port;
struct timerequest *timer_io;


const struct TextAttr topaz_attr = {
  "topaz.font", 8, FS_NORMAL, 0
};

struct NewMenu new_menus[] = {
  NM_TITLE, MSG_EMULATION_MENU, NULL, 0, 0, NULL,
  NM_ITEM, MSG_SETTINGS_MENU, "P", 0, 0, NULL,
  NM_ITEM, MSG_SAM_MENU, "M", 0, 0, NULL,
  NM_ITEM, NM_BARLABEL, NULL, 0, 0, NULL,
  NM_ITEM, MSG_QUIT_MENU, "Q", 0, 0, NULL,
  NM_END, NULL, NULL, 0, 0, NULL
};

const UBYTE palette_red[16] = {
  0, 15, 12, 0, 15, 0, 0, 15, 15, 8, 15, 4, 8, 8, 8, 12
};

const UBYTE palette_green[16] = {
  0, 15, 0, 15, 0, 12, 0, 15, 8, 4, 8, 4, 8, 15, 8, 12
};

const UBYTE palette_blue[16] = {
  0, 15, 0, 12, 15, 0, 12, 0, 0, 0, 8, 4, 8, 8, 15, 12
};


/*
 *  Screen und Fenster öffnen, alles vorbereiten
 *  0: OK, 1: Fehler beim Screen-Öffnen, 2: Kein Speicher
 */

int OpenDisplay(int type, ULONG display_id, UWORD overscan, int width, int height)
{
  int depth;
  int i;

  has_gfx_39 = (GfxBase->lib_Version >= 39);
  has_gfx_40 = (GfxBase->lib_Version >= 40);

  must_wait_for_c2p4 = FALSE;
  using_db = FALSE;

  current_type = type;
  current_width = width;
  current_height = height;

  switch (type) {
    case SCRTYPE_8BIT: depth = 8; break;
    case SCRTYPE_4BIT: depth = 4; break;
    case SCRTYPE_1BIT: depth = 1; break;
  }

  if (!(the_screen = OpenScreenTags(NULL,
    SA_Width, width,
    SA_Height, height,
    SA_DisplayID, display_id,
    SA_Overscan, overscan,
    SA_Depth, depth,
    SA_Quiet, TRUE,
    SA_AutoScroll, TRUE,
    TAG_DONE)))
    return 1;

  the_rast_port = &the_screen->RastPort;
  the_view_port = &the_screen->ViewPort;

  if (topaz_font = OpenFont(&topaz_attr))
    SetFont(the_rast_port, topaz_font);
  SetAPen(the_rast_port, 1);

  if (!(the_visual_info = GetVisualInfo(the_screen, NULL)))
    return 2;

  if (!(the_menus = CreateMenus(new_menus, GTMN_FullMenu, TRUE, TAG_DONE)))
    return 2;
  LayoutMenus(the_menus, the_visual_info, GTMN_NewLookMenus, TRUE, TAG_DONE);

  if (!(the_window = OpenWindowTags(NULL,
    WA_Left, 0,
    WA_Top, 0,
    WA_Width, width,
    WA_Height, height,
    WA_CustomScreen, the_screen,
    WA_IDCMP, IDCMP_RAWKEY | IDCMP_MENUPICK | IDCMP_MENUVERIFY,
    WA_Backdrop, TRUE,
    WA_Borderless, TRUE,
    WA_NoCareRefresh, TRUE,
    WA_Activate, TRUE,
    WA_NewLookMenus, TRUE,
    TAG_DONE)))
    return 2;

  SetMenuStrip(the_window, the_menus);

  switch (type) {
    case SCRTYPE_8BIT:

      // Farbpalette laden
      for (i=0; i<256; i++)
        SetRGB4(the_view_port, i,
          palette_red[i & 0xf], palette_green[i & 0xf], palette_blue[i & 0xf]);

      // Temporären RastPort für WritePixelArray8() anlegen
      if (has_gfx_39) {
        if (!(temp_bm = AllocBitMap(width, 1, 8, 0, NULL)))
          return 2;
      } else {
        InitBitMap(temp_bm = &v37_temp_bm, depth, width, 1);
        for (i=0; i<8; i++)
          if (!(v37_temp_bm.Planes[i] = AllocRaster(width, 1)))
            return 2;
      }

      InitRastPort(&temp_rp);
      temp_rp.BitMap = temp_bm;

      CURRENTA5 = ChunkyBuf;  // Wird nicht mehr verändert
      break;

    case SCRTYPE_4BIT:

      if (!Initc2p4())
        return 2;

      // Speicher für Comparison Buffer holen
      if (!(comparison_buf[0] = AllocVec(width * height, MEMF_PUBLIC | MEMF_CLEAR)))
        return 2;

      // Farbpalette laden
      for (i=0; i<16; i++)
        SetRGB4(the_view_port, i,
          palette_red[i & 0xf], palette_green[i & 0xf], palette_blue[i & 0xf]);

      CURRENTA5 = ChunkyBuf;

      open_double_buf(type, width, height);
      must_wait_for_c2p4 = FALSE;
      break;

    case SCRTYPE_1BIT:

      SetRGB4(the_view_port, 0, 0, 0, 0);		// schwarz
      SetRGB4(the_view_port, 1, 15, 15, 15);	// weiß

      CURRENTA5 = the_rast_port->BitMap->Planes[0];

      open_double_buf(type, width, height);
      break;
  }

  return 0;
}


/*
 *  Double Buffering einrichten
 */

void open_double_buf(int type, int width, int height)
{
  using_db = FALSE;
  if (IntuitionBase->lib_Version >= 39) {

    if (!(scr_buf[0] = AllocScreenBuffer(the_screen, NULL, SB_SCREEN_BITMAP)))
      return;

    if (!(scr_buf[1] = AllocScreenBuffer(the_screen, NULL, SB_COPY_BITMAP)))
      return;

    WaitTOF(); WaitTOF();
    inv_buf_num = 1;

    if (type == SCRTYPE_4BIT) {
      CURRENTA5 = ChunkyBuf;
      if (!(comparison_buf[1] = AllocVec(width * height, MEMF_PUBLIC | MEMF_CLEAR)))
        return;
    } else {
      CURRENTA5 = scr_buf[1]->sb_BitMap->Planes[0];
    }

    using_db = TRUE;
  }
}


/*
 *  Screen und Fenster schließen
 */

void CloseDisplay(void)
{
  int i;

  if (current_type == SCRTYPE_4BIT)
    Exitc2p4();

  if (comparison_buf[0]) {
    FreeVec(comparison_buf[0]);
    comparison_buf[0] = NULL;
  }

  if (comparison_buf[1]) {
    FreeVec(comparison_buf[1]);
    comparison_buf[1] = NULL;
  }

  FreeScreenBuffer(the_screen, scr_buf[0]);  // NULL ist OK
  scr_buf[0] = NULL;
  FreeScreenBuffer(the_screen, scr_buf[1]);
  scr_buf[1] = NULL;

  if (the_menus) {
    ClearMenuStrip(the_window);
    FreeMenus(the_menus);
    the_menus = NULL;
  }

  if (the_window) {
    CloseWindow(the_window);
    the_window = NULL;
  }

  // Temporären RastPort schließen
  if (temp_bm) {
    if (has_gfx_39)
      FreeBitMap(temp_bm);
    else {
      for (i=0; i<8; i++) {
        FreeRaster(v37_temp_bm.Planes[i], current_width, current_height);
        v37_temp_bm.Planes[i] = NULL;
      }
    }
    temp_bm = NULL;
  }

  if (the_visual_info) {
    FreeVisualInfo(the_visual_info);
    the_visual_info = NULL;
  }

  if (topaz_font) {
    CloseFont(topaz_font);
    topaz_font = NULL;
  }

  if (the_screen) {
    CloseScreen(the_screen);
    the_screen = NULL;
  }
}


/*
 *  Vom 6510-Task aus nötige Initialisierungen
 *  Wird vom 6510-Task aufgerufen
 */

void InitDisplayFrom6510(void)
{
  // Signal für c2p4
  c2p4_signal = AllocSignal(-1);
  c2p4_set = 1 << c2p4_signal;

  // TimerIO einrichten
  if (timer_port = CreateMsgPort()) {
    if (timer_io = CreateIORequest(timer_port, sizeof(struct timerequest))) {
      OpenDevice(TIMERNAME, UNIT_MICROHZ, (struct IORequest *)timer_io, 0);

      // timer_io für Speed Limiter starten
      timer_io->tr_node.io_Command = TR_ADDREQUEST;
      timer_io->tr_time.tv_secs = 0;
      timer_io->tr_time.tv_micro = SkipLatch * 20000;  // 20ms pro Bild
      SendIO((struct IORequest *)timer_io);
    }
  }
}


/*
 *  Vom 6510-Task aus nötige Aufräumarbeiten
 *  Wird vom 6510-Task aufgerufen
 */

void ExitDisplayFrom6510(void)
{
  if (timer_io) {
    if (!CheckIO((struct IORequest *)timer_io))
      WaitIO((struct IORequest *)timer_io);
    CloseDevice((struct IORequest *)timer_io);
    DeleteIORequest((struct IORequest *)timer_io);
    timer_io = NULL;
  }

  if (timer_port) {
    DeleteMsgPort(timer_port);
    timer_port = NULL;
  }

  // Ggf. auf Signal von c2p4 warten
  if (must_wait_for_c2p4)
    Wait(c2p4_set);

  FreeSignal(c2p4_signal);
}


/*
 *  C64-Grafik neu darstellen (VBlank, Double Buffering)
 *  Wird vom 6510-Task aufgerufen
 */

struct timeval start_time, end_time;
char speed_str[20];
int percent;

void RedrawDisplay(void)
{
  switch (current_type) {
    // SCRTYPE_8BIT: WritePixelLine in 6569.asm

    case SCRTYPE_4BIT:		// Double Buffering und c2p-Konvertierung
      if (must_wait_for_c2p4)
        Wait(c2p4_set);

      if (using_db) {
        while (!ChangeScreenBuffer(the_screen, scr_buf[inv_buf_num]))
          WaitTOF();
        inv_buf_num ^= 1;
        c2p4(ChunkyBuf, comparison_buf[inv_buf_num], scr_buf[inv_buf_num]->sb_BitMap->Planes, CPUTask, c2p4_set);
      } else
        c2p4(ChunkyBuf, comparison_buf[0], the_rast_port->BitMap->Planes, CPUTask, c2p4_set);

      must_wait_for_c2p4 = TRUE;
      CURRENTA5 = ChunkyBuf;
      break;

    case SCRTYPE_1BIT:		// Double Buffering
      if (using_db) {
        while (!ChangeScreenBuffer(the_screen, scr_buf[inv_buf_num]))
          WaitTOF();
        inv_buf_num ^= 1;
        CURRENTA5 = scr_buf[inv_buf_num]->sb_BitMap->Planes[0];
      } else
        CURRENTA5 = the_rast_port->BitMap->Planes[0];
      break;
  }

  if (timer_io) {
    // timer_io abbrechen, wenn Speed Limiter aus ist
    if (!LimitSpeed)
      if (!CheckIO((struct IORequest *)timer_io))
        AbortIO((struct IORequest *)timer_io);

    WaitIO((struct IORequest *)timer_io);

    // timer_io neu starten
    timer_io->tr_node.io_Command = TR_ADDREQUEST;
    timer_io->tr_time.tv_secs = 0;
    timer_io->tr_time.tv_micro = SkipLatch * 20000;  // 20ms pro Bild
    SendIO((struct IORequest *)timer_io);
  }

  // Zeit seit letztem Aufruf ermitteln
  GetSysTime(&end_time);
  SubTime(&end_time, &start_time);
  GetSysTime(&start_time);

  // Ein Bild sollte 20ms dauern
  percent = (20000 * 100 / end_time.tv_micro + 1) * SkipLatch;

  RawDoFmt("%ld%%", &percent, &PutChProc, speed_str);
  Move(the_rast_port, 4, current_height-6);
  Text(the_rast_port, speed_str, strlen(speed_str));
}


/*
 *  C64-Grafik nach hinten
 */

void EmulToBack(void)
{
  ScreenToBack(the_screen);
  ModifyIDCMP(the_window, IDCMP_RAWKEY);
  ClearMenuStrip(the_window);
}


/*
 *  C64-Grafik nach vorne
 */

void EmulToFront(void)
{
  ScreenToFront(the_screen);
  ModifyIDCMP(the_window, IDCMP_RAWKEY | IDCMP_MENUPICK | IDCMP_MENUVERIFY);
  SetMenuStrip(the_window, the_menus);
}


/*
 *  Event-Schleife des Fensters
 */

void EventLoop(void)
{
  int done = FALSE;
  ULONG signals;

  while (!done) {
    signals = Wait((1 << the_window->UserPort->mp_SigBit) | InvokeSAMSet);

    if (signals & (1 << the_window->UserPort->mp_SigBit))
      done = handle_IDCMP(done);

    if (signals & InvokeSAMSet)
      handle_menu(0, 1, done);
  }
}


/*
 *  IDCMP-Messages handhaben
 */

int handle_IDCMP(int done)
{
  struct IntuiMessage *msg;
  ULONG class;
  UWORD code;
  UWORD menu_number;
  struct MenuItem *item;

  while (!done && (msg = GetMsg(the_window->UserPort))) {
    class = msg->Class;
    code = msg->Code;

    if (class == IDCMP_MENUVERIFY)
      Pause6510();	// Subtask stoppen

    ReplyMsg((struct Message *)msg);

    switch (class) {
      case IDCMP_MENUPICK:
        Resume6510();  // Subtask fortführen

        menu_number = code;
        while (!done && (menu_number != MENUNULL)) {
          item = ItemAddress(the_window->MenuStrip, menu_number);
          done = handle_menu(MENUNUM(menu_number), ITEMNUM(menu_number), done);
          menu_number = item->NextSelect;
        }
        break;

      case IDCMP_RAWKEY:
        switch (code) {
          case 0x59:	// F10: Reset
            ResetC64();
            break;
          case 0x5f:	// Help: JoystickSwap umschalten
            JoystickSwap = JoystickSwap ? 0 : -1;
            break;
          case 0x5e:	// + (NK): SkipLatch erhöhen
            SkipLatch++;
            break;
          case 0x4a:	// - (NK): SkipLatch erniedrigen
            if (SkipLatch > 1)
              SkipLatch--;
            break;
          default:
            KeyPressed(code);
        }
        break;
    }
  }

  return done;
}


/*
 *  Menü wurde ausgewählt
 */

int handle_menu(int menu, int item, int done)
{
  if (menu == 0) {
    switch (item) {
      case 0:		// Einstellungen
        Pause6510();
        PauseSound();
        EmulToBack();
        ShowPrefs();
        EmulToFront();
        ResumeSound();
        Resume6510();
        break;

      case 1:		// SAM
        Pause6510();
        PauseSound();
        EmulToBack();
        SAM();
        EmulToFront();
        ResumeSound();
        Resume6510();
        break;

      case 3:		// Quit
        done = TRUE;
        break;
    }
  }

  return done;
}


/*
 *  Requester anzeigen
 */

int ShowRequester(int text, int gads, APTR args)
{
  struct EasyStruct es;

  es.es_StructSize = sizeof(struct EasyStruct);
  es.es_Flags = 0;
  es.es_Title = GetStr(MSG_REQTITLE);
  es.es_TextFormat = GetStr(text);
  es.es_GadgetFormat = GetStr(gads);

  return EasyRequestArgs(NULL, &es, NULL, args);
}


/*
 *  Lokalisierungen vornehmen
 */

void LocalizeDisplay(void)
{
  int i;

  for (i=0; new_menus[i].nm_Type != NM_END; i++)
    if (new_menus[i].nm_Label != NM_BARLABEL)
      new_menus[i].nm_Label = GetStr((int)new_menus[i].nm_Label);
}
