#include "global.h"

#include <stdio.h>
#include <stdlib.h>

#include <exec/exec.h>
#include <dos/dos.h>
#include <graphics/gfx.h>
#include <intuition/intuition.h>
#include <libraries/asl.h>
#include <libraries/diskfont.h>
#include <cybergraphics/cybergraphics.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/graphics.h>
#include <proto/intuition.h>
#include <proto/asl.h>
#include <proto/diskfont.h>
#include <proto/cybergraphics.h>

#include "astuff.h"
#include "hardware.h"

/**********************************************************************/
struct Library *AslBase = NULL;
struct Library *CyberGfxBase = NULL;

static int fontwidth = 8;
static int fontheight = 16;
static int fontdescent = 4;
static int width = 80*8;
static int height = 25*16;
static int cursor_st = 0;
static int cursor_height = 0;
static cursor_x = 0;
static cursor_y = 0;
static struct ScreenModeRequester *smr = NULL;
static struct Screen *screen = NULL;
static struct Window *window = NULL;
static struct RastPort *rp = NULL;
static struct BitMap *cursor_bm = NULL;
static struct RastPort cursor_rp;
static struct TextAttr vga16 = {
  "vga.font", 16, FS_NORMAL, FPF_DISKFONT
};
static struct TextFont *vga16font = NULL;
static struct 
{
    unsigned r,g,b;
} text_cols[] = 
{
    {0,0,0},{10,10,185},{10,195,10},{20,160,160},
    {167,10,10},{167,0,167},{165,165,40},{197,197,197},
    {100,100,100},{10,10,255},{10,255,10},{10,255,255},
    {255,10,10},{255,10,255},{255,255,0},{255,255,255}
};

/**********************************************************************/

static void open_display (int new_width, int new_height)
{
  ULONG propertymask, mode, palette[1+3*16+1];
  int i;
  char reqtitle[32];
  static struct TagItem tags[5];

  width = new_width;
  height = new_height;
  sprintf (reqtitle, "pcemu %dx%d", width, height);
  propertymask = DIPF_IS_EXTRAHALFBRITE | DIPF_IS_DUALPF | DIPF_IS_PF2PRI |
                 DIPF_IS_HAM;
  if (CyberGfxBase != NULL) {
    tags[0].ti_Tag = CYBRBIDTG_NominalWidth;
    tags[0].ti_Data = width;
    tags[1].ti_Tag = CYBRBIDTG_NominalHeight;
    tags[1].ti_Data = height;
    tags[2].ti_Tag = CYBRBIDTG_Depth;
    tags[2].ti_Data = 4;
    tags[3].ti_Tag = TAG_DONE;
    tags[3].ti_Data = 0;
    mode = BestCModeIDTagList (tags);
  } else
    mode = BestModeID (BIDTAG_NominalWidth,     width,
                       BIDTAG_NominalHeight,    height,
                       BIDTAG_Depth,            4,
                       BIDTAG_DIPFMustNotHave,  propertymask,
                       TAG_DONE);
  if (!AslRequestTags (smr,
                       ASLSM_TitleText,        (ULONG)reqtitle,
                       ASLSM_InitialDisplayID, mode,
                       ASLSM_MinWidth,         width,
                       ASLSM_MinHeight,        height,
                       ASLSM_MinDepth,         4,
                       ASLSM_PropertyMask,     propertymask,
                       ASLSM_PropertyFlags,    0,
                       TAG_DONE)) {
    fprintf (stderr, "ScreenMode requester failed\n");
    exit (1);
  }
  if ((screen = OpenScreenTags (NULL,
        SA_Type,        CUSTOMSCREEN,
        SA_DisplayID,   smr->sm_DisplayID,
        SA_Width,       width,
        SA_Height,      height,
        SA_Depth,       4,
        SA_Quiet,       TRUE,
        SA_Font,        &vga16,
        TAG_DONE)) == NULL) {
    fprintf (stderr, "OpenScreen() failed\n");
    exit (1);
  }
  palette[0] = 16<<16+0;
  for (i = 0; i < 16; i++) {
    palette[1+3*i+0] = text_cols[i].r * 0x01010101;
    palette[1+3*i+1] = text_cols[i].g * 0x01010101;
    palette[1+3*i+2] = text_cols[i].b * 0x01010101;
  }
  palette[1+3*16+0] = 0;
  LoadRGB32 (&screen->ViewPort, palette);
  if ((window = OpenWindowTags (NULL,
        WA_Left,         0,
        WA_Top,          0,
        WA_Width,        width,
        WA_Height,       height,
        WA_IDCMP,        IDCMP_RAWKEY,
        WA_Flags,        WFLG_ACTIVATE | WFLG_BORDERLESS,
        WA_CustomScreen, screen,
        TAG_DONE)) == NULL) {
    fprintf (stderr, "OpenWindow() failed\n");
    exit (1);
  }
  rp = window->RPort;
  SetFont (rp, vga16font);
  if ((cursor_bm = AllocBitMap (fontwidth, fontheight, 4, BMF_CLEAR,
                                rp->BitMap)) == NULL) {
    fprintf (stderr, "AllocBitMap() failed\n");
    exit (1);
  }
  InitRastPort (&cursor_rp);
  cursor_rp.BitMap = cursor_bm;
}

/**********************************************************************/
static void close_display (void)
{
  if (cursor_bm != NULL) {
    FreeBitMap (cursor_bm);
    cursor_bm = NULL;
  }
  if (window != NULL) {
    CloseWindow (window);
    window = NULL;
  }
  if (screen != NULL) {
    CloseScreen (screen);
    screen = NULL;
  }
}

/**********************************************************************/
void start_X (void)
{
  CyberGfxBase = OpenLibrary ("cybergraphics.library", 0);
  if (AslBase == NULL) {
    if ((AslBase = OpenLibrary ("asl.library", 37L)) == NULL ||
        (smr = AllocAslRequestTags (ASL_ScreenModeRequest, TAG_DONE)) == NULL) {
      fprintf (stderr, "AllocAslRequest() failed\n");
      exit (1);
    }
  }
  if ((vga16font = OpenDiskFont (&vga16)) == NULL) {
    vga16.ta_Name = "progdir:fonts/vga.font";
    if ((vga16font = OpenDiskFont (&vga16)) == NULL) {
      fprintf (stderr, "OpenDiskFont(vga.font) failed\n");
      exit (1);
    }
  }
  fontwidth = vga16font->tf_XSize;
  fontheight = vga16font->tf_YSize;
  fontdescent = vga16font->tf_YSize - vga16font->tf_Baseline;
  open_display (80 * fontwidth, 25 * fontheight);
}

/**********************************************************************/
void end_X (void)
{
  close_display ();
  if (vga16font != NULL) {
    CloseFont (vga16font);
    vga16font = NULL;
  }
  if (AslBase != NULL) {
    CloseLibrary (AslBase);
    AslBase = NULL;
  }
  if (CyberGfxBase != NULL) {
    CloseLibrary (CyberGfxBase);
    CyberGfxBase = NULL;
  }
}

/**********************************************************************/
static ULONG xlate[0x68] = {
  0x29, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
  0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x2b, 0x00, 0x52,
  0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
  0x18, 0x19, 0x1a, 0x1b, 0x00, 0x4f, 0x50, 0x51,
  0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25,
  0x26, 0x27, 0x28, 0x00, 0x00, 0x4b, 0x4c, 0x4d,
  0x00, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32,
  0x33, 0x34, 0x35, 0x00, 0x53, 0x47, 0x48, 0x49,
  0x39, 0x0e, 0x0f, 0x1ce0, 0x1c, 0x01, 0x53e0, 0x00,
  0x00, 0x00, 0x4a, 0x00, 0x48e0, 0x50e0, 0x4de0, 0x4be0,
  0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42,
  0x43, 0x44, 0x00, 0x00, 0x36e0, 0x37, 0x4e, 0x00,
  0x2a, 0x36, 0xba3a, 0x1d, 0x38, 0x00, 0x00, 0x00};

/**********************************************************************/
char *set_scancode (int value, int value2)
{
  if (value >= 0 && value < 0x68) {
    xlate[value] = (ULONG)value2;
    return NULL;
  } else
    return "Invalid scan code (must be between $00 and $67)";
}

/**********************************************************************/
void process_Xevents (void)
{
  ULONG class, scan;
  int count;
  UWORD code;
  UBYTE buffer[4], key;
  struct IntuiMessage *msg;

  while ((msg = (struct IntuiMessage *)GetMsg (window->UserPort)) != NULL) {
    class = msg->Class;
    code = msg->Code;
    ReplyMsg ((struct Message *)msg);
    switch (class) {
      case IDCMP_RAWKEY:
        if (code >= 0x80)
          key = code & 0x7f;
        else
          key = code;
        if (key < 0x68 && (scan = xlate[key]) != 0) {
          for (count = 0; scan != 0; count++) {
            if (key != 0x62 && code >= 0x80)
              buffer[count] = 0x80 | (scan & 0xff);
            else
              buffer[count] = scan & 0xff;
            scan >>= 8;
          }
          if (port60_buffer_ok (count))
            put_scancode (buffer, count);
        }
        break;
      default:
        break;
    }
  }
}

/**********************************************************************/
void flush_X (void)
{
}

/**********************************************************************/
void copy (unsigned x1, unsigned y1, unsigned x2, unsigned y2,
           unsigned nx, unsigned ny)
{
  /* printf ("copy (%u, %u, %u, %u, %u, %u)\n", x1, y1, x2, y2, nx, ny); */
  x1 *= fontwidth;
  y1 *= fontheight;
  x2 = (x2+1)*fontwidth;
  y2 = (y2+1)*fontheight;
  nx *= fontwidth;
  ny *= fontheight;

  ClipBlit (rp, x1, y1,
            rp, nx, ny, x2 - x1, y2 - y1,
            0xc0);
}

/**********************************************************************/
void draw_char (unsigned x, unsigned y, char c, UBYTE attr)
{
  SetAPen (rp, attr & 0x0f);
  SetBPen (rp, (attr & 0x70) >> 4);
  Move (rp, x * fontwidth, (y + 1) * fontheight - fontdescent - 1);
  Text (rp, &c, 1);
}

/**********************************************************************/
void draw_line (unsigned x, unsigned y, char *text, unsigned len, UBYTE attr)
{
  SetAPen (rp, attr & 0x0f);
  SetBPen (rp, (attr & 0x70) >> 4);
  Move (rp, x * fontwidth, (y + 1) * fontheight - fontdescent - 1);
  Text (rp, text, len);
}

/**********************************************************************/
void clear_screen (void)
{
  SetRast (rp, 0);
}

/**********************************************************************/
void window_size (unsigned textwidth, unsigned textheight)
{
  if (textwidth * fontwidth != width || textheight * fontheight != height) {
    close_display ();
    open_display (textwidth * fontwidth, textheight * fontheight);
  }
}

/**********************************************************************/
void new_cursor (int st, int end)
{
  cursor_st = st;
  cursor_height = end - st + 1;
}

/**********************************************************************/
void put_cursor (unsigned x, unsigned y)
{
  if (cursor_height < 1)
    return;

  cursor_x = x * fontwidth;
  cursor_y = y * fontheight + cursor_st;
  ClipBlit (rp, cursor_x, cursor_y,
            &cursor_rp, 0, 0, fontwidth, cursor_height,
            0xc0);
  RectFill (rp, cursor_x, cursor_y,
            cursor_x + fontwidth - 1, cursor_y + cursor_height - 1);
}

/**********************************************************************/
void unput_cursor (void)
{
  if (cursor_height < 1)
    return;

  ClipBlit (&cursor_rp, 0, 0,
            rp, cursor_x, cursor_y, fontwidth, cursor_height,
            0xc0);
}

/**********************************************************************/
void _STDastuff_cleanup (void)
{
  end_X ();
}

/**********************************************************************/
extern int graphics, blink;

/**********************************************************************/
