
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include "swis.h"
#include "Global/RISCOS.h"

#include "debugrep.h"
#include "usbjoystick.h"
#include "USBJoystickHdr.h"   // pktlog_callback veneer

#include "pktlog.h"


// One ring shared by all slots; 32 entries outlast the gap between the handler
// and the Reporter callback.
#define PKTLOG_ENTRIES        32

// Lines sent to Reporter per slot before it falls silent, in case a packet
// changes every time.
#define PKTLOG_MAX_REPORTED   256


struct pktlog_entry {
  uint32_t seq;               // 0 until written
  uint8_t  slot;
  uint8_t  valid;
  uint8_t  len;
  uint8_t  data[PKTLOG_BYTES];
};

static struct pktlog_entry ring[PKTLOG_ENTRIES];
static uint32_t newest = 0;          // sequence number of the newest entry
static uint32_t reported = 0;        // last sequence number sent to Reporter
static int callback_pending = FALSE;

// Per slot: the last packet compared against, for each kind.
static uint8_t last_other[Joystick_MaxSlots][PKTLOG_BYTES];
static uint8_t last_other_len[Joystick_MaxSlots];
static uint8_t last_input[Joystick_MaxSlots][PKTLOG_BYTES];   // body bytes zeroed
static uint8_t last_input_len[Joystick_MaxSlots];
static uint32_t seen_other[Joystick_MaxSlots];
static uint32_t reported_lines[Joystick_MaxSlots];


static void hex_line(char *line, const uint8_t *data, uint32_t len)
{
  for (uint32_t i=0; i<len; i++)
    sprintf(line + 3*i, "%02x ", data[i]);
  line[3*len] = '\0';
}


static void record(uint32_t slot, int valid, const uint8_t *data, uint32_t len)
{
  uint32_t s = newest + 1;
  struct pktlog_entry *e = &ring[s % PKTLOG_ENTRIES];

  e->slot = (uint8_t) slot;
  e->valid = (uint8_t) valid;
  e->len = (uint8_t) len;
  memcpy(e->data, data, len);
  e->seq = s;
  newest = s;

  if (!callback_pending) {
    callback_pending = TRUE;
    if (_swix(OS_AddCallBack, _INR(0,1), pktlog_callback, g_module_pw))
      callback_pending = FALSE;
  }
}


void pktlog_note(uint32_t slot, const uint8_t *data, uint32_t len, int valid, uint32_t body_start, uint32_t body_len)
{
  if (len > PKTLOG_BYTES)
    len = PKTLOG_BYTES;

  if (valid) {
    uint8_t masked[PKTLOG_BYTES];

    memcpy(masked, data, len);
    for (uint32_t i=body_start; i<body_start+body_len && i<len; i++)
      masked[i] = 0;

    if (len == last_input_len[slot] && memcmp(masked, last_input[slot], len) == 0)
      return;

    memcpy(last_input[slot], masked, len);
    last_input_len[slot] = (uint8_t) len;
  }
  else {
    seen_other[slot]++;

    if (len == last_other_len[slot] && memcmp(data, last_other[slot], len) == 0)
      return;

    memcpy(last_other[slot], data, len);
    last_other_len[slot] = (uint8_t) len;
  }

  record(slot, valid, data, len);
}


uint32_t pktlog_print(uint32_t slot, uint32_t max)
{
  uint32_t oldest = newest + 1;
  uint32_t found = 0;
  char line[3*PKTLOG_BYTES + 1];

  // walk back from the newest entry to find where max of this slot's begin
  for (uint32_t s=newest; s>0 && newest-s<PKTLOG_ENTRIES && found<max; s--) {
    struct pktlog_entry *e = &ring[s % PKTLOG_ENTRIES];
    if (e->seq == s && e->slot == slot) {
      oldest = s;
      found++;
    }
  }

  for (uint32_t s=oldest; s<=newest; s++) {
    struct pktlog_entry *e = &ring[s % PKTLOG_ENTRIES];
    if (e->seq == s && e->slot == slot) {
      hex_line(line, e->data, e->len);
      printf("    %-9s : %s\n", e->valid ? "input" : "non-input", line);
    }
  }

  return seen_other[slot];
}


void pktlog_reset(uint32_t slot)
{
  memset(last_other[slot], 0, PKTLOG_BYTES);
  memset(last_input[slot], 0, PKTLOG_BYTES);
  last_other_len[slot] = 0;
  last_input_len[slot] = 0;
  seen_other[slot] = 0;
  reported_lines[slot] = 0;
}


void pktlog_shutdown(void)
{
  _swix(OS_RemoveCallBack, _INR(0,1), pktlog_callback, g_module_pw);
  callback_pending = FALSE;
}


_kernel_oserror *pktlog_callback_handler(_kernel_swi_regs *r, void *pw)
{
  IGNORE(r);
  IGNORE(pw);

  uint32_t end = newest;
  uint32_t first = reported + 1;
  char line[3*PKTLOG_BYTES + 1];

  callback_pending = FALSE;

  // entries older than the ring holds have been overwritten
  if (end - reported > PKTLOG_ENTRIES)
    first = end - PKTLOG_ENTRIES + 1;

  for (uint32_t s=first; s<=end; s++) {
    struct pktlog_entry *e = &ring[s % PKTLOG_ENTRIES];
    if (e->seq != s)
      continue;

    if (reported_lines[e->slot] < PKTLOG_MAX_REPORTED) {
      hex_line(line, e->data, e->len);
      debug_printf("pktlog: slot %u: %s packet, %u bytes: %s\n", e->slot, e->valid ? "input (header or tail changed)" : "non-input", e->len, line);
    }
    else if (reported_lines[e->slot] == PKTLOG_MAX_REPORTED) {
      debug_printf("pktlog: slot %u: no more packets will be reported\n", e->slot);
    }
    reported_lines[e->slot]++;
  }

  reported = end;
  return NULL;
}
