/* xscreensaver, Copyright (c) 1998-2010 Jamie Zawinski <jwz@jwz.org>
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.  No representations are made about the suitability of this
 * software for any purpose.  It is provided "as is" without express or
 * implied warranty.
 *
 * Apple ][ CRT simulator, by Trevor Blackwell <tlb@tlb.org>
 * with additional work by Jamie Zawinski <jwz@jwz.org>
 */

#include <math.h>
#include "screenhackI.h"
#include "apple2.h"
#include "ximage-loader.h"

#ifdef HAVE_XSHM_EXTENSION
#include "xshm.h"
#endif

#include <sys/time.h>

/*
 * Implementation notes
 *
 * The A2 had 3 display modes: text, lores, and hires. Text was 40x24, and it
 * disabled color in the TV. Lores gave you 40x48 graphics blocks, using the
 * same memory as the text screen. Each could be one of 16 colors. Hires gave
 * you 280x192 pixels. Odd pixels were blue or purple, and even pixels were
 * orange or green depending on the setting of the high bit in each byte.
 *
 * The graphics modes could also have 4 lines of text at the bottom. This was
 * fairly unreadable if you had a color monitor.
 *
 * Each mode had 2 different screens using different memory space. In hires
 * mode this was sometimes used for double buffering, but more often the lower
 * screen was full of code/data and the upper screen was used for display, so
 * you got random garbage on the screen.
 *
 * The text font is based on X's standard 6x10 font, with a few tweaks like
 * putting a slash across the zero.
 *
 * To use this, you'll call apple2(display, window, duration,
 * controller) where the function controller defines what will happen.
 * See bsod.c and apple2-main.c for example controllers. The
 * controller function gets called whenever the machine ready to start
 * something new. By setting sim->printing or sim->typing, it'll be
 * busy for some time spitting characters out one at a time. By
 * setting *next_actiontime+=X.X, it'll pause and just update the screen
 * for that long before calling the controller function again.
 *
 * By setting stepno to A2CONTROLLER_DONE, the loop will end. It will also end
 * after the time specified by the delay parameter. In either case, it calls
 * the controller with stepno==A2CONTROLLER_FREE to allow it to release any
 * memory.
 *
 * The void* apple2_sim_t::controller_data is for the use of the controller.
 * It will be initialize to NULL, and the controller can store its own state
 * there.
 *
 */

void
a2_scroll(apple2_state_t *st)
{
  int i;
  st->textlines[st->cursy][st->cursx] |= 0xc0;     /* turn off cursor */

  for (i=0; i<23; i++) {
    memcpy(st->textlines[i],st->textlines[i+1],40);
  }
  memset(st->textlines[23],0xe0,40);
}

static void
a2_printc_1(apple2_state_t *st, char c, int scroll_p)
{
  st->textlines[st->cursy][st->cursx] |= 0xc0; /* turn off blink */

  if (c == '\n')                      /* ^J == NL */
    {
      if (st->cursy==23)
        {
          if (scroll_p)
            a2_scroll(st);
        }
      else
        {
          st->cursy++;
        }
      st->cursx=0;
    }
  else if (c == 014)                  /* ^L == CLS, Home */
    {
      a2_cls(st);
      a2_goto(st,0,0);
    }
  else if (c == '\t')                 /* ^I == tab */
    {
      a2_goto(st, st->cursy, (st->cursx+8)&~7);
    }
  else if (c == 010)                  /* ^H == backspace */
    {
      st->textlines[st->cursy][st->cursx]=0xe0;
      a2_goto(st, st->cursy, st->cursx-1);
    }
  else if (c == '\r')                 /* ^M == CR */
    {
      st->cursx=0;
    }
  else
    {
      st->textlines[st->cursy][st->cursx]=c ^ 0xc0;
      st->cursx++;
      if (st->cursx==40) {
        if (st->cursy==23) {
          if (scroll_p)
            a2_scroll(st);
        } else {
          st->cursy++;
        }
        st->cursx=0;
      }
    }

  st->textlines[st->cursy][st->cursx] &= 0x7f; /* turn on blink */
}

void
a2_printc(apple2_state_t *st, char c)
{
  a2_printc_1(st, c, 1);
}

void
a2_printc_noscroll(apple2_state_t *st, char c)
{
  a2_printc_1(st, c, 0);
}


void
a2_prints(apple2_state_t *st, char *s)
{
  while (*s) a2_printc(st, *s++);
}

void
a2_goto(apple2_state_t *st, int r, int c)
{
  if (r > 23) r = 23;
  if (c > 39) c = 39;
  st->textlines[st->cursy][st->cursx] |= 0xc0; /* turn off blink */
  st->cursy=r;
  st->cursx=c;
  st->textlines[st->cursy][st->cursx] &= 0x7f; /* turn on blink */
}

void
a2_cls(apple2_state_t *st)
{
  int i;
  for (i=0; i<24; i++) {
    memset(st->textlines[i],0xe0,40);
  }
}

void
a2_clear_gr(apple2_state_t *st)
{
  int i;
  for (i=0; i<24; i++) {
    memset(st->textlines[i],0x00,40);
  }
}

void
a2_clear_hgr(apple2_state_t *st)
{
  int i;
  for (i=0; i<192; i++) {
    memset(st->hireslines[i],0,40);
  }
}

void
a2_invalidate(apple2_state_t *st)
{
}

void
a2_poke(apple2_state_t *st, int addr, int val)
{

  if (addr>=0x400 && addr<0x800) {
    /* text memory */
    int row=((addr&0x380)/0x80) + ((addr&0x7f)/0x28)*8;
    int col=(addr&0x7f)%0x28;
    if (row<24 && col<40) {
      st->textlines[row][col]=val;
      if (!(st->gr_mode&(A2_GR_HIRES)) ||
          (!(st->gr_mode&(A2_GR_FULL)) && row>=20)) {
      }
    }
  }
  else if (addr>=0x2000 && addr<0x4000) {
    int row=(((addr&0x1c00) / 0x400) * 1 +
             ((addr&0x0380) / 0x80) * 8 +
             ((addr&0x0078) / 0x28) * 64);
    int col=((addr&0x07f)%0x28);
    if (row<192 && col<40) {
      st->hireslines[row][col]=val;
      if (st->gr_mode&A2_GR_HIRES) {
      }
    }
  }
}

void
a2_hplot(apple2_state_t *st, int hcolor, int x, int y)
{
  int highbit,run;

  highbit=((hcolor<<5)&0x80) ^ 0x80; /* capture bit 2 into bit 7 */

  if (y<0 || y>=192 || x<0 || x>=280) return;

  for (run=0; run<2 && x<280; run++) {
    unsigned char *vidbyte = &st->hireslines[y][x/7];
    unsigned char whichbit=1<<(x%7);
    int masked_bit;

    *vidbyte = (*vidbyte & 0x7f) | highbit;

    /* use either bit 0 or 1 of hcolor for odd or even pixels */
    masked_bit = (hcolor>>(1-(x&1)))&1;

    /* Set whichbit to 1 or 0 depending on color */
    *vidbyte = (*vidbyte & ~whichbit) | (masked_bit ? whichbit : 0);

    x++;
  }
}

void
a2_hline(apple2_state_t *st, int hcolor, int x1, int y1, int x2, int y2)
{
  int dx,dy,incx,incy,x,y,balance;

  /* Bresenham's line drawing algorithm */

  if (x2>=x1) {
    dx=x2-x1;
    incx=1;
  } else {
    dx=x1-x2;
    incx=-1;
  }
  if (y2>=y1) {
    dy=y2-y1;
    incy=1;
  } else {
    dy=y1-y2;
    incy=-1;
  }

  x=x1; y=y1;

  if (dx>=dy) {
    dy*=2;
    balance=dy-dx;
    dx*=2;
    while (x!=x2) {
      a2_hplot(st, hcolor, x, y);
      if (balance>=0) {
        y+=incy;
        balance-=dx;
      }
      balance+=dy;
      x+=incx;
    }
    a2_hplot(st, hcolor, x, y);
  } else {
    dx*=2;
    balance=dx-dy;
    dy*=2;
    while (y!=y2) {
      a2_hplot(st, hcolor, x, y);
      if (balance>=0) {
        x+=incx;
        balance-=dy;
      }
      balance+=dx;
      y+=incy;
    }
    a2_hplot(st, hcolor, x, y);
  }
}

void
a2_plot(apple2_state_t *st, int color, int x, int y)
{
  int textrow=y/2;
  unsigned char byte;

  if (x<0 || x>=40 || y<0 || y>=48) return;

  byte=st->textlines[textrow][x];
  if (y&1) {
    byte = (byte&0xf0) | (color&0x0f);
  } else {
    byte = (byte&0x0f) | ((color&0x0f)<<4);
  }
  st->textlines[textrow][x]=byte;
}

void
a2_display_image_loading(apple2_state_t *st, unsigned char *image,
                         int lineno)
{
  /*
    When loading images,it would normally just load the big binary
    dump into screen memory while you watched. Because of the way
    screen memory was laid out, it wouldn't load from the top down,
    but in a funny interleaved way. You should call this with lineno
    increasing from 0 thru 191 over a period of a few seconds.
  */

  int row=(((lineno / 24) % 8) * 1 +
           ((lineno / 3 ) % 8) * 8 +
           ((lineno / 1 ) % 3) * 64);

  memcpy (st->hireslines[row], &image[row * 40], 40);
}

/*
  Simulate plausible initial memory contents for running a program.
*/
void
a2_init_memory_active(apple2_sim_t *sim)
{
  int i,j,x,y,c;
  int addr=0;
  apple2_state_t *st=sim->st;

  while (addr<0x4000) {
    int n;

    switch (random()%4) {
    case 0:
    case 1:
      n=random()%500;
      for (i=0; i<n && addr<0x4000; i++) {
        unsigned char rb=((random()%6==0 ? 0 : random()%16) |
                   ((random()%5==0 ? 0 : random()%16)<<4));
        a2_poke(st, addr++, rb);
      }
      break;

    case 2:
      /* Simulate shapes stored in memory. We use the font since we have it.
         Unreadable, since rows of each character are stored in consecutive
         bytes. It was typical to store each of the 7 possible shifts of
         bitmaps, for fastest blitting to the screen. */
      x=random()%(sim->text_im->width);
      for (i=0; i<100; i++) {
        for (y=0; y<8; y++) {
          c=0;
          for (j=0; j<8; j++) {
            c |= XGetPixel(sim->text_im, (x+j)%sim->text_im->width, y)<<j;
          }
          a2_poke(st, addr++, c);
        }
        x=(x+1)%(sim->text_im->width);
      }
      break;

    case 3:
      if (addr>0x2000) {
        n=random()%200;
        for (i=0; i<n && addr<0x4000; i++) {
          a2_poke(st, addr++, 0);
        }
      }
      break;

    }
  }
}


#if 1  /* jwz: since MacOS doesn't have "6x10", I dumped this font to a PNG...
        */

#include "images/gen/apple2font_png.h"

static void
a2_make_font(apple2_sim_t *sim)
{
  int pix_w, pix_h;
  XWindowAttributes xgwa;
  Pixmap m = 0;
  Pixmap p = image_data_to_pixmap (sim->dpy, sim->window,
                                   apple2font_png, sizeof(apple2font_png),
                                   &pix_w, &pix_h, &m);
  XImage *im = XGetImage (sim->dpy, p, 0, 0, pix_w, pix_h, ~0L, ZPixmap);
  XImage *mm = XGetImage (sim->dpy, m, 0, 0, pix_w, pix_h, 1, XYPixmap);
  unsigned long black =
    BlackPixelOfScreen (DefaultScreenOfDisplay (sim->dpy));
  int x, y;

  XFreePixmap (sim->dpy, p);
  XFreePixmap (sim->dpy, m);
  if (pix_w != 64*7) abort();
  if (pix_h != 8) abort();

  XGetWindowAttributes (sim->dpy, sim->window, &xgwa);
  sim->text_im = XCreateImage (sim->dpy, xgwa.visual, 1, XYBitmap, 0, 0,
                               pix_w, pix_h, 8, 0);
  sim->text_im->data = malloc (sim->text_im->bytes_per_line *
                               sim->text_im->height);

  /* Convert deep image to 1 bit */
  for (y = 0; y < pix_h; y++)
    for (x = 0; x < pix_w; x++)
      XPutPixel (sim->text_im, x, y,
                 (XGetPixel (mm, x, y)
                  ? XGetPixel (im, x, y) == black
                  : 0));

  XDestroyImage (im);
  XDestroyImage (mm);
}

#else /* 0 */

/* This table lists fixes for characters that differ from the standard 6x10
   font. Each encodes a pixel, as (charindex*7 + x) + (y<<10) + (value<<15)
   where value is 0 for white and 1 for black. */
static const unsigned short a2_fixfont[] = {
  /* Fix $ */  0x8421, 0x941d,
  /* Fix % */  0x8024, 0x0028, 0x8425, 0x0426, 0x0825, 0x1027, 0x1426, 0x9427,
               0x1824, 0x9828,
  /* Fix * */  0x8049, 0x8449, 0x8849, 0x0c47, 0x0c48, 0x0c4a, 0x0c4b, 0x9049,
               0x9449, 0x9849,
  /* Fix , */  0x9057, 0x1458, 0x9856, 0x1857, 0x1c56,
  /* Fix . */  0x1465, 0x1864, 0x1866, 0x1c65,
  /* Fix / */  0x006e, 0x186a,
  /* Fix 0 */  0x8874, 0x8c73, 0x9072,
  /* Fix 1 */  0x0878, 0x1878, 0x187c,
  /* Fix 5 */  0x8895, 0x0c94, 0x0c95,
  /* Fix 6 */  0x809f, 0x8c9c, 0x109c,
  /* Fix 7 */  0x8ca4, 0x0ca5, 0x90a3, 0x10a4,
  /* Fix 9 */  0x08b3, 0x8cb3, 0x98b0,
  /* Fix : */  0x04b9, 0x08b8, 0x08ba, 0x0cb9, 0x90b9, 0x14b9, 0x18b8, 0x18b9,
               0x18ba, 0x1cb9,
  /* Fix ; */  0x04c0, 0x08bf, 0x08c1, 0x0cc0, 0x90c0, 0x14c1, 0x98bf, 0x18c0,
               0x1cbf,
  /* Fix < */  0x80c8, 0x00c9, 0x84c7, 0x04c8, 0x88c6, 0x08c7, 0x8cc5, 0x0cc6,
               0x90c6, 0x10c7,
               0x94c7, 0x14c8, 0x98c8, 0x18c9,
  /* Fix > */  0x80d3, 0x00d4, 0x84d4, 0x04d5, 0x88d5, 0x08d6, 0x8cd6, 0x0cd7,
               0x90d5, 0x10d6,
               0x94d4, 0x14d5, 0x98d3, 0x18d4,
  /* Fix @ */  0x88e3, 0x08e4, 0x8ce4, 0x98e5,
  /* Fix B */  0x84ef, 0x04f0, 0x88ef, 0x08f0, 0x8cef, 0x90ef, 0x10f0, 0x94ef,
               0x14f0,
  /* Fix D */  0x84fd, 0x04fe, 0x88fd, 0x08fe, 0x8cfd, 0x0cfe, 0x90fd, 0x10fe,
               0x94fd, 0x14fe,
  /* Fix G */  0x8116, 0x0516, 0x9916,
  /* Fix J */  0x0129, 0x012a, 0x052a, 0x852b, 0x092a, 0x892b, 0x0d2a, 0x8d2b,
               0x112a, 0x912b,
               0x152a, 0x952b, 0x992a,
  /* Fix M */  0x853d, 0x853f, 0x093d, 0x893e, 0x093f,
  /* Fix Q */  0x915a, 0x155a, 0x955b, 0x155c, 0x195b, 0x995c, 0x1d5c,
  /* Fix V */  0x8d7b, 0x0d7c, 0x0d7e, 0x8d7f, 0x917b, 0x117c, 0x117e, 0x917f,
  /* Fix [ */  0x819e, 0x81a2, 0x859e, 0x899e, 0x8d9e, 0x919e, 0x959e, 0x999e,
               0x99a2,
  /* Fix \ */  0x01a5, 0x19a9,
  /* Fix ] */  0x81ac, 0x81b0, 0x85b0, 0x89b0, 0x8db0, 0x91b0, 0x95b0, 0x99ac,
               0x99b0,
  /* Fix ^ */  0x01b5, 0x05b4, 0x05b6, 0x09b3, 0x89b5, 0x09b7, 0x8db4, 0x8db6,
               0x91b3, 0x91b7,
  /* Fix _ */  0x9db9, 0x9dbf,
  0,
};

static void
a2_make_font(apple2_sim_t *sim)
{
  /*
    Generate the font. It used a 5x7 font which looks a lot like the standard X
    6x10 font, with a few differences. So we render up all the uppercase
    letters of 6x10, and make a few tweaks (like putting a slash across the
    zero) according to fixfont.
   */

  int i;
  const char *def_font="6x10";
  XFontStruct *font;
  Pixmap text_pm;
  GC gc;
  XGCValues gcv;

  font = load_font_retry (sim->dpy, def_font);
  if (!font) {
    fprintf(stderr, "%s: can't load font %s\n", progname, def_font);
    abort();
  }

  text_pm=XCreatePixmap(sim->dpy, sim->window, 64*7, 8, 1);

  memset(&gcv, 0, sizeof(gcv));
  gcv.foreground=1;
  gcv.background=0;
  gcv.font=font->fid;
  gc=XCreateGC(sim->dpy, text_pm, GCFont|GCBackground|GCForeground, &gcv);

  XSetForeground(sim->dpy, gc, 0);
  XFillRectangle(sim->dpy, text_pm, gc, 0, 0, 64*7, 8);
  XSetForeground(sim->dpy, gc, 1);
  for (i=0; i<64; i++) {
    char c=32+i;
    int x=7*i+1;
    int y=7;
    if (c=='0') {
      c='O';
      XDrawString(sim->dpy, text_pm, gc, x, y, &c, 1);
    } else {
      XDrawString(sim->dpy, text_pm, gc, x, y, &c, 1);
    }
  }

# if 0
  for (i=0; a2_fixfont[i]; i++) {
    XSetForeground (sim->dpy, gc, (a2_fixfont[i]>>15)&1);
    XDrawPoint(sim->dpy, text_pm, gc, a2_fixfont[i]&0x3ff,
              (a2_fixfont[i]>>10)&0xf);
  }
  XWriteBitmapFile(sim->dpy, "/tmp/a2font.xbm", text_pm, 64*7, 8, -1, -1);
# endif

  sim->text_im = XGetImage(sim->dpy, text_pm, 0, 0, 64*7, 8, ~0L, ZPixmap);
  XFreeGC(sim->dpy, gc);
  XFreePixmap(sim->dpy, text_pm);

  for (i=0; a2_fixfont[i]; i++) {
    XPutPixel(sim->text_im, a2_fixfont[i]&0x3ff,
              (a2_fixfont[i]>>10)&0xf,
              (a2_fixfont[i]>>15)&1);
  }
}

#endif /* 0 */

apple2_sim_t *
apple2_start(Display *dpy, Window window, int delay,
             void (*controller)(apple2_sim_t *sim,
                                int *stepno,
                                double *next_actiontime))
{
  apple2_sim_t *sim;

  sim=(apple2_sim_t *)calloc(1,sizeof(apple2_sim_t));
  sim->dpy = dpy;
  sim->window = window;
  sim->delay = delay;
  sim->controller = controller;

  sim->st = (apple2_state_t *)calloc(1,sizeof(apple2_state_t));
  sim->dec = analogtv_allocate(dpy, window);
  sim->inp = analogtv_input_allocate();

  sim->reception.input = sim->inp;
  sim->reception.level = 1.0;

  sim->prompt=']';

  if (random()%4==0 && !sim->dec->use_cmap && sim->dec->use_color && sim->dec->visbits>=8) {
    sim->dec->flutter_tint=1;
  }
  else if (random()%3==0) {
    sim->dec->flutter_horiz_desync=1;
  }
  sim->typing_rate = 1.0;

  analogtv_set_defaults(sim->dec, "");
  sim->dec->squish_control=0.05;
  analogtv_setup_sync(sim->inp, 1, 0);


  a2_make_font(sim);

  sim->stepno=0;
  a2_goto(sim->st,23,0);

  if (random()%2==0) sim->basetime_tv.tv_sec -= 1; /* random blink phase */
  sim->next_actiontime=0.0;

  sim->curtime=0.0;
  sim->next_actiontime=sim->curtime;
  sim->controller (sim, &sim->stepno, &sim->next_actiontime);

# ifdef GETTIMEOFDAY_TWO_ARGS
  gettimeofday(&sim->basetime_tv, NULL);
# else
  gettimeofday(&sim->basetime_tv);
# endif

  return sim;
}

int
apple2_one_frame (apple2_sim_t *sim)
{
    double blinkphase;
    int i;
    int textrow;

    if (sim->stepno==A2CONTROLLER_DONE)
      goto DONE;  /* when caller says we're done, be done, dammit! */

    {
      struct timeval curtime_tv;
# ifdef GETTIMEOFDAY_TWO_ARGS
      struct timezone tzp;
      gettimeofday(&curtime_tv, &tzp);
# else
      gettimeofday(&curtime_tv);
# endif
      sim->curtime=(curtime_tv.tv_sec - sim->basetime_tv.tv_sec) +
        0.000001*(curtime_tv.tv_usec - sim->basetime_tv.tv_usec);
      if (sim->curtime > sim->dec->powerup)
        sim->dec->powerup=sim->curtime;
    }

    blinkphase=sim->curtime/0.8;

    /* The blinking rate was controlled by 555 timer with a resistor/capacitor
       time constant. Because the capacitor was electrolytic, the flash rate
       varied somewhat between machines. I'm guessing 1.6 seconds/cycle was
       reasonable. (I soldered a resistor in mine to make it blink faster.) */
    i=sim->st->blink;
    sim->st->blink=((int)blinkphase)&1;
    if (sim->st->blink!=i && !(sim->st->gr_mode&A2_GR_FULL)) {
      int downcounter=0;
      /* For every row with blinking text, set the changed flag. This basically
         works great except with random screen garbage in text mode, when we
         end up redrawing the whole screen every second */
      int row, col;
      for (row=(sim->st->gr_mode ? 20 : 0); row<24; row++) {
        for (col=0; col<40; col++) {
          int c=sim->st->textlines[row][col];
          if ((c & 0xc0) == 0x40) {
            downcounter=4;
            break;
          }
        }
        if (downcounter>0) {
          downcounter--;
        }
      }
    }

    if (sim->curtime >= sim->delay)
      sim->stepno = A2CONTROLLER_DONE;

    if (sim->printing) {
      int nlcnt=0;
      while (*sim->printing) {
        if (*sim->printing=='\001') { /* pause */
          sim->printing++;
          break;
        }
        else if (*sim->printing=='\n') {
          a2_printc(sim->st,*sim->printing);
          sim->printing++;
          nlcnt++;
          if (nlcnt>=2) break;
        }
        else {
          a2_printc(sim->st,*sim->printing);
          sim->printing++;
        }
      }
      if (!*sim->printing) sim->printing=NULL;
    }
    else if (sim->curtime >= sim->next_actiontime) {
      if (sim->typing) {

        int c;
        /* If we're in the midst of typing a string, emit a character with
           random timing. */
        c =*sim->typing;
        if (c==0) {
          sim->typing=NULL;
        }
        else {
          sim->typing++;
          a2_printc(sim->st, c);
          if (c=='\r' || c=='\n') {
            sim->next_actiontime = sim->curtime;
          }
          else if (c==010) {
            sim->next_actiontime = sim->curtime + 0.1;
          }
          else {
            sim->next_actiontime = (sim->curtime +
                               (((random()%1000)*0.001 + 0.3) *
                                sim->typing_rate));
          }
        }
      } else {
        sim->next_actiontime = sim->curtime;

        sim->controller (sim, &sim->stepno, &sim->next_actiontime);

        if (sim->stepno==A2CONTROLLER_DONE) {

        DONE:
          sim->stepno=A2CONTROLLER_FREE;
          sim->controller (sim, &sim->stepno, &sim->next_actiontime);
          /* if stepno is changed, return 1 */
          if (sim->stepno != A2CONTROLLER_FREE)
            return 1;

          XClearWindow(sim->dpy, sim->window);

          /* free sim */
          /* This is from a2_make_font */
          free(sim->text_im->data);
          sim->text_im->data = 0;
          XDestroyImage(sim->text_im);

          /* And free else */
          analogtv_release(sim->dec);
          free(sim->st);
          free(sim->inp);
          free(sim);

          return 0;
        }

      }
    }


    analogtv_setup_sync(sim->inp, sim->st->gr_mode? 1 : 0, 0);
    analogtv_setup_frame(sim->dec);

    for (textrow=0; textrow<24; textrow++) {
      int row;
      for (row=textrow*8; row<textrow*8+8; row++) {

        /* First we generate the pattern that the video circuitry shifts out
           of memory. It has a 14.something MHz dot clock, equal to 4 times
           the color burst frequency. So each group of 4 bits defines a color.
           Each character position, or byte in hires, defines 14 dots, so odd
           and even bytes have different color spaces. So, pattern[0..600]
           gets the dots for one scan line. */

        signed char *pp=&sim->inp->signal[row+ANALOGTV_TOP+4][ANALOGTV_PIC_START+100];

        if ((sim->st->gr_mode&A2_GR_HIRES) &&
            (row<160 || (sim->st->gr_mode&A2_GR_FULL))) {
          int col;

          /* Emulate the mysterious pink line, due to a bit getting
             stuck in a shift register between the end of the last
             row and the beginning of this one. */
          if ((sim->st->hireslines[row][0] & 0x80) &&
              (sim->st->hireslines[row][39]&0x40)) {
            pp[-1]=ANALOGTV_WHITE_LEVEL;
          }

          for (col=0; col<40; col++) {
            unsigned char b=sim->st->hireslines[row][col];
            int shift=(b&0x80)?0:1;

            /* Each of the low 7 bits in hires mode corresponded to 2 dot
               clocks, shifted by one if the high bit was set. */
            for (i=0; i<7; i++) {
              pp[shift+1] = pp[shift] = (((b>>i)&1)
                                         ?ANALOGTV_WHITE_LEVEL
                                         :ANALOGTV_BLACK_LEVEL);
              pp+=2;
            }
          }
        }
        else if ((sim->st->gr_mode&A2_GR_LORES) &&
                 (row<160 || (sim->st->gr_mode&A2_GR_FULL))) {
          int col;
          for (col=0; col<40; col++) {
            unsigned char nib=((sim->st->textlines[textrow][col] >> (((row/4)&1)*4))
                        & 0xf);
            /* The low or high nybble was shifted out one bit at a time. */
            for (i=0; i<14; i++) {
              *pp = (((nib>>((col*14+i)&3))&1)
                     ?ANALOGTV_WHITE_LEVEL
                     :ANALOGTV_BLACK_LEVEL);
              pp++;
            }
          }
        }
        else {
          int col;
          for (col=0; col<40; col++) {
            int rev;
            int c=sim->st->textlines[textrow][col]&0xff;
            /* hi bits control inverse/blink as follows:
               0x00: inverse
               0x40: blink
               0x80: normal
               0xc0: normal */
            rev=!(c&0x80) && (!(c&0x40) || sim->st->blink);

            for (i=0; i<7; i++) {
              unsigned long pix=XGetPixel(sim->text_im,
                                          ((c&0x3f)^0x20)*7+i,
                                          row%8);
              pp[1] = pp[2] = ((pix^rev)
                               ?ANALOGTV_WHITE_LEVEL
                               :ANALOGTV_BLACK_LEVEL);
              pp+=2;
            }
          }
        }
      }
    }
    analogtv_reception_update(&sim->reception);
    {
      const analogtv_reception *rec = &sim->reception;
      analogtv_draw(sim->dec, 0.02, &rec, 1);
    }
 
    return 1;
}


#if 0
void
a2controller_test(apple2_sim_t *sim, int *stepno, double *next_actiontime)
{
  int row,col;

  switch(*stepno) {
  case 0:
    a2_invalidate(sim->st);
    /*
      For testing color rendering. The spec is:
      red grn blu
      0  black       0   0   0
      1  red       227  30  96
      2  dk blue    96  78 189
      3  purple    255  68 253
      4  dk green    0 163  96
      5  gray      156 156 156
      6  med blue   20 207 253
      7  lt blue   208 195 255
      8  brown      96 114   3
      9  orange    255 106  60
      10 grey      156 156 156
      11 pink      255 160 208
      12 lt green   20 245  60
      13 yellow    208 221 141
      14 aqua      114 255 208
      15 white     255 255 255
    */
    sim->st->gr_mode=A2_GR_LORES;
    for (row=0; row<24; row++) {
      for (col=0; col<40; col++) {
        sim->st->textlines[row][col]=(row&15)*17;
      }
    }
    *next_actiontime+=0.4;
    *stepno=99;
    break;

  case 99:
    if (sim->curtime > 10) *stepno=-1;
    break;
  }
}
#endif
