#include "stdio.h"

#include "mbed.h"
#include "rtos.h"
#include "string"

#include "Terminal6x8.h"
#include "Mono19x27.h"
#include "Mono15x22.h"
#include "Arial12x12.h"

#include "SSD1322.h"
#include "hp34comm.h"


#if (defined STM32F303x8) || (defined STM32L432xx)
// display
#define DSP_MOSI PA_7 //A6
#define DSP_MISO PA_6 //A5 
#define DSP_SCLK PA_5 //A4
#define DSP_CS   PB_0 //D3
#define DSP_RST  PB_5 //D11
#define DSP_DC   PB_4 //D12
// UART for RX (CPU->DP) and TX (DP->CPU) combined
#define HP_RX PA_10 // serial1 RX
// misc
#define DBGPIN PA_12

#elif defined STM32F446xx
// display
#define DSP_MOSI PB_15
#define DSP_MISO PB_14
#define DSP_SCLK PB_13
#define DSP_CS   PB_12
#define DSP_RST  D11
#define DSP_DC   D12
// UART for RX (CPU->DP)
#define HP_RX PA_1 // serial4 RX
// UART for TX (DP->CPU)
#define HP_TX PC_7 // serial6 RX

// misc
#define DBGPIN PC_0

#endif

#ifdef HAVE_PC
Serial pc(USBTX, USBRX);
#endif

SSD1322 *dsp;//(10000000, DSP_MOSI, DSP_MISO, DSP_SCLK, DSP_CS, DSP_RST, DSP_DC, "SSD1322");
HPSerial *hp; //(HP_RX, HP_TX);
volatile uint8_t must_refresh;
Thread tdsp, tloop;
Timeout blinker;
DigitalOut led(LED1);

uint8_t curchar;
uint8_t cmd;
uint8_t nchars;
char buffer[MAX_BUFF+1];

void timeout_h() {
  led = !led;
  blinker.attach(&timeout_h, 0.5f);  
}

typedef struct _DSP
{
  uint8_t cmd;
  uint8_t color;
  uint8_t bgcolor;
  uint8_t x0;
  uint8_t y0;
  uint8_t fmt; // 0x01=>ascii, 0x02=>hex, 0x04=>bits, 0x08=>flags, 0x80=>ignore
  uint8_t maxsize;
  uint8_t width;
  const unsigned char* font;
  char buffer[MAX_BUFF+1];
} DSP;

static DSP table[] =
{
  { 0x00, 0xF,   0x0,  0,  0, 0x01, MAX_BUFF, 245, Mono19x27}, // main display
  { 0x0C, 0xF,   0x0,204, 38, 0x01, 3,        45,  Mono15x22}, // channels display
  //  { 0x01, 0xF,   0x0,  0,  0, 0x80, MAX_BUFF, 0, Terminal6x8},
  //  { 0x02, 0xF,   0x0,  0,  0, 0x80, MAX_BUFF, 0, Terminal6x8},
  { 0x0A, 0xF,   0x0,  0, 57, 0x0C, 4,        0, Terminal6x8}, // flags + bits
  //  { 0xFF, 0xF,   0x0,  0,  0, 0x80, MAX_BUFF, 0, Terminal6x8},
};

// 9x10
const unsigned char icon_alarm[] __attribute__((aligned (2))) =
{
  0x1c, 0x0,
  0x3e, 0x0,
  0x7f, 0x0,
  0x7f, 0x0,
  0x7f, 0x0,
  0x7f, 0x0,
  0x7f, 0x0,
  0x7f, 0x0,
  0xff, 0x80,
  0x10, 0x0
};

const unsigned char icon_curve[] __attribute__((aligned (2))) =
{
  0x80, 0x0,
  0x80, 0x0,
  0x80, 0x80,
  0x81, 0x0,
  0x9e, 0x0,
  0xa0, 0x0,
  0xc0, 0x0,
  0x80, 0x0,
  0x80, 0x0,
  0xff, 0x80
 };

typedef struct _FLAG
{
  uint8_t flag;
  uint16_t zone;
  uint8_t x;
  uint8_t y;
  const char* msg;
  const unsigned char* icon;
} FLAG;

typedef struct _FRAME
{
  uint16_t flag;
  uint8_t x0;
  uint8_t y0;
  uint8_t x1;
  uint8_t y1;
} FRAME;

static const FLAG flags[] =
  {
    // flag, zone, x0, y0, msg, icon
    // right-side icons area
    { 0x00, 0x02, 0, 0,  NULL, icon_alarm}, // F1.1
    { 0x01, 0x02, 0, 11, NULL, icon_curve}, // F1.2
    

    { 0x03, 0x04, 1, 2, "Channel"}, // F1.4

    // col 1
    { 0x34, 0x10, 0,  0,  "MON"},     // F4.5
    { 0x33, 0x10, 0,  8,  "VIEW"},    // F4.4
    { 0x35, 0x10, 0,  16, "SCAN"},   // F4.6

    //col 2
    { 0x32, 0x20, 0, 0,  "*"},      // F4.3
    { 0x36, 0x20, 0, 8,  "CFG"},    // F4.7
    { 0x27, 0x20, 0, 16, "ERR"},  // F3.8

    // col 3
    { 0x41, 0x040, 0, 0, "ADRS"}, // F4.2
    { 0x40, 0x040, 0, 8,  "RMT"},  // F4.1

    // col 4
    { 0x26, 0x080, 0, 0,  "EXT"},  // F3.7
    { 0x25, 0x080, 0, 8,  "ONCE"}, // F3.6
    
    { 0xFF, 0x080, 0, 16,  "MEM"},

    
    // col 5
    { 0x14, 0x100, 0, 0,  "4W"},    // F2.5
    { 0x15, 0x100, 0, 8,  "OC"},    // F2.6
    { 0xFF, 0x100, 0, 16, "LST"},   // F3.8? Nope
    { 0xFF, 0x100, 0, 16, "MIN"},
    { 0xFF, 0x100, 0, 16, "MAX"},
    { 0xFF, 0x100, 0, 16, "AVG"},

    { 0x05, 0x08, 0,   0,  "Alarm"}, // F1.6
    { 0x06, 0x08, 0,  10,  "H"}, // F1.7
    { 0x13, 0x08, 6,  10,  "1"}, // F2.4
    { 0x10, 0x08, 12, 10,  "2"}, // F2.1
    { 0x12, 0x08, 18, 10,  "3"}, // F2.3
    { 0x11, 0x08, 24, 10,  "4"}, // F2.2
    { 0x04, 0x08, 30, 10,  "L"}, // F1.5

  };

static const FRAME zones[] =
  {
    { 0x001, 0,   0, 245, 27}, // main display area
    { 0x002, 246, 0, 255, 27}, // right notif area
    { 0x004, 203, 28, 255, 54}, // channels display area
    { 0x008, 160, 28, 202, 54}, // alarms area
    { 0x010, 0,   28, 32,  54}, // flags col1
    { 0x020, 33,  28, 70,  54}, // flags col2
    { 0x040, 71,  28, 103, 54}, // flags col3
    { 0x080, 104, 28, 128, 54}, // flags col4
    { 0x100, 129, 28, 159, 54}, // flags col5

    { 0x8000, 0, 55, 255, 63}, // flags bits display area
  };

static const FRAME frames[] =
  {
    { 0x02, 203, 35, 255, 54}, // F1.3 - channel frame
    { 0x07, 160, 28, 202, 54}, // F1.8 - alarm frame
  };


#ifdef DEBUG

DigitalOut dbgpin(DBGPIN);
inline void pulse(uint8_t count=1, bool stayup=false)
{
  dbgpin = 0;
  wait_us(2);
  while (count--)
  {
    dbgpin = 1;
    wait_us(2);
    dbgpin = 0;
    wait_us(2);
 }
  if (stayup)
    dbgpin = 1;

}
#else
inline void pulse(uint8_t count=1, bool stayup=false)
{}
#endif



void copy_to_lcd(void);
void test_dsp();

void setup() {
  blinker.attach(&timeout_h, 0.5f);

  // init the LCD
  dsp = new SSD1322(20000000, DSP_MOSI, DSP_MISO, DSP_SCLK, DSP_CS, DSP_RST, DSP_DC, "SSD1322");
  //dsp.set_orientation(3);
#ifdef HAVE_PC
  pc.baud (115200);
#endif
  printf("\n\nSystem Core Clock = %.3f MHZ\r\n",
  	 (float)SystemCoreClock/1000000);
  
  // myLCD.set_font((unsigned char*) Terminal6x8);
  // myLCD.claim(stdout);      // send stdout to the LCD display
  // myLCD.claim(stderr);      // send stderr to the LCD display
  dsp->background(Black);    // set background to black
  dsp->foreground(0xF);
  dsp->cls();
  
  cmd = 0xFF;
  curchar = 0;
  nchars = 0;

  for (uint8_t i=0; i<sizeof(table)/sizeof(table[0]); ++i)
    memset(table[i].buffer, 0, MAX_BUFF+1);
  
  dsp->locate(30, 10);
  dsp->set_font((unsigned char*)Mono19x27);
  dsp->printf("HP34970A");
  dsp->set_font((unsigned char*)Terminal6x8);
  dsp->locate(90, 40);
  dsp->printf("David Douard");
  dsp->copy_to_lcd();
  wait(2);
  dsp->cls();

  hp = new HPSerial(HP_RX);
  //hp = NULL;
}

void copy_to_lcd(void) {
  uint8_t mask=1;

  while(1) {
    pulse(0, true);
    if (must_refresh) {
      must_refresh = 0;
      //Thread::wait(20); // give a bit of time for some more cmds  
      dsp->copy_to_lcd();
    }

    /*
    if (must_refresh & mask) {
      for(uint8_t i=0; i<sizeof(zones)/sizeof(zones[0]); i++)
	if (zones[i].flag == mask) {
	  dsp->copy_to_lcd(zones[i].x0/4, (zones[i].x1+3)/4,
			  zones[i].y0,   zones[i].y1);
	  must_refresh &= ~mask;
	  break;
	}
    }
    mask = mask << 1;
    if (mask == 0) {
      mask = 1;
    }
    */
    pulse(0, false);
    Thread::wait(30);
  }
}

void show(uint8_t cmd, char *txt, uint8_t nchar=0) {
  uint8_t i, len;
  uint16_t bgcolor, fgcolor;
  char *oldv;

  pulse(1, true);

  len = MAX_BUFF;

  for (i=0; i<sizeof(table)/sizeof(table[0]); ++i) {
    if (table[i].cmd == cmd) {
      bgcolor = table[i].bgcolor;
      fgcolor = table[i].color;
      dsp->background(bgcolor);
      dsp->foreground(fgcolor);
      dsp->set_font((unsigned char*) table[i].font);
      oldv = table[i].buffer;

      dsp->locate(table[i].x0, table[i].y0);

      if (table[i].fmt & 0x01 ) {
	if (strncmp(oldv, txt, table[i].maxsize) != 0) {
	  // ascii
	  if (table[i].width > 0)
	    dsp->fillrect(table[i].x0, table[i].y0, table[i].x0 + table[i].width, table[i].y0 + table[i].font[2], bgcolor);
	  for (uint8_t k=0; ;k++) {
	    if (txt[k] == 0x00)
	      {
		dsp->printf(txt);
		break;
	      }
	    if (txt[k] == 0x09) { // \t is a special char for 'unselected' display value
	      txt[k] = 0x00;
	      dsp->printf(txt);
	      
	      if (fgcolor == table[i].color)
		fgcolor /= 2;
	      else
		fgcolor = table[i].color;
	      dsp->foreground(fgcolor);
	      txt = &(txt[k+1]);
	      k = 0;
	    }
	  }
	  if (cmd == 0x00) // main area
	    must_refresh |= 0x01;
	  if (cmd == 0x0C) // channels area
	    must_refresh |= 0x04;
	}
      }
      /*
      if (table[i].fmt & 0x02 ) {
      // hex
	for (uint8_t j=0;; j++) {
	  if (txt[j] == 0x00)
	    break;
	  dsp->printf("%02X ", txt[j]);
	}
	for (uint8_t j=3*strlen(txt); j<table[i].maxsize; j++)
	  dsp->printf(" ");
      }
      */
      if (table[i].fmt & 0x04 ) {
      // binary
	dsp->foreground(fgcolor);
	dsp->printf(" [");
	for (uint8_t j=0; j<max(nchar, table[i].maxsize) ; j++) {
	  if (j>0) {
	    dsp->foreground(fgcolor);
	    dsp->printf(" | ");
	  }
	  for (uint8_t k=0; k<8; k++) {
	    if (txt[j] & (1 << (7-k)))
	      dsp->foreground(fgcolor);
	    else
	      dsp->foreground(bgcolor);
	    dsp->printf("%d", (8-k));
	  }
	}
	dsp->foreground(fgcolor);
	dsp->printf("]");
	must_refresh |= 0x8000;
      }

      if (table[i].fmt & 0x08 ) {
      // flags
	for (uint8_t j=0; j<max(nchar, table[i].maxsize) ; j++) {
	  for (uint8_t k=0; k<8; k++) {
	    if (1) {//(txt[j] & (1 << k) ) != (oldv[j] & (1 << k))) {

	      if (txt[j] & (1 << k))
		dsp->foreground(fgcolor);
	      else
		dsp->foreground(bgcolor);

	      /*
	      for (uint8_t l=0;
		   l<(sizeof(frames)/sizeof(frames[0])); ++l) {
		if (frames[l].flag & ((j<<4) + k))
		  dsp->fillrect(frames[l].x0, frames[l].y0,
			       frames[l].x1, frames[l].y1,
			       fgcolor/8);
		else
		  dsp->fillrect(frames[l].x0, frames[l].y0,
			       frames[l].x1, frames[l].y1,
			       bgcolor);

	      }
	      */

	      for (uint8_t l=0;
		   l<(sizeof(flags)/sizeof(flags[0])); ++l) {
		if (flags[l].flag == ((j<<4) + k)) {
		  for (uint8_t m=0; m<(sizeof(zones)/sizeof(zones[0])); m++) {
		    if (flags[l].zone == zones[m].flag) {
		      if (flags[l].msg != NULL) { // a string
			dsp->locate(flags[l].x + zones[m].x0,
				   flags[l].y + zones[m].y0);
			dsp->printf(flags[l].msg);}
		      else { // an icon
			Bitmap_s pic = {9, 10, 2, (char*) flags[l].icon};
			dsp->Bitmap_BW(pic,
				      flags[l].x + zones[m].x0,
				      flags[l].y + zones[m].y0);
		      }
		      must_refresh |= zones[m].flag;
		      break;
		    }
		  }
		  break;
		}
	      }
	    }
	  }
	}
      }

      for(uint8_t j=0; j<table[i].maxsize; j++)
	oldv[j] = txt[j];
      break;
    }
  }

  //dsp->copy_to_lcd();
  pulse(1, false);
}

void test_dsp()
{
  const FRAME *z;
  show(0x00, "8g8g8g8g8g8g8", 13); // main dsp
  show(0x0C, "888", 3); // channel dsp
  show(0x0A, "\xFF\xFF\xFF\xFF", 4); // all flags
  dsp->copy_to_lcd();
  wait(3);
  dsp->cls();


  for (uint8_t i=0; i<(sizeof(zones)/sizeof(zones[0])); i++)
    {
      z = &zones[i];
      dsp->fillrect(z->x0, z->y0, z->x1, z->y1, 4+i);
      dsp->locate(z->x0+1, z->y0+1);
      dsp->printf("%d", i);
    }

  /*
  for (uint8_t i=0; i<(sizeof(zones)/sizeof(zones[0])); i++)
    {
      z = &zones[i];
      printf("Zone %d [%x]: %d, %d, %d, %d\n", i, z->flag,
		z->x0, z->y0, z->x1, z->y1);
      must_refresh = z->flag;
      wait(1);
    }
  printf("Done\n");
  wait(2);
  printf("Copy ALL\n");
  dsp->copy_to_lcd();
  */
  wait(2);
  dsp->cls();
}


void loop() { // run over and over
  unsigned int err[8];
  for (uint8_t i=0; i<8; i++)
    err[i] = 0;
  while(1) {
    if (hp != NULL) {
      if (hp->cmd_available())
	{
	  led = 1;
	  HPSerial::CMD cmd;
	  if (hp->pop(cmd))
	    {
	      for (uint8_t i=0; i<7; i++)
		if (hp->nerrors(i) > err[i]) {
		  printf("ERR: %d/%d/%d/%d/%d/%d/%d\n",
			 hp->nerrors(0),
			 hp->nerrors(1),
			 hp->nerrors(2),
			 hp->nerrors(3),
			 hp->nerrors(4),
			 hp->nerrors(5),
			 hp->nerrors(6)
			 );
		  break;
		}
	      for (uint8_t i=0; i<7; i++)
		err[i] = hp->nerrors(i);
	      printf("CMD[%s:%d] %02X", (cmd.direction==HPSerial::Rx)?"Rx":"Tx", cmd.id,
		     cmd.cmd);
	      
	      if (cmd.direction == HPSerial::Rx) {
		if ((cmd.cmd == 0x00) || (cmd.cmd == 0x0C))
		  printf(": '%s'\n", cmd.value);
		else {
		  printf(":");
		  for (uint8_t i=0; i<cmd.size; i++)
		    printf("%02x ", cmd.value[i]);
		  printf("\n");	      
		}
		show(cmd.cmd, cmd.value, cmd.size);
	      }
	      else
		printf("\n");
	  
	      if (cmd.direction == HPSerial::Rx)
		show(cmd.cmd, cmd.value, cmd.size);
	    }
	  led = 0;
	}
    }
    else {
      show(0x00, "hello", 6);
    }
      
  Thread::wait(10);
  }
}

int main()
{
  setup();

  tdsp.start(copy_to_lcd);

  test_dsp();

  tloop.start(loop);
  while(1);
  //loop();
}
