extern "C" {
#include "shared.h"
#include "pilotcpu.h"
}

#include "pilotshell.h"
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <time.h>
#include <qpixmap.h>
#include <qtimer.h>
#include <qpainter.h>

#define UPDATEPERIOD 1200

PilotShell::PilotShell(shared_img *s, Pilot *p,
	QWidget *parent, const char *name) :
    QLabel(parent, name)
{
    int i;
    char *casename = "~/.qcopilot/case.png";

    shptr = s;
    pilot = p;
    setFocusPolicy(StrongFocus);
    curkeydown = -1;
    lcdcleared = 0;

    /* Load the background */
    expand_tilde(&casename);
    setPixmap(QPixmap( casename ));

    /* The LCD area */
    lcd = QRect(31, 10, 160, 160);

    /* The digitizer area */
    digitizer = QRect(31, 10, 160, 222);

    /* Power / Backlight */
    key[0] = QRegion(0, 244, 15, 22, QRegion::Rectangle);

    /* Up */
    key[1] = QRegion(96, 247, 30, 13, QRegion::Rectangle);

    /* Down */
    key[2] = QRegion(96, 268, 30, 13, QRegion::Rectangle);

    /* DateBook */
    key[3] = QRegion(24, 244, 26, 34, QRegion::Ellipse);

    /* Phone */
    key[4] = QRegion(60, 244, 30, 34, QRegion::Ellipse);

    /* ToDo */
    key[5] = QRegion(130, 244, 30, 34, QRegion::Ellipse);

    /* Memo */
    key[6] = QRegion(170, 244, 26, 34, QRegion::Ellipse);
    //
    // The 7 grey levels the display can support
    static const unsigned long greylevel[7] =
	{ 65535, 49152, 45056, 32768, 20480, 16384, 0};

    // Make the "normal" background
    unsigned long norm_r = 0x90;
    unsigned long norm_g = 0xb3;
    unsigned long norm_b = 0x98;

    for(i=0;i<7;++i) {
      greys[i] = QColor(
	  (norm_r * greylevel[i]) / greylevel[0],
	  (norm_g * greylevel[i]) / greylevel[0],
	  (norm_b * greylevel[i]) / greylevel[0]
	);
    }

    // Make the "backlit" background
    unsigned long hi_r = 0x80;
    unsigned long hi_g = 0xff;
    unsigned long hi_b = 0x80;

    for(i=0;i<7;++i) {
      greys[i+7] = QColor(
	  (hi_r * greylevel[i]) / greylevel[0],
	  (hi_g * greylevel[i]) / greylevel[0],
	  (hi_b * greylevel[i]) / greylevel[0]
	);
    }

    // Set up the LCD update timer
    lcdtimer = new QTimer(this);
    connect(lcdtimer, SIGNAL(timeout()), this, SLOT(tickLCD()) );
    lcdtimer->start( UPDATEPERIOD );
    running = 1;
}

void PilotShell::focusInEvent(QFocusEvent *e)
{
    if (!running) {
	lcdtimer->start( UPDATEPERIOD );
	if (shptr) {
	    shptr->CpuReq = cpuStart;
	}
	running = 1;
    }
}

void PilotShell::focusOutEvent(QFocusEvent *e)
{
    if (running) {
	lcdtimer->stop();
	if (shptr) {
	    shptr->CpuReq = cpuStop;
	}
	running = 0;
    }
}

void PilotShell::mousePressEvent(QMouseEvent *e)
{
    penEvent(1, e->pos());
}

void PilotShell::mouseMoveEvent(QMouseEvent *e)
{
    penEvent(2, e->pos());
}

void PilotShell::mouseReleaseEvent(QMouseEvent *e)
{
    penEvent(0, e->pos());
}

void PilotShell::keyPressEvent(QKeyEvent *e)
{
    int asc = e->ascii();
    if (asc == '\r') {
	asc = '\n';
    }
    int in = shptr->kbin;
    shptr->kb[in] = asc;
    shptr->kbin = (in+1)&15;
}

static void waitforzero(volatile int *p, int press)
{
    if (!press) usleep(100000);
    while (*p) usleep(1000);
}

void PilotShell::penEvent(int down, const QPoint& p)
{
    if (!shptr) return;

    /* Did we just release a key? */
    if (down == 0 && curkeydown >= 0) {
	waitforzero(&(shptr->run_updateisr), 0);
	shptr->keydown = down;
	shptr->key = curkeydown;
	shptr->run_updateisr = 1;
	curkeydown = -1;
	return;
    }

    /* If we're still holding down a key, ignore everything until
     * we release it. */
    if (curkeydown >= 0) {
	return;
    }

    if (digitizer.contains(p)) {
	/* We're on the digitizer */
	waitforzero(&(shptr->run_updateisr), (down!=0));
	if (!shptr->pendown && down) {  /* pen going down */
	    shptr->pen = 1;
	} else if (shptr->pendown && !down) { /* pen coming up */
	    shptr->pen = 0;
	}
	shptr->pendown = (down > 0);
	shptr->penx = p.x() - digitizer.x();
	shptr->peny = p.y() - digitizer.y();
	shptr->run_updateisr = 1;
    } else if (down == 1) {
	/* There was a pen down somewhere off the digitizer.  Find it. */
	for (int i=0; i<7; ++i) {
	    if (key[i].contains(p)) {
		waitforzero(&(shptr->run_updateisr), 1);
		shptr->keydown = 1;
		shptr->key = i;
		shptr->run_updateisr = 1;
		curkeydown = i;
	    }
	}
    }
}

/*
 *  Update the LCD image.
 */
void PilotShell::tickLCD()
{
  if (shptr->LcdPower == lcdOff) {
    /* When screen is turned off, make everything "black".  */
    if (lcdcleared == 0) {
      clearLCD();
      lcdcleared = 1;
    }
  } else {
    unsigned char *scrnmap;
    /* Get current location of screen ram from dragonball register
     * LSSA, then update LCD */
    scrnmap = (unsigned char *)CPU_getmemptr(shptr->lssa & ~1);
    updateLCD(scrnmap);
    lcdcleared = 0;
  }
  
  /* maybe ring the bell */
  if (shptr->LcdReq == lcdBell) {
    ringBell(shptr->BellAmp, shptr->BellFreq, shptr->BellDur);
    shptr->LcdReq = lcdNone;
  }
}

void PilotShell::clearLCD()
{
    QPainter p(this);
    p.fillRect(lcd, black);
}

void PilotShell::updateLCD(unsigned char *screenmap)
{
    /*
    struct timeval ts, te;
    FILE *lf = fopen("log", "a");
    gettimeofday(&ts, NULL);
    fprintf(lf, "Start: %ld.%06ld\n", ts.tv_sec, ts.tv_usec);
    */
  const int lcdwidth = lcd.width();
  const int lcdheight = lcd.height();
  QPixmap buffer(lcdwidth, lcdheight);
  QPainter p;
  p.begin(&buffer);
  const unsigned char VPW = shptr->VPW;
  const unsigned char POSR = shptr->POSR;
  const unsigned char PICF = shptr->PICF;

  int backlight = shptr->Backlight ? 7 : 0;
  int twobit = PICF & 0x01;
  const unsigned char *srcstart = screenmap +
                                  ((POSR & (0x04 | !twobit << 3)) ? 1 : 0);
  int shiftstart = (6 | !twobit) -
                   (twobit + 1) * (POSR & (0x03 | !twobit << 2));
  int maskstart = (1 | twobit << 1) << shiftstart;
  int row;

#ifdef WORDS_BIGENDIAN
#define GOOD_ENDIAN(x) (x)
#else
  /* Wonk: sizeof(unsigned char *) must be == sizeof(unsigned int)  - Ian */
#define GOOD_ENDIAN(x) ((unsigned char *)(((unsigned int)(x))^1UL))
#endif

  for (row = 0; row < lcdheight; row += 1) {
    const unsigned char *src = srcstart;
    int shift = shiftstart;
    int mask = maskstart;
    int bit;
    for (bit = 0; bit < lcdwidth; bit += 1) {
      unsigned int grey;
      grey = twobit ? shptr->grpalette[(*(GOOD_ENDIAN(src)) & mask) >> shift] :
                      ((*(GOOD_ENDIAN(src)) & mask) ? 6 : 0);
      p.setPen(greys[grey + backlight]);
      p.drawPoint(bit, row);

      if (shift) {
        shift -= 1 + twobit;
        mask >>= 1 + twobit;
      } else {
        shift = 6 | !twobit;
        mask = (1 | twobit << 1) << (6 | !twobit);
        ++src;
      }
    }
#if 0
    if ((min_x <= max_x) &&
        (row == lcdheight - MagFactor || !(row + 1 % 10))) {
      xcpPutImage(min_x, min_y, max_x - min_x + MagFactor,
                  max_y - min_y + MagFactor);
      min_x = lcdwidth;
      min_y = lcdheight;
      max_x = 0;
      max_y = 0;
    }
#endif
    srcstart += 2 * VPW;
  }
  p.end();
  QPainter pb(this);
  pb.drawPixmap(lcd.topLeft(), buffer);
    /*
    gettimeofday(&te, NULL);
    fprintf(lf, "End  : %ld.%06ld\n", te.tv_sec, te.tv_usec);
    unsigned long usd = (te.tv_sec - ts.tv_sec)*1000000 + (te.tv_usec - ts.tv_usec);
    fprintf(lf, "Diff : %ld.%06ld\n", usd/1000000, usd%1000000);
    fclose(lf);
    */

}

void PilotShell::ringBell(int /* amp */, int /* freq */, int /* dur */)
{
    // I don't think QT/Embedded can do such a thing.  :-(
}

