#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <serial.h>
#include <uucode.h>
#include <lpc.h>

const struct lpc_part lpc_parts[] = {
  {"LPC1111/101", 0x041e502b, 2, 8},
  {"LPC1111/101 or 102", 0x2516d02b, 2, 8},
  {"LPC1111/201", 0x0416502b, 4, 8},
  {"LPC1111/201 or 202", 0x2516902b, 4, 8},
  
  {"LPC1112/101", 0x042d502b, 2, 16},
  {"LPC1112/101 or 102", 0x2524d02b, 2, 16},
  {"LPC1112/201", 0x0425502b, 4, 16},
  {"LPC1112/201 or 202", 0x2524902b, 4, 16},
  
  {"LPC1113/201", 0x0434502b, 4, 24},
  {"LPC1113/201 or 202", 0x2532902b, 4, 24},
  {"LPC1113/301", 0x0434102b, 8, 24},
  {"LPC1113/301 or 302", 0x2532102b, 8, 24},
  
  {"LPC1114/201", 0x0444502b, 4, 32},
  {"LPC1114/201 or 202", 0x2540902b, 4, 32},
  {"LPC1114/301", 0x0444102b, 8, 32},
  {"LPC1114/301 or 302", 0x2540102b, 8, 32},
  
  {"LPC11C12/301", 0x1421102b, 8, 16},
  {"LPC11C14/301", 0x1440102b, 8, 32},
  {"LPC11C22/301", 0x1431102b, 8, 16},
  {"LPC11C24/301", 0x1430102b, 8, 32},
  {NULL, 0, 0, 0}
};

const struct sprog_family lpc_family = {
  .setup = (void*(*)(struct serial_device*)) lpc_setup,
  .init = (void(*)(void*)) lpc_init,
  .exec = (void(*)(void*, const struct sprog_data*)) lpc_exec,
  .write = (void(*)(void*, const struct sprog_data*)) lpc_write,
  .close = (void(*)(void*)) lpc_close
};
  
  

void lpc_ispmode(struct lpc_device *dev, int state);
void lpc_reset(struct lpc_device *dev, int isp);
int lpc_scanf(struct lpc_device *dev, const char *text, ...);
void lpc_vprintf(struct lpc_device *dev, const char *text, va_list l);
void lpc_printf(struct lpc_device *dev, const char *text, ...);
int lpc_getline(struct lpc_device *dev, char *buf);
int lpc_command(struct lpc_device *dev, const char *text, ...);
int lpc_await_reply(struct lpc_device *dev, ...);
int lpc_read_partid(struct lpc_device *dev);
void lpc_write_ram(struct lpc_device *dev, const struct sprog_data *d, unsigned int addr);


struct lpc_device *lpc_setup(struct serial_device *port) {
  struct lpc_device *dev;
  dev = malloc(sizeof(struct lpc_device));
  dev->port = port;
  dev->part = NULL;
  lpc_reset(dev, 1);
  return dev;
}

void lpc_init(struct lpc_device *dev) {
  char buf[4096];
  
  serial_write(dev->port, "?");
  
  if(lpc_await_reply(dev, "Synchronized", NULL)==1) {
      sprog_info("Synchronization successful\n");
    lpc_printf(dev, "Synchronized\r\n");
    if(lpc_await_reply(dev, "OK", NULL)!=1)
      sprog_error("Expected OK, received '%s'\n", buf);
    lpc_printf(dev, "12000\r\n");
    if(lpc_await_reply(dev, "OK", NULL)!=1)
      sprog_error("Expected OK, received '%s'\n", buf);
  } else {
    serial_write(dev->port, "\r\n");
    if(lpc_await_reply(dev, "?", NULL)==1)
      sprog_info("The device appears to be already synchronized\n");
    else
      sprog_error("Invalid device response\n");
    lpc_getline(dev, buf); /* receive invalid command reply */
  }
  if(lpc_read_partid(dev)==0)
    sprog_info("Found device: %s, %dkB Flash, %dkB RAM\n", dev->part->name, dev->part->flash, dev->part->ram);
}

void lpc_write(struct lpc_device *dev, const struct sprog_data *d) {
  static const int chunk_sizes[] = {256, 512, 1024, 4096, 0};
  struct sprog_data chunk;
  int i;
  int offset;
  int chunk_size;
  int data_size;
  offset = 0;
  
  chunk.data = NULL;
  
  i = dev->part->flash/4; /* number of sectors = Flash size / 4kB */
  lpc_command(dev, "P 0 %d\r\n", i);
  
  sprog_info("Erasing Flash memory... ");
  if(lpc_command(dev, "E 0 %d\r\n", i))
    sprog_info("Error\n");
  else
    sprog_info("OK\n");
  
  i = d->size/4096;
  lpc_command(dev, "P 0 %d\r\n", i); /* prepare sectors for write */
  
  while(d->size-offset>0) {
    chunk_size = d->size - offset;
    for(i=0; chunk_sizes[i]; i++) {
      if(chunk_sizes[i]>=chunk_size)
	break;
      if(chunk_sizes[i]+1024 > dev->part->ram) {
	i--;
	break;
      }
    }
    
    chunk_size = chunk_sizes[i];
    if(chunk_size==0) chunk_size = chunk_sizes[i-1];
    sprog_alloc_data(&chunk, chunk_size);
    
    data_size = chunk_size;
    if(data_size > d->size-offset)
      data_size = d->size - offset;
    
    for(i=0; i<data_size; i++)
      chunk.data[i] = d->data[offset+i];
    
    for(i=data_size; i<chunk_size; i++)
      chunk.data[i] = 0;
    
    chunk.size = chunk_size;
    
    lpc_write_ram(dev, &chunk, 0x10000400);
    
    sprog_info("Copying to Flash...\n");
    if(lpc_command(dev, "C %d %u %d\r\n", offset, 0x10000400, chunk_size))
      sprog_error("Error while copying to flash\n");
    else
      sprog_info("Done!\n");
    
    offset += data_size;
  }
}

void lpc_exec(struct lpc_device *dev, const struct sprog_data *d) {
  lpc_write_ram(dev, d, 0x10000400);
  lpc_command(dev, "U 23130\r\n");
  sprog_info("Executing code... ");
  if(lpc_command(dev, "G %u T\r\n", 0x10000400))
    sprog_info("Error\n");
  else
    sprog_info("OK\n");
}

void lpc_write_ram(struct lpc_device *dev, const struct sprog_data *d, unsigned int addr) {
  int last_offset;
  int last_i;
  int offset;
  int checksum;
  char buf[64];
  int i;
  int j;
  
  if(d->size & 3)
    sprog_error("Invalid data size - should be aligned to 4\n");
  
  lpc_command(dev, "W %u %u\r\n", addr, d->size);
  sprog_info("Writing %d bytes\n", d->size);
  
  last_i = 0;
  last_offset = 0;
  offset = 0;
  sprog_progress(offset, d->size);
  checksum = 0;
  
  for(i=0; (j = uuencode_line(d, buf, &offset, &checksum)); i++) {
    lpc_printf(dev, "%s\r\n", buf);
    if(j==2 || (i % 20)==19) {
      lpc_printf(dev, "%u\r\n", checksum);
      checksum = 0;
      
      switch(lpc_await_reply(dev, "OK", "RESEND", NULL)) {
	case 1:
	  last_offset = offset;
	  last_i = i;
	  break;
	case 2:
	default:
	  offset = last_offset;
	  i = last_i;
      }
    }
    sprog_progress(offset, d->size);
  }
  sprog_info("\n");
}

int lpc_await_reply(struct lpc_device *dev, ...) {
  char buf[4096];
  int i;
  const char *t;
  va_list l;
  va_start(l, dev);
  if(!lpc_getline(dev, buf))
    return 0;
  for(i=0; buf[i]; i++) {
    if(buf[i]=='\n') {
      buf[i] = 0;
      if(i>0) {
	if(buf[i-1]=='\r')
	  buf[i-1] = 0;
      }
      break;
    }
  }
  
  for(i=0; (t = va_arg(l, const char*)); i++) {
    if(strcmp(buf, t)==0)
      return i+1;
  }
  return -1;
}
  

int lpc_getline(struct lpc_device *dev, char *buf) {
  if(sprog_waitdata(dev->port, 5000)==0)
    return 0;
  fgets(buf, 4096, dev->port->f);
  return 1;
}

int lpc_scanf(struct lpc_device *dev, const char *text, ...) {
  int r;
  char buf[4096];
  va_list l;
  va_start(l, text);
  if(!lpc_getline(dev, buf))
    return 0;
  r = vsscanf(buf, text, l);
  va_end(l);
  return r;
}

void lpc_vprintf(struct lpc_device *dev, const char *text, va_list l) {
  char buf[4096];
  vsprintf(buf, text, l);
  serial_write(dev->port, buf);
  lpc_getline(dev, buf);
}

void lpc_printf(struct lpc_device *dev, const char *text, ...) {
  va_list l;
  va_start(l, text);
  lpc_vprintf(dev, text, l);
  va_end(l);
}

int lpc_command(struct lpc_device *dev, const char *text, ...) {
  int res;
  va_list l;
  va_start(l, text);
  lpc_vprintf(dev, text, l);
  va_end(l);
  if(lpc_scanf(dev, "%d", &res)<1)
    return -1;
  return res;
}

int lpc_read_partid(struct lpc_device *dev) {
  int i;
  int res;
  unsigned int partid;
  res = lpc_command(dev, "J\r\n");
  if(res!=0)
    return res;
  lpc_scanf(dev, "%u", &partid);
  for(i=0; lpc_parts[i].name; i++)
    if(lpc_parts[i].part_id==partid)
      break;
  if(lpc_parts[i].name)
    dev->part = &lpc_parts[i];
  else
    return -1;
  return 0;
}

void lpc_ispmode(struct lpc_device *dev, int state) {
  serial_setline(dev->port, SERIAL_DTR, state);
}

void lpc_reset(struct lpc_device *dev, int isp) {
  /* TODO: configure the control lines */
  serial_setline(dev->port, SERIAL_DTR, 1);
  if(isp)
    lpc_ispmode(dev, 1);
  sprog_sleep(50);
  serial_setline(dev->port, SERIAL_DTR, 0);
  if(isp) {
    sprog_sleep(50);
    lpc_ispmode(dev, 0);
  }
}

void lpc_close(struct lpc_device *dev) {
  serial_close(dev->port);
  free(dev);
}