// license:BSD-3-Clause

#include "driver.h"
#include "core.h"
#include "wpc.h"
#include "sim.h"
#include "wmssnd.h"
#include "machine/4094.h"

/* 150904 Added Saucer LEDs (GV) */
/* 231100 Added Sound Support (SJE) */
/* 161000 Cleaned up BITIMP use */
/* 181000 Corrected year to 1995 according to IPD */
/* 191000 Added Drop target status and corrected condition */
/* 231000 Now uses textOutf */
/*--------------
/ Keys
/   +R   L/R Ramp
/   +I   L/R Inlane
/   +-   L/R Slingshot
/   +L   L/R Loop
/   +O   L/R Outlane
/   +X   L/R Bonus X
/   +S   L/R Sauser
/   C    Center Ramp
/JK[]',: MARTIAN (7)
/  VBN   Jet (3)
/  FGH	 ForceField (3)
/   D    Drop Target
/   Z    Hole
/   L    Stroke of Luck
/---------------------------*/

#define AFM_BANKTIME  300
#define AFM_BANKSLACK  50

/*-- custom key conditions --*/
#define AFM_DROPCOND    1  /* Drop traget reachable */
#define AFM_CTCOND      2  /* Center trough reachable */

/*------------------
/  Local functions
/-------------------*/
static void init_afm(void);
static int  afm_getSol(int solNo);
static void afm_handleMech(int mech);
static int afm_getMech(int mechNo);
static int  afm_keyCond(int sw, int ballState, int *inports);
static void afm_drawMech(BMTYPE **line);
static void afm_drawStatic(BMTYPE **line);

/*-----------------------
/ local static variables
/------------------------*/
static struct afm_tLocals {
  int bankPos;     /* bank motor position */
} afm_locals;

/*--------------------------
/ Game specific input ports
/---------------------------*/
WPC_INPUT_PORTS_START(afm, 4)
  PORT_START /* 0 */
    COREPORT_BIT(   0x0001, "M",               KEYCODE_J)
    COREPORT_BIT(   0x0002, "A",               KEYCODE_K)
    COREPORT_BIT(   0x0004, "R",               KEYCODE_OPENBRACE)
    COREPORT_BIT(   0x0008, "T",               KEYCODE_CLOSEBRACE)
    COREPORT_BIT(   0x0010, "I",               KEYCODE_QUOTE)
    COREPORT_BIT(   0x0020, "A",               KEYCODE_COMMA)
    COREPORT_BIT(   0x0040, "N",               KEYCODE_COLON)
    COREPORT_BIT(   0x0080, "Jet 1",           KEYCODE_V)
    COREPORT_BIT(   0x0100, "Jet 2",           KEYCODE_B)
    COREPORT_BIT(   0x0200, "Jet 3",           KEYCODE_N)
    COREPORT_BIT(   0x0400, "Force 1",         KEYCODE_F)
    COREPORT_BIT(   0x0800, "Force 2",         KEYCODE_G)
    COREPORT_BIT(   0x1000, "Force 3",         KEYCODE_H)
    COREPORT_BITIMP(0x2000, "Drop Target",     KEYCODE_D)
    COREPORT_BITIMP(0x4000, "Hole",            KEYCODE_Z)
    COREPORT_BITIMP(0x8000, "Stroke of Luck",  KEYCODE_U)
  PORT_START /* 1 */
    COREPORT_BIT(   0x0001, "Left Qualifier",  KEYCODE_LCONTROL)
    COREPORT_BIT(   0x0002, "Right Qualifier", KEYCODE_RCONTROL)
    COREPORT_BITIMP(0x0004, "L/R Ramp",        KEYCODE_R)
    COREPORT_BITIMP(0x0008, "L/R Outlane",     KEYCODE_O)
    COREPORT_BITIMP(0x0010, "L/R Loop",        KEYCODE_L)
    COREPORT_BITIMP(0x0020, "L/R Bonus X",     KEYCODE_X)
    COREPORT_BIT(   0x0040, "L/R Slingshot",   KEYCODE_MINUS)
    COREPORT_BIT(   0x0080, "L/R Inlane",      KEYCODE_I)
    COREPORT_BIT(   0x0100, "L/R Saucer",      KEYCODE_S)
    COREPORT_BITIMP(0x0200, "Center Ramp",     KEYCODE_C)
    COREPORT_BITIMP(0x0400, "Drain",           KEYCODE_Q)
WPC_INPUT_PORTS_END

/*-------------------
/ Switch definitions
/--------------------*/
#define swLaunch    11
#define swStart     13
#define swTilt      14
#define swLOut      16
#define swRIn       17
#define swSLane     18

#define swSlamTilt  21
#define swCoinDoor  22
#define swLIn       26
#define swROut      27

#define swTEject    31
#define swTrough1   32
#define swTrough2   33
#define swTrough3   34
#define swTrough4   35
#define swLPop      36
#define swRPop      37
#define swLTopRoll  38

#define swMARTIaN   41
#define swMARTIAn   42
#define swMARtIAN   43
#define swMARTiAN   44
#define swLMotor    45
#define swCMotor    46
#define swRMotor    47
#define swRTopRoll  48

#define swLSling    51
#define swRSling    52
#define swJet1      53
#define swJet2      54
#define swJet3      55
#define swmARTIAN   56
#define swMaRTIAN   57
#define swMArTIAN   58
#define swLRampEnt  61
#define swCRampEnt  62
#define swRRampEnt  63
#define swLRampTop  64
#define swRRampEx   65
#define swMBankDn   66
#define swMBankUp   67

#define swRLoopHi   71
#define swRLoopLo   72
#define swLLoopHi   73
#define swLLoopLo   74
#define swLSaucer   75
#define swRSaucer   76
#define swDrop      77
#define swCTrough   78

/*----------------------
/ Solenoid definitions
/----------------------*/
#define sAutoFire   1
#define sBallRel    2
#define sLPop       3
#define sRPop       4
#define sLAlienLo   5
#define sLAlienHi   6
#define sKnocker    7
#define sRAlienHi   8
#define sLSling     9
#define sRSling    10
#define sJet1      11
#define sJet2      12
#define sJet3      13
#define sRAlienLo  14
#define sSaucer    15
#define sDrop      16
#define sBankMotor 24
#define sRGate     CORE_CUSTSOLNO(1) /* 33 */
#define sLGate     CORE_CUSTSOLNO(2) /* 34 */
#define sDiverter  CORE_CUSTSOLNO(3) /* 35 (power) & 36 (hold) */

/*---------------------
/  Ball state handling
/----------------------*/
enum { stBonusX=SIM_FIRSTSTATE, stDrain, stDrop,
       stLOut, stRIn, stSLane, stLIn, stROut,
       stOuthole, stTEject, stTrough1, stTrough2, stTrough3, stTrough4,
       stLPop, stRPop, stLTopRoll, stRTopRoll, stLRampEnt,
       stCRampEnt, stRRampEnt, stLRampTop, stRRampEx,
       stRLoopHiUp, stRLoopLoUp, stLLoopHiUp, stLLoopLoUp,
       stRLoopHiDn, stRLoopLoDn, stLLoopHiDn, stLLoopLoDn, stCTrough
};

static sim_tState afm_stateDef[] = {
  {"Not Installed",      0,0,         0,        stDrain,     0,0,0,SIM_STNOTEXCL},
  {"Moving",},
  {"Playfield",                 0,0,         0,        0,           0,0,0,SIM_STNOTEXCL},
  {"Bonus X Area",       0,0,         0,        0,           0,0,0,SIM_STNOTEXCL},
  {"Drain",              1,0,         0,        stOuthole,   5,0,0,SIM_STNOTEXCL}, /* stDrain */
  {"Drop Target",        5,swDrop,    0,        stFree,      5,0,0,SIM_STSWKEEP},
  {"Left Outlane",       1,swLOut,    0,        stDrain,     5}, /* stLOut */
  {"Right Inlane",       1,swRIn,     0,        stFree,      5}, /* stRin */
  {"Shooter Lane",       1,swSLane,   sAutoFire,stRLoopHiUp,10}, /* stSLane */
  {"Left Inlane",        1,swLIn,     0,        stFree,      5}, /* stLin */
  {"Right Outlane",      1,swROut,    0,        stDrain,     5}, /* stRout */
  {"Outhole",            1,0,         0,        stTrough4,   5}, /* stOuthole */
/* I have no idea what this solenoid does. the simulator seems to work fine without it */
  {"Trough Eject",       1,swTEject,  0,        stFree,      5}, /* stTEject */
  {"Trough 1",           1,swTrough1, sBallRel, stSLane,     5}, /* stTrough1 */
  {"Trough 2",           1,swTrough2, 0,        stTrough1,   5}, /* stTrough2 */
  {"Trough 3",           1,swTrough3, 0,        stTrough2,   5}, /* stTrough3 */
  {"Trough 4",           1,swTrough4, 0,        stTrough3,   5}, /* stTrough4 */
  {"Left Popper",        1,swLPop,    sLPop,    stLIn,      10}, /* stLPop */ /* up to left ramp */
  {"Right Popper",       1,swRPop,    sRPop,    stFree,      5}, /* stRPop */ /* to playfield */
  {"Left Top Roll",      1,swLTopRoll,0,        stFree,      5}, /* stLTopRoll */
  {"Right Top Roll",     1,swRTopRoll,0,        stFree,      5}, /* stRTopRoll */
  {"Left Ramp Ent",      1,swLRampEnt,0,        stLRampTop,  5}, /* stLRampEnt */
  {"Center Ramp Ent",    1,swCRampEnt,0,        stRRampEx,   5,sDiverter,stLPop}, /* stCRampEnt */
  {"Right Ramp Ent",     1,swRRampEnt,0,        stRRampEx,   5}, /* stRRampEnt */
  {"Left Ramp Top",      1,swLRampTop,0,        stLIn,       5}, /* stLRampTop */
  {"Right Ramp Exit",    1,swRRampEx, 0,        stRIn,       5}, /* stRRampEx */
  {"Right Loop Hi",      3,swRLoopHi, 0,        stBonusX,    5,sRGate,stLLoopHiDn}, /* stRLoopHiUp */
  {"Right Loop Lo",      3,swRLoopLo, 0,        stRLoopHiUp, 3}, /* stRLoopLoUp */
  {"Left Loop Hi",       3,swLLoopHi, 0,        stBonusX,    3,sLGate,stRLoopHiDn}, /* stLLoopHiUp */
  {"Left Loop Lo",       3,swLLoopLo, 0,        stLLoopHiUp, 3}, /* stLLoopLoUp */
  {"Right Loop Hi",      3,swRLoopHi, 0,        stRLoopLoDn, 3}, /* stRLoopHiDn */
  {"Right Loop Lo",      3,swRLoopLo, 0,        stFree,      3}, /* stRLoopLoDn */
  {"Left Loop Hi",       3,swLLoopHi, 0,        stLLoopLoDn, 3}, /* stLLoopHiDn */
  {"Left Loop Lo",       3,swLLoopLo, 0,        stFree,      3}, /* stLLoopLoDn */
  {"Center Trough",      3,swCTrough, 0,        stLPop,      3},
  {0}
};

/*-- return status of custom solenoids --*/
static int afm_getSol(int solNo) {
  if (solNo == sLGate)    return (wpc_data[WPC_FLIPPERCOIL95] & 0x10) > 0;
  if (solNo == sRGate)    return (wpc_data[WPC_FLIPPERCOIL95] & 0x20) > 0;
  if (solNo == sDiverter) return (wpc_data[WPC_FLIPPERCOIL95] & 0xc0) > 0;
  return 0;
}

static void afm_handleMech(int mech) {
  /*---------------
  /  handle bank
  /----------------*/
  if (mech & 0x01) {
    if (core_getSol(sBankMotor)) afm_locals.bankPos = (afm_locals.bankPos + 1) % AFM_BANKTIME;
    core_setSw(swMBankDn, afm_locals.bankPos < AFM_BANKSLACK);
    core_setSw(swMBankUp, (afm_locals.bankPos >= AFM_BANKTIME/2) &&
                         (afm_locals.bankPos <  AFM_BANKTIME/2 + AFM_BANKSLACK));
  }
  /*-------------
  /  Drop target
  /--------------*/
  if ((mech & 0x02) && core_getSol(sDrop)) {
    core_setSw(swDrop, FALSE);
  }
}

static int afm_getMech(int mechNo) {
  return afm_locals.bankPos;
}
/* The drop target and hole behind can only be reached if the bank is down */
static int afm_keyCond(int cond, int ballState, int *inports) {
  switch (cond) {
    case AFM_DROPCOND:
      /* ball on PF & bank down & drop up */
      return (ballState == stFree) && core_getSw(swMBankDn) && !core_getSw(swDrop);
    case AFM_CTCOND:
      /* ball on PF & bank down & drop down */
      return (ballState == stFree) && core_getSw(swMBankDn) && core_getSw(swDrop);
  }
  return FALSE;
}

/*---------------------------
/  Keyboard conversion table
/----------------------------*/
static sim_tInportData afm_inportData[] = {
  {0, 0x0001, swmARTIAN},
  {0, 0x0002, swMaRTIAN},
  {0, 0x0004, swMArTIAN},
  {0, 0x0008, swMARtIAN},
  {0, 0x0010, swMARTiAN},
  {0, 0x0020, swMARTIaN},
  {0, 0x0040, swMARTIAn},
  {0, 0x0080, swJet1},
  {0, 0x0100, swJet2},
  {0, 0x0200, swJet3},
  {0, 0x0400, swLMotor, swMBankUp},
  {0, 0x0800, swCMotor, swMBankUp},
  {0, 0x1000, swRMotor, swMBankUp},
  {0, 0x2000, stDrop,   SIM_CUSTCOND(AFM_DROPCOND)},
  {0, 0x4000, stCTrough,SIM_CUSTCOND(AFM_CTCOND)},
  {0, 0x8000, stRPop},
  {1, 0x0005, stLRampEnt},
  {1, 0x0006, stRRampEnt},
  {1, 0x0009, stLOut},
  {1, 0x000a, stROut},
  {1, 0x0011, stLLoopLoUp},
  {1, 0x0012, stRLoopLoUp},
  {1, 0x0021, stLTopRoll, stBonusX},
  {1, 0x0022, stRTopRoll, stBonusX},
  {1, 0x0041, swLSling},
  {1, 0x0042, swRSling},
  {1, 0x0081, stLIn},
  {1, 0x0082, stRIn},
  {1, 0x0101, swLSaucer, swMBankDn},
  {1, 0x0102, swRSaucer, swMBankDn},
  {1, 0x0200, stCRampEnt},
  {1, 0x0400, stDrain, SIM_STANY},
  {0}
};

/*--------------------
/ Drawing information
/---------------------*/
static void afm_drawMech(BMTYPE **line) {
  core_textOutf(50, 0,BLACK,"Bank: %-4s", core_getSw(swMBankUp) ? "Up" : (core_getSw(swMBankDn) ? "Down" : ""));
  core_textOutf(50,10,BLACK,"Drop: %-4s", core_getSw(swDrop) ? "Down" : "Up");
}

/* Help */
static void afm_drawStatic(BMTYPE **line) {
  core_textOutf(30, 50,BLACK,"Help on this Simulator:");
  core_textOutf(30, 60,BLACK,"L/R Ctrl+I/O = L/R Inlane/Outlane");
  core_textOutf(30, 70,BLACK,"L/R Ctrl+- = L/R Slingshot");
  core_textOutf(30, 80,BLACK,"L/R Ctrl+L = L/R Loop");
  core_textOutf(30, 90,BLACK,"L/R Ctrl+R = L/R Ramp");
  core_textOutf(30,100,BLACK,"L/R Ctrl+X = L/R Bonus X");
  core_textOutf(30,110,BLACK,"L/R Ctrl+S = L/R Saucer");
  core_textOutf(30,120,BLACK,"Q = Drain Ball, C = Center Ramp");
  core_textOutf(30,130,BLACK,"J/K/[/]/'/,/: = M/A/R/T/I/A/N Targets");
  core_textOutf(30,140,BLACK,"V/B/N = Left/Right/Bottom Jet Bumpers");
  core_textOutf(30,150,BLACK,"F/G/H = Forcefield Targets, Z = Hole");
  core_textOutf(30,160,BLACK,"D = Drop Target, L = Stroke of Luck");
}

/*-----------------
/  ROM definitions
/------------------*/
WPC_ROMSTART(afm,11, "mars1_1.rom",  0x080000,CRC(13b174d9) SHA1(57952f3184496b0316e4cf301e0181cb9de3519a))
DCS_SOUNDROM3m(	"afm_1_00.s2",CRC(610ff107) SHA1(9590f809a05cb2bda4979fa16f165e2e994719a0),
		"afm_s3.l1",CRC(1cbce9b1) SHA1(7f258bfe1904a879a2cb007419483f4fee91e072),
		"afm_s4.l1",CRC(5ff7fbb7) SHA1(ebaf825d3b90b6acee1920e6703801a4bcddfc5b))
WPC_ROMEND

WPC_ROMSTART(afm,11u, "marsu1_1.rom",  0x080000,CRC(bc1c0a0a) SHA1(859b40aaae46623f9b3519b5e422977d1724b715))
DCS_SOUNDROM3m(	"afm_s2.l1",CRC(6e39d96e) SHA1(b34e31bb1734c86614f153f7201163aaa9943cec),
		"afm_s3.l1",CRC(1cbce9b1) SHA1(7f258bfe1904a879a2cb007419483f4fee91e072),
		"afm_s4.l1",CRC(5ff7fbb7) SHA1(ebaf825d3b90b6acee1920e6703801a4bcddfc5b))
WPC_ROMEND

WPC_ROMSTART(afm,11pfx, "marspfx1_1.rom",  0x080000,CRC(89cc2d47) SHA1(4cabebff151de62066c8c69458a66a5a3dbcbd4b))
DCS_SOUNDROM3m(	"afm_s2.l1",CRC(6e39d96e) SHA1(b34e31bb1734c86614f153f7201163aaa9943cec),
		"afm_s3.l1",CRC(1cbce9b1) SHA1(7f258bfe1904a879a2cb007419483f4fee91e072),
		"afm_s4.l1",CRC(5ff7fbb7) SHA1(ebaf825d3b90b6acee1920e6703801a4bcddfc5b))
WPC_ROMEND

WPC_ROMSTART(afm,10, "afm_1_00.g11",  0x080000,CRC(1a30fe95) SHA1(218674e63ce4efeecb266f35f0f315758f7c72fc))
DCS_SOUNDROM3m(	"afm_1_00.s2",CRC(610ff107) SHA1(9590f809a05cb2bda4979fa16f165e2e994719a0),
		"afm_s3.l1",CRC(1cbce9b1) SHA1(7f258bfe1904a879a2cb007419483f4fee91e072),
		"afm_s4.l1",CRC(5ff7fbb7) SHA1(ebaf825d3b90b6acee1920e6703801a4bcddfc5b))
WPC_ROMEND

WPC_ROMSTART(afm,113,"afm_1_13.bin", 0x100000,CRC(e1fbd81b) SHA1(0ff35253d8eac7b75abb3e4db84cdcca458182cd))
DCS_SOUNDROM3m(	"afm_s2.l1",CRC(6e39d96e) SHA1(b34e31bb1734c86614f153f7201163aaa9943cec),
		"afm_s3.l1",CRC(1cbce9b1) SHA1(7f258bfe1904a879a2cb007419483f4fee91e072),
		"afm_s4.l1",CRC(5ff7fbb7) SHA1(ebaf825d3b90b6acee1920e6703801a4bcddfc5b))
WPC_ROMEND

WPC_ROMSTART(afm,113b,"afm_113b.bin", 0x100000,CRC(34fd2d7d) SHA1(57a41bd686286429880e63696d7d9d3990ca5d05))
DCS_SOUNDROM3m(	"afm_s2.l1",CRC(6e39d96e) SHA1(b34e31bb1734c86614f153f7201163aaa9943cec),
		"afm_s3.l1",CRC(1cbce9b1) SHA1(7f258bfe1904a879a2cb007419483f4fee91e072),
		"afm_s4.l1",CRC(5ff7fbb7) SHA1(ebaf825d3b90b6acee1920e6703801a4bcddfc5b))
WPC_ROMEND

WPC_ROMSTART(afm,f10, "fafm0_10.rom",  0x100000,CRC(fd075feb) SHA1(d17f5156062bac09412b0d2a3578f9a2ea41c8d9))
DCS_SOUNDROM3m(	"afm_s2.l1",CRC(6e39d96e) SHA1(b34e31bb1734c86614f153f7201163aaa9943cec),
		"afm_s3.l1",CRC(1cbce9b1) SHA1(7f258bfe1904a879a2cb007419483f4fee91e072),
		"afm_s4.l1",CRC(5ff7fbb7) SHA1(ebaf825d3b90b6acee1920e6703801a4bcddfc5b))
WPC_ROMEND

WPC_ROMSTART(afm,f20, "fafm0_20.rom",  0x100000,CRC(3a9ea1bc) SHA1(86719f55ac586dcefc38d658a8f583f96880a928))
DCS_SOUNDROM3m(	"afm_s2.l1",CRC(6e39d96e) SHA1(b34e31bb1734c86614f153f7201163aaa9943cec),
		"afm_s3.l1",CRC(1cbce9b1) SHA1(7f258bfe1904a879a2cb007419483f4fee91e072),
		"afm_s4.l1",CRC(5ff7fbb7) SHA1(ebaf825d3b90b6acee1920e6703801a4bcddfc5b))
WPC_ROMEND

WPC_ROMSTART(afm,f32, "fafm0_32.rom",  0x100000,CRC(e1ef2857) SHA1(dfe3c63f9829bbcea19690d74ad90a7e3ed42db4))
DCS_SOUNDROM3m(	"afm_s2.l1",CRC(6e39d96e) SHA1(b34e31bb1734c86614f153f7201163aaa9943cec),
		"afm_s3.l1",CRC(1cbce9b1) SHA1(7f258bfe1904a879a2cb007419483f4fee91e072),
		"afm_s4.l1",CRC(5ff7fbb7) SHA1(ebaf825d3b90b6acee1920e6703801a4bcddfc5b))
WPC_ROMEND

/*--------------
/  Game drivers
/---------------*/
CORE_GAMEDEF( afm,113,"Attack From Mars (1.13 / S1.1)", 1995, "Bally",wpc_m95S,0)
CORE_CLONEDEF(afm,113b,113,"Attack From Mars (1.13b / S1.1)", 1995, "Bally",wpc_m95S,0)
CORE_CLONEDEF(afm,11,113,"Attack From Mars (1.1 / S1.0)", 1995, "Bally",wpc_m95S,0)
CORE_CLONEDEF(afm,11u,113,"Attack From Mars (1.1 Ultrapin / S1.1)", 2006, "Global VR",wpc_m95S,0)
CORE_CLONEDEF(afm,11pfx,113,"Attack From Mars (1.1 Pinball FX / S1.1)", 2018, "Zen Studios",wpc_m95S,0)
CORE_CLONEDEF(afm,10,113,"Attack From Mars (1.0 / S1.0)", 1995, "Bally",wpc_m95S,0)
CORE_CLONEDEF(afm,f10,113,"Attack From Mars (FreeWPC 0.10)", 1995, "FreeWPC",wpc_m95S,0)
CORE_CLONEDEF(afm,f20,113,"Attack From Mars (FreeWPC 0.20)", 1995, "FreeWPC",wpc_m95S,0)
CORE_CLONEDEF(afm,f32,113,"Attack From Mars (FreeWPC 0.32)", 1995, "FreeWPC",wpc_m95S,0)

/*----------
/ Game Data
/-----------*/
static sim_tSimData afmSimData = {
  2,                   /* 2 input ports */
  afm_stateDef,        /* stateData*/
  afm_inportData,      /* inportData*/
  { stTrough1, stTrough2, stTrough3, stTrough4, stDrain, stDrain, stDrain },
  NULL,                /* initSim */
  NULL,                /* handleBallState*/
  afm_drawStatic,      /* help */
  FALSE,               /* automatic plunger */
  afm_keyCond          /* advanced key conditions */
};

static core_tGameData afmGameData = {
  GEN_WPC95, wpc_dispDMD,
  {
    FLIP_SW(FLIP_L | FLIP_U) | FLIP_SOL(FLIP_L),
    0,2,3,0,0,1,0, // 2 extra lamp columns for the LEDs
    afm_getSol, afm_handleMech, afm_getMech, afm_drawMech,
    NULL, NULL
  },
  &afmSimData,
  {
    "541 123456 12345 123",
    /*Coin    1     2     3     4     5     6     7     8     9    10   Cab.  Cust */
    { 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* inverted switches */
    /*Start    Tilt    SlamTilt    CoinDoor    Shooter */
    { swStart, swTilt, swSlamTilt, swCoinDoor, swLaunch},
  }
};

static WRITE_HANDLER(parallel_0_out) {
  coreGlobals.lampMatrix[8] = coreGlobals.tmpLampMatrix[8] = data;
}
static WRITE_HANDLER(parallel_1_out) {
  coreGlobals.lampMatrix[9] = coreGlobals.tmpLampMatrix[9] = data;
}
static WRITE_HANDLER(qspin_0_out) {
  HC4094_data_w(1, data);
}

static HC4094interface hc4094afm = {
  2, // 2 chips
  { parallel_0_out, parallel_1_out },
  { qspin_0_out }
};

#ifdef PROC_SUPPORT
  #include "p-roc/p-roc.h"
  #include "p-roc/proc_shift_reg.h"

  /*
    LEDs of saucer controlled by a serial shift register.  Game sets DATA and
    then pulses clock HIGH for less than 1ms, about every 100ms.  Except for
    when it's clearing the LEDs or pulsing a specific pattern and shifts with
    sub-millisecond timing.  We use a 64-bit queue to track shift requests,
    and a 12ms timer to shift them out at a rate the P-ROC can handle.
    
    Martians seem to be enabled for 0.5 to 1.1ms, disabled briefly (0.1ms),
    enabled for about 15ms, and disabled for 133ms.  Then the pattern repeats.
    
    For solenoids with immediate handling, invert the `smoothed` value to allow
    default handler to process the changes.
  */
  void afm_wpc_proc_solenoid_handler(int solNum, int enabled, int smoothed) {
    static int saucer_data = 0;
    switch (solNum) {
      case  4:  // C05 Left Alien Low
      case  5:  // C06 Left Alien High
      case  7:  // C08 Right Alien High
      case 13:  // C14 Right Alien Low

      case 16:  // C17 to C23, C25 to C28 flashers
      case 17:
      case 18:
      case 19:
      case 20:
      case 21:
      case 22:
      case 24:
      case 25:
      case 26:
      case 27:
      
      case 23:  // C24 Bank Motor
      case 38:  // C39 Strobe Light
        smoothed = !smoothed;
        break;

      case 37:  // C38 data for saucer LEDs
        if (!smoothed)
          saucer_data = enabled;
        return;

      case 36:  // C37 clock for saucer LEDs
        if (!smoothed && enabled) {
          proc_shiftRegEnqueue(saucer_data);
        }
        return;
    }
    default_wpc_proc_solenoid_handler(solNum, enabled, smoothed);
  }
#endif

static WRITE_HANDLER(afm_wpc_w) {
  wpc_w(offset, data);
  if (offset == WPC_SOLENOID1) {
    HC4094_data_w (0, GET_BIT5);
    HC4094_clock_w(0, GET_BIT4);
    HC4094_clock_w(1, GET_BIT4);
  }
}

/*---------------
/  Game handling
/----------------*/
static void init_afm(void) {
  core_gameData = &afmGameData;
  install_mem_write_handler(0, 0x3fb0, 0x3fff, afm_wpc_w);
  HC4094_init(&hc4094afm);
  HC4094_oe_w(0, 1);
  HC4094_oe_w(1, 1);
  HC4094_strobe_w(0, 1);
  HC4094_strobe_w(1, 1);
  wpc_set_fastflip_addr(0x80);
#ifdef PROC_SUPPORT
  wpc_proc_solenoid_handler = afm_wpc_proc_solenoid_handler;
  if (coreGlobals.p_rocEn) {
    // clock on C37, data on C38
    proc_shiftRegInit(36 + 32, 37 + 32);
  }
#endif
}

