#include <string.h>
#include <stdint.h>
#include <chopstx.h>
#include "tty.h"
#include "config.h"
#ifdef TEMPERATURE_SUPPORT
#include "adc.h"
static int adc_initialized = 0;
#endif

struct command_table
{
  const char *name;
  void (*handler) (struct tty *tty, const char *line);
};

/*
 * Put a line (or lines) to TTY.
 * LINE should be terminated with newline.
 */
static void
put_line (struct tty *tty, const char *line)
{
  tty_send (tty, (uint8_t *)line, strlen (line));
}

static const char *help_string = 
  "mdw ADDR [COUNT]\r\n"
  "mww ADDR VALUE [COUNT]\r\n"
#ifdef CRC32_SUPPORT
  "crc32 string\r\n"
#endif
#ifdef TEMPERATURE_SUPPORT
  "temp\r\n"
#endif
  "help\r\n";

static char hexchar (uint8_t x)
{
  x &= 0x0f;
  if (x <= 0x09)
    return '0' + x;
  else if (x <= 0x0f)
    return 'a' + x - 10;
  else
    return '?';
}

static char *
compose_decimal (char *s, int value)
{
  uint32_t v;
  int col = 1000000000;
  int d;
  int digit_output = 0;

  if (value < 0)
    {
      *s++ = '-';
      v = 1 + ~((uint32_t)value);
    }
  else
    v = (uint32_t)value;

  while (col >= 10)
    {
      if (v >= (uint32_t)col)
	{
	  d = v / col;
	  v = v - d * col;
	  *s++ = d + '0';
	  digit_output = 1;
	}
      else if (digit_output)
	*s++ = '0';

      col = col / 10;
    }

  *s++ = v + '0';

  return s;
}

static char *
compose_hex (char *s, uint32_t v)
{
  s[0] = hexchar (v >> 28);
  s[1] = hexchar (v >> 24);
  s[2] = hexchar (v >> 20);
  s[3] = hexchar (v >> 16);
  s[4] = hexchar (v >> 12);
  s[5] = hexchar (v >> 8);
  s[6] = hexchar (v >> 4);
  s[7] = hexchar (v);
  return s+8;
}

static void
cmd_mdw (struct tty *tty, const char *line)
{
  int i;
  uint32_t addr = 0;
  int count = 0;
  char c;

  if (line[0] == '0' && line[1] == 'x')
    line = line + 2;
  while (1)
    {
      c = *line++;

      if (c == 0)
	{
	  count = 1;
	  break;
	}

      if (c == ' ')
	break;

      addr = (addr << 4);
      if (c >= '0' && c <= '9')
	addr += (c - '0');
      else if (c >= 'a' && c <= 'f')
	addr += (c - 'a') + 10;
      else if (c >= 'A' && c <= 'F')
	addr += (c - 'A') + 10;
      else
	{
	  put_line (tty, "mdw error\r\n");
	  return;
	}
    }

  addr &= ~3;

  if (count == 0)
    {
      while (1)
	{
	  c = *line++;

	  if (c == 0 || c == ' ')
	    break;

	  count = count * 10;
	  if (c >= '0' && c <= '9')
	    count += c - '0';
	  else
	    {
	      put_line (tty, "mdw error\r\n");
	      return;
	    }
	}
    }

  i = 0;
  while (i < count)
    {
      uint32_t v;
      char output[48];
      char *s;

      s = compose_hex (output, addr);
      *s++ = ':';
      *s++ = ' ';

      while (1)
	{
	  v = *(uint32_t *)addr;
	  s = compose_hex (s, v);
	  i++;
	  addr += 4;
	  if (i >= count || (i % 4) == 0)
	    break;
	  *s++ = ' ';
	}

      *s++ = '\r';
      *s++ = '\n';
      tty_send (tty, (uint8_t *)output, s - output);
    }
}

static void
cmd_mww (struct tty *tty, const char *line)
{
  (void)tty;
  (void)line;
  put_line (tty, "mww not yet supported\r\n");
}


#ifdef CRC32_SUPPORT
#include "crc32.h"

static void
cmd_crc32  (struct tty *tty, const char *line)
{
  uint32_t v;
  char string[10];
  char *s;

  crc32_init ();
  while (*line)
    crc32_u8 (*line++);
  v = crc32_value () ^ 0xffffffff;

  s = compose_hex (string, v);
  *s++ = '\r';
  *s++ = '\n';
  tty_send (tty, (uint8_t *)string, sizeof (string));
}
#endif

#ifdef TEMPERATURE_SUPPORT
#define ADCV_TEMP25  14800 /* TYP: 716mV=14219 */
#define ADCV_100M     3217

/* m = 1.62 mV/C_degree */

static void
cmd_temperature  (struct tty *tty, const char *line)
{
  uint32_t v;
  int t;
  char string[13];
  char *s;

  (void)line;

  if (!adc_initialized)
    {
      if (adc_init ())
	{
	  put_line (tty, "adc_init error\r\n");
	  return;
	}
      else
	{
	  adc_start ();
	  adc_initialized = 1;
	}
    }

  adc_start_conversion (0, 1);
  adc_wait_completion (NULL);
  v = adc_buf[0];

  t = 25 - (((int)v - ADCV_TEMP25) * 100 / ADCV_100M);

  s = compose_hex (string, v);
  *s++ = '\r';
  *s++ = '\n';
  tty_send (tty, (uint8_t *)string, s - string);

  s = compose_decimal (string, t);
  *s++ = '\r';
  *s++ = '\n';
  tty_send (tty, (uint8_t *)string, s - string);
}
#endif

static void
cmd_help (struct tty *tty, const char *line)
{
  (void)line;
  put_line (tty, help_string);
}


struct command_table command_table[] = {
  { "mdw", cmd_mdw },
  { "mww", cmd_mww },
#ifdef CRC32_SUPPORT
  { "crc32", cmd_crc32 },
#endif
#ifdef TEMPERATURE_SUPPORT
  { "temp", cmd_temperature },
#endif
  { "help", cmd_help },
};

#define N_CMDS (int)(sizeof (command_table) / sizeof (struct command_table))


/*
 * Dispatch a command parsing LINE.
 * Line is NULL terminated with no newline.
 */
void
cmd_dispatch (struct tty *tty, const char *line)
{
  int i;
  const char *p;
  unsigned int n = 0;

  p = line;
  while (*p)
    {
      if (*p++ == ' ')
	break;
      n++;
    }

  for (i = 0; i < N_CMDS; i++)
    if (n == strlen (command_table[i].name)
	&& strncmp ((const char *)line, command_table[i].name, n) == 0)
      break;

  if (i != N_CMDS)
    (*command_table[i].handler) (tty, p);
  else
    {
      uint8_t crlf[] = { '\r', '\n' };

      put_line (tty, "No such command: ");
      tty_send (tty, (const uint8_t *)line, n);
      tty_send (tty, crlf, sizeof (crlf));
    }
}