/* imlac_dp.c: Imlac display processor

   Copyright (c) 2020, Lars Brinkhoff

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
   LARS BRINKHOFF BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
   IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

   Except as contained in this notice, the name of Lars Brinkhoff shall not be
   used in advertising or otherwise to promote the sale, use or other dealings
   in this Software without prior written authorization from Lars Brinkhoff.
*/

#include "imlac_defs.h"

/* Debug */
#define DBG             0001

/* Alpha display processor */
#define DP_ALPHA        0001

static t_addr DPC;
static t_addr DT[8];
static uint16 SP = 0;
static uint16 ON = 0;
static uint16 HALT = 0;
static uint16 MODE = 0;
static uint16 XMSB, YMSB;
static uint16 XLSB, YLSB;
static uint16 SCALE = 2;
static uint16 BLOCK = 0;
static uint16 MIT8K;
static uint16 SGR;
static uint16 SYNC = 1;
static uint16 HZ = 40;

/* Function declaration. */
static uint16 dp_iot (uint16, uint16);
static t_stat dp_svc (UNIT *uptr);
static t_stat dp_reset(DEVICE *dptr);
static uint16 sync_iot (uint16, uint16);
static t_stat sync_svc (UNIT *uptr);

static IMDEV dp_imdev = {
  3,
  { { 0000, dp_iot, { NULL, NULL, NULL, "DLA" } },
    { 0001, dp_iot, { NULL, "CTB", "DOF", NULL } },
    { 0030, dp_iot, { NULL, NULL, NULL, "DCF" } } }
};

static UNIT dp_unit = {
  UDATA (&dp_svc, UNIT_IDLE, 0)
};

static REG dp_reg[] = {
  { ORDATAD (DPC, DPC, 16, "Display program counter") },
  { ORDATAD (ON, ON, 1, "Display on") },
  { ORDATAD (HALT, HALT, 1, "Display halted") },
  { ORDATAD (MODE, MODE, 1, "Display mode") },
  { BRDATAD (DT, DT, 8, 16, 8, "Return address stack") },
  { ORDATAD (SP, SP, 3, "Stack pointer") },
  { ORDATAD (XMSB, XMSB, 7, "X accumulator MSB") },
  { ORDATAD (XLSB, XLSB, 5, "X accumulator LSB") },
  { ORDATAD (YMSB, YMSB, 7, "Y accumulator MSB") },
  { ORDATAD (YLSB, YLSB, 5, "Y accumulator LSB") },
  { ORDATAD (SCALE, SCALE, 3, "Scale") },
  { ORDATAD (BLOCK, BLOCK, 3, "Block") },
  { ORDATAD (MIT8K, MIT8K, 1, "MIT 8K addressing") },
  { ORDATAD (SGR, SGR, 1, "Suppressed grid mode") },
  { NULL }
};

static MTAB dp_mod[] = {
  { DP_ALPHA, DP_ALPHA, "Alpha machine", "ALPHA",
    NULL, NULL, NULL, "Enables Alpha display processor" },
  { DP_ALPHA, 0,        "Graphics machine", "GRAPHICS",
    NULL, NULL, NULL, "Disables Alpha display processor" },
  { 0 }
};

static DEBTAB dp_deb[] = {
  { "DBG", DBG },
  { NULL, 0 }
};

DEVICE dp_dev = {
  "DP", &dp_unit, dp_reg, dp_mod,
  1, 8, 16, 1, 8, 16,
  NULL, NULL, dp_reset,
  NULL, NULL, NULL, &dp_imdev, DEV_DEBUG, 0, dp_deb,
  NULL, NULL, NULL, NULL, NULL, NULL
};

static UNIT sync_unit = {
  UDATA (&sync_svc, UNIT_IDLE, 0)
};

static REG sync_reg[] = {
  { ORDATAD (SYNC, SYNC, 1, "Flag") },
  { ORDATAD (HZ,   HZ,   1, "Frequency") },
  { NULL }
};

static IMDEV sync_imdev = {
  1,
  { { 0007, sync_iot, { NULL, "SCF", "IOS", NULL } } }
};

static DEBTAB sync_deb[] = {
  { "DBG", DBG },
  { NULL, 0 }
};

DEVICE sync_dev = {
  "SYNC", &sync_unit, sync_reg, NULL,
  1, 8, 16, 1, 8, 16,
  NULL, NULL, NULL,
  NULL, NULL, NULL,
  &sync_imdev, DEV_DEBUG, 0, sync_deb,
  NULL, NULL, NULL, NULL, NULL, NULL
};

void
dp_on (int flag)
{
  if (!ON && flag) {
    SP = 0;
    MIT8K = 0;
    sim_activate_abs (&dp_unit, 0);
    sim_debug (DBG, &dp_dev, "Display on\n");
  } else if (ON && !flag) {
    sim_cancel (&dp_unit);
    sim_debug (DBG, &dp_dev, "Display off\n");
    crt_idle ();
    if (SYNC && HALT)
      flag_on (FLAG_SYNC);
  }
  ON = (uint16) flag;
}

uint16
dp_is_on (void)
{
  return ON;
}

static uint16
dp_iot (uint16 insn, uint16 AC)
{
  if ((insn & 0771) == 0001) { /* DLZ */
    sim_debug (DBG, &dp_dev, "DPC cleared\n");
    DPC = 0;
  }
  if ((insn & 0772) == 0002) { /* DLA */
    sim_debug (DBG, &dp_dev, "DPC set to %06o\n", AC & memmask);
    DPC = AC & memmask;
    BLOCK = (AC >> 12) & 3;
  }
  if ((insn & 0771) == 0011) { /* CTB */
    ;
  }
  if ((insn & 0772) == 0012) { /* DOF */
    dp_on (0);
  }
  if ((insn & 0774) == 0304) { /* DCF */
    HALT = 0;
  }
  return AC;
}

static uint16 deflect (uint16 msb, uint16 lsb)
{
  if (dp_unit.flags & DP_ALPHA)
    return 18*msb + lsb;
  else
    return (msb << 5) + lsb;
}

static void increment (uint16 *msb, uint16 *lsb, uint16 x)
{
  /* On an Alpha machine, the LSB doesn't carry to the MSB. */

  *lsb += SCALE * x;
  if (dp_unit.flags & DP_ALPHA)
    return;

  *msb += *lsb >> 5;
  *lsb &= 037;
}

static void decrement (uint16 *msb, uint16 *lsb, uint16 x)
{
  /* On an Alpha machine, the LSB doesn't carry to the MSB. */

  *lsb -= SCALE * x;
  if (dp_unit.flags & DP_ALPHA)
    return;

  *msb += ((int16)*lsb) >> 5;
  *lsb &= 037;
}

static int scale_or_one (void)
{
  /* On an Alpha machine, the MSB is incremented per the scaling
     factor.  On a Graphics machine, it's incremented by one
     regardless of the scaling factor. */

  if (dp_unit.flags & DP_ALPHA)
    return SCALE >> 1;
  else
    return 1;
}

static void load_xy_accumulator (uint16 *msb, uint16 *lsb, uint16 insn)
{
  if (dp_unit.flags & DP_ALPHA)
    *msb = (insn >> 5) & 0177;
  else
    *msb = (insn >> 4) & 0077;
  *lsb = (insn << 1) & 0036;
}

static t_stat
dp_opr(uint16 insn)
{
  if ((insn & 04000) == 0) {
    sim_debug (DBG, &dp_dev, "DHLT ");
    HALT = 1;
  }
  else if (insn == 04000)
    sim_debug (DBG, &dp_dev, "DNOP");
  dp_on ((insn & 04000) != 0); /* DHLT */

  switch (insn & 00014) {
  case 000:
    if (insn & 1) {
      sim_debug (DBG, &dp_dev, "DADR ");
      MIT8K = !MIT8K; 
    }
    break;
  case 004:
    sim_debug (DBG, &dp_dev, "DSTS%o ", insn & 3);
    SCALE = insn & 3;
    if (SCALE == 0)
      SCALE = 1;
    else
      SCALE *= 2;
    break;
  case 010:
    sim_debug (DBG, &dp_dev, "DSTB%o ", insn & 3);
    BLOCK = insn & 3;
    break;
  case 014:
    sim_debug (DBG, &dp_dev, "DSTL%o ", insn & 3);
    /* TODO: Light pen. */
    break;
  }
  if (insn & 00020) { /* DDSP */
    sim_debug (DBG, &dp_dev, "DDSP ");
    crt_point (deflect (XMSB, XLSB), deflect (YMSB, YLSB));
  }
  if (insn & 00040) { /* DRJM */
    sim_debug (DBG, &dp_dev, "DRJM ");
    if (SP > 0)
      DPC = DT[--SP];
    else
      sim_debug (DBG, &dp_dev, "stack underflow");
  }
  if (insn & 00100) { /* DDYM */
    sim_debug (DBG, &dp_dev, "DDYM ");
    YMSB -= scale_or_one ();
  }
  if (insn & 00200) { /* DDXM */
    sim_debug (DBG, &dp_dev, "DDXM ");
    XMSB -= scale_or_one ();
  }
  if (insn & 00400) { /* DIYM */
    sim_debug (DBG, &dp_dev, "DIYM ");
    YMSB += scale_or_one ();
  }
  if (insn & 01000) { /* DIXM */
    sim_debug (DBG, &dp_dev, "DIXM ");
    XMSB += scale_or_one ();
  }
  if (insn & 02000) { /* DHVC */
    sim_debug (DBG, &dp_dev, "DHVC ");
    crt_hvc ();
  }

  sim_debug (DBG, &dp_dev, "\n");
  return SCPE_OK;
}

static void
jump (uint16 insn)
{
  DPC = insn & 07777;
  if (MIT8K)
    DPC |= (insn & 0100000) >> 3;
  else
    DPC |= BLOCK << 12;
}

static void
dp_sgr (uint16 insn)
{
  sim_debug (DBG, &dp_dev, "DSGR %o\n", insn & 7);

  SGR = insn & 1;
  if (insn & 1) {
    sim_debug (DBG, &dp_dev, "Enter SGR mode\n");
  } else {
    sim_debug (DBG, &dp_dev, "Exit SGR mode\n");
  }
  if (insn & 2) {
    sim_debug (DBG, &dp_dev, "SGR: Return\n");
  }
  if (insn & 4) {
    sim_debug (DBG, &dp_dev, "SGR: Beam on\n");
  } else {
    sim_debug (DBG, &dp_dev, "SGR: Beam off\n");
  }
}

static void
dp_opt (uint16 insn)
{
  switch (insn & 07770) {
  case 07660: /* ASG-1 */
  case 07667:
    break;
  case 07720: /* VIC-1 */
  case 07730:
    break;
  case 07740: /* MCI-1 */
  case 07750:
    break;
  case 07760: /* STI-1 or LPA-1 */
    break;
  case 07770: /* SGR-1 */
    dp_sgr (insn);
    break;
  default:
    sim_debug (DBG, &dp_dev, "Unknown instruction: %06o ", insn);
    break;
  }
}

static void
dp_inc_vector (uint16 byte)
{
  uint16 x1 = deflect (XMSB, XLSB), y1 = deflect (YMSB, YLSB);
  uint16 dx, dy;

  if (byte == 0200) {
    sim_debug (DBG, &dp_dev, "P\n");
  } else {
    sim_debug (DBG, &dp_dev, "%s", byte & 0100 ? "B" : "D");
    if (byte & 00040)
      sim_debug (DBG, &dp_dev, "M");
    sim_debug (DBG, &dp_dev, "%o", (byte >> 3) & 3);
    if (byte & 00004)
      sim_debug (DBG, &dp_dev, "M");
    sim_debug (DBG, &dp_dev, "%o\n", byte & 3);
  }

  dx = (byte >> 3) & 3;
  dy = byte & 3;
  if (byte & 040)
    decrement (&XMSB, &XLSB, dx);
  else
    increment (&XMSB, &XLSB, dx);
  if (byte & 4)
    decrement (&YMSB, &YLSB, dy);
  else
    increment (&YMSB, &YLSB, dy);
  if (byte & 0100)
    crt_line (x1, y1, deflect (XMSB, XLSB), deflect (YMSB, YLSB));
}

static void
dp_inc_escape (uint16 byte)
{
  if (byte == 0100)
    sim_debug (DBG, &dp_dev, "T");
  else if (byte == 0140)
    sim_debug (DBG, &dp_dev, "X");
  else if (byte == 0151)
    sim_debug (DBG, &dp_dev, "R");
  else
    sim_debug (DBG, &dp_dev, "%03o", byte);

  if (byte & 0100)
    MODE = 0;
  if (byte & 040) {
    if (SP > 0)
      DPC = DT[--SP];
    else
      sim_debug (DBG, &dp_dev, "stack underflow");
  }
  if (byte & 020)
    XMSB += scale_or_one ();
  if (byte & 010)
    XLSB = 0;
  if (byte & 4) /* Enter PPM mode. */
    ;
  if (byte & 2)
    YMSB += scale_or_one ();
  if (byte & 1)
    YLSB = 0;

  sim_debug (DBG, &dp_dev, "\n");
}

static void
dp_inc (uint16 byte)
{
  if (byte & 0200) {
    /* Increment byte. */
    dp_inc_vector (byte);
  } else {
    /* Escape byte. */
    dp_inc_escape (byte);
  }
}

static void
dp_deim (uint16 insn)
{
  MODE = 1;
  sim_debug (DBG, &dp_dev, "E,");
  dp_inc (insn & 0377);
}

static void
dp_dlvh (uint16 insn1, uint16 insn2, uint16 insn3)
{
  uint16 x1 = deflect (XMSB, XLSB), y1 = deflect (YMSB, YLSB);
  uint16 m, n, dx, dy;
  m = insn2 & 07777;
  n = insn3 & 07777;
  if (insn3 & 010000) {
    dx = n;
    dy = m;
  } else {
    dx = m;
    dy = n;
  }
  if (insn3 & 040000)
    decrement (&XMSB, &XLSB, dx);
  else
    increment (&XMSB, &XLSB, dx);
  if (insn3 & 020000)
    decrement (&YMSB, &YLSB, dy);
  else
    increment (&YMSB, &YLSB, dy);
  if (insn2 & 020000)
    crt_line (x1, y1, deflect (XMSB, XLSB), deflect (YMSB, YLSB));
}

static void
dp_insn (uint16 insn)
{
  switch ((insn >> 12) & 7) {
  case 0: /* DOPR */
    dp_opr (insn);
    break;
  case 1: /* DLXA */
    sim_debug (DBG, &dp_dev, "DLXA %04o\n", insn & 01777);
    load_xy_accumulator (&XMSB, &XLSB, insn);
    break;
  case 2: /* DLYA */
    sim_debug (DBG, &dp_dev, "DLYA %04o\n", insn & 01777);
    load_xy_accumulator (&YMSB, &YLSB, insn);
    break;
  case 3: /* DEIM */
    sim_debug (DBG, &dp_dev, "DEIM ");
    dp_deim (insn);
    break;
  case 4: /* DLVH */
    sim_debug (DBG, &dp_dev, "DLVH\n");
    dp_dlvh (insn, M[DPC], M[DPC+1]);
    DPC += 2;
    break;
  case 5: /* DJMS */
    sim_debug (DBG, &dp_dev, "DJMS\n");
    if (SP < 7)
      DT[SP++] = DPC;
    else
      sim_debug (DBG, &dp_dev, "stack overflow");
    jump (insn);
    break;
  case 6: /* DJMP */
    sim_debug (DBG, &dp_dev, "DJMP\n");
    jump (insn);
    break;
  case 7: /* optional */
    dp_opt (insn);
    break;
  }
}

static t_stat
dp_svc(UNIT * uptr)
{
  uint16 insn;

  if (sim_brk_summ && sim_brk_test(DPC, SWMASK('D'))) {
    sim_activate_abs (&dp_unit, 0);
    return sim_messagef (SCPE_STOP, "Display processor breakpoint.\n");
  }

  sim_debug (DBG, &dp_dev, "%06o ", DPC);
  insn = M[DPC];
  DPC++;
  if (MODE) {
    sim_debug (DBG, &dp_dev, "INC ");
    dp_inc (insn >> 8);
    if (MODE) {
      sim_debug (DBG, &dp_dev, ",");
      dp_inc (insn & 0377);
    }
    sim_debug (DBG, &dp_dev, "\n");
  } else
    dp_insn (insn);

  if (ON)
    sim_activate_after (&dp_unit, 2);

  return SCPE_OK;
}

static t_stat
dp_reset(DEVICE * uptr)
{
  sim_activate_abs (&dp_unit, 0);
  return SCPE_OK;
}

static t_stat
sync_svc (UNIT *uptr)
{
  sim_debug (DBG, &sync_dev, "Display sync (%d Hz)\n", HZ);
  SYNC = 1;
  if (SYNC && HALT)
    flag_on (FLAG_SYNC);
  sim_cancel (&sync_unit);
  return SCPE_OK;
}

static uint16
sync_iot (uint16 insn, uint16 AC)
{
  if ((insn & 0771) == 0071) { /* SCF */
    sim_debug (DBG, &sync_dev, "Clear flag\n");
    SYNC = 0;
    flag_off (FLAG_SYNC);
    sim_activate_after (&sync_unit, 1000000 / HZ);
  }
  if ((insn & 0772) == 0072) { /* IOS */
  }
  return AC;
}
