/*
   GSPLUS - Advanced Apple IIGS Emulator Environment
   Based on the KEGS emulator written by Kent Dickey
   See COPYRIGHT.txt for Copyright information
   See LICENSE.txt for license (GPL v2)
 */

/** This module implements the LLAP port of the bridge. **/

#include <stdbool.h>
#include "../defc.h"
#include "atbridge.h"
#include "port.h"
#include "llap.h"

typedef enum {
  LLAP_DDP_SHORT = 0x01,
  LLAP_DDP_LONG = 0x02,
  LLAP_ENQ = 0x81,
  LLAP_ACK = 0x82,
  LLAP_RTS = 0x84,
  LLAP_CTS = 0x85
} LLAP_TYPES;

const unsigned int LLAP_PACKET_MAX = 603 /* bytes */;
const unsigned int LLAP_PACKET_MIN = 3 /* bytes */;
const double LLAP_IDG = 400 /* microseconds */;
const double LLAP_IFG = 200 /* microseconds */;
const double GAP_TOLERANCE = 4.0;

static struct packet_port_t llap_port;

typedef enum {
  DIALOG_READY,
  DIALOG_GOT_CTS,
  DIALOG_WAIT_IDG
} DIALOG_STATE;
static DIALOG_STATE dialog_state;
static double dialog_end_dcycs;
static double last_frame_dcycs;

void llap_init() {
  dialog_state = DIALOG_READY;
  last_frame_dcycs = 0;
  port_init(&llap_port);
}

void llap_shutdown() {
  port_shutdown(&llap_port);
}


/** Queue one data packet out from the bridge's LLAP port and into the guest. **/
void llap_enqueue_out(struct packet_t* packet) {
  // Generate the RTS.
  struct packet_t* rts = (struct packet_t*)malloc(sizeof(struct packet_t));
  rts->source.network = packet->source.network;
  rts->source.node = packet->source.node;
  rts->dest.network = packet->dest.network;
  rts->dest.node = packet->dest.node;
  rts->size = 0;
  rts->data = 0;
  rts->type = LLAP_RTS;
  enqueue_packet(&llap_port.out, rts);

  // Enqueue the data.
  enqueue_packet(&llap_port.out, packet);
}

struct packet_t* llap_dequeue_in() {
  return dequeue(&llap_port.in);
}

static void llap_dump_packet(size_t size, byte data[]) {
  if (size < LLAP_PACKET_MIN)
    atbridge_printf("LLAP short packet.\n");
  else if (size > LLAP_PACKET_MAX)
    atbridge_printf("LLAP long packet.\n");
  else
  {
    at_node_t dest = data[0];
    at_node_t source = data[1];
    LLAP_TYPES type = (LLAP_TYPES)(data[2]);

    const char* typeName = 0;
    switch (type)
    {
      case LLAP_DDP_SHORT:
        typeName = "DDP (short)";
        break;
      case LLAP_DDP_LONG:
        typeName = "DDP (long)";
        break;
      case LLAP_ENQ:
        typeName = "lapENQ";
        break;
      case LLAP_ACK:
        typeName = "lapACK";
        break;
      case LLAP_RTS:
        typeName = "lapRTS";
        break;
      case LLAP_CTS:
        typeName = "lapCTS";
        break;
    }

    if (typeName)
      atbridge_printf("LLAP[%d->%d] %s: %d bytes.\n", source, dest, typeName, size);
    else
      atbridge_printf("LLAP[%d->%d] %x: %d bytes.\n", source, dest, type, size);

    /*for (size_t i = 0; i < size; i++)
            atbridge_printf("%02x ", data[i]);
       atbridge_printf("\n");*/
  }
}

/** Reply to a control packet from the GS **/
static void llap_reply_control(at_node_t dest, at_node_t source, LLAP_TYPES type) {
  struct at_addr_t dest_addr = { 0, dest };
  struct at_addr_t source_addr = { 0, source };

  // Insert control packets at the head of the queue contrary to normal FIFO queue operation
  // to ensure that control frames arrive in the intended order.
  insert(&llap_port.out, dest_addr, source_addr, type, 0, 0);
}

/** Accept a data packet from the GS. **/
static void llap_handle_data(size_t size, byte data[]) {
  at_node_t dest = data[0];
  at_node_t source = data[1];
  LLAP_TYPES type = (LLAP_TYPES)(data[2]);

  const size_t data_size = size - 3;
  byte* data_copy = (byte*)malloc(data_size);
  memcpy(data_copy, data + 3, data_size);

  struct at_addr_t dest_addr = { 0, dest };
  struct at_addr_t source_addr = { 0, source };
  enqueue(&llap_port.in, dest_addr, source_addr, type, data_size, data_copy);
}

/** Accept a control packet from the GS. **/
static void llap_handle_control(size_t size, byte data[]) {
  at_node_t dest = data[0];
  at_node_t source = data[1];
  LLAP_TYPES type = (LLAP_TYPES)(data[2]);

  struct at_addr_t addr = { atbridge_get_net(), dest };

  switch (type)
  {
    case LLAP_ENQ:
      // Require the GS to take a valid "client" address not known to be in use.
      if (dest > 127 || dest == 0 || atbridge_address_used(&addr))
        llap_reply_control(source, dest, LLAP_ACK);
      break;
    case LLAP_ACK:
      break;
    case LLAP_RTS:
      if (dest != at_broadcast_node)
        // The GS is trying to make a directed transmission.  Provide the required RTS/CTS handshake.
        // Note that broadcast packets do not require a CTS.
        llap_reply_control(source, dest, LLAP_CTS);
      break;
    case LLAP_CTS:
      // The GS sent a CTS.  If the bridge has pending data, prepare to deliver the packet.
      dialog_state = DIALOG_GOT_CTS;
      break;
    default:
      break;
  }
}

/** Occassionally, we receive an invalid packet from the GS.  I'm unsure if this is due to a bug in GS/OS
    or, more likely, a bug in the SCC emulation.  Regardless, when such a thing does occur, discard the
        current, corrupted dialog.  Link errors are routine in real LocalTalk networks, and LocalTalk will recover.
 **/
static void llap_reset_dialog() {
  dialog_state = DIALOG_READY;
  last_frame_dcycs = 0;

  // Discard packets until the queue is either empty or the next dialog starts (and dialogs begin with an RTS).
  while (true)
  {
    struct packet_t* packet = queue_peek(&llap_port.out);

    if (packet && (packet->type != LLAP_RTS))
    {
      packet = dequeue(&llap_port.out);
      if (packet->data)
        free(packet->data);
      free(packet);
    }
    else
      break;
  }
}

/** Transfer (send) one LLAP packet from the GS. **/
void llap_enqueue_in(double dcycs, size_t size, byte data[]) {
  atbridge_printf("<%0.0f> TX: ", dcycs);
  llap_dump_packet(size, data);

  if (size < LLAP_PACKET_MIN)
    atbridge_printf("ATBridge: Dropping LLAP short packet.\n");
  else if (size > LLAP_PACKET_MAX)
    atbridge_printf("ATBridge: Dropping LLAP long packet.\n");
  else
  {
    last_frame_dcycs = dcycs;
    LLAP_TYPES type = (LLAP_TYPES)(data[2]);

    switch (type)
    {
      case LLAP_DDP_SHORT:
      case LLAP_DDP_LONG:
        llap_handle_data(size, data);
        break;
      case LLAP_ENQ:
      case LLAP_ACK:
      case LLAP_RTS:
      case LLAP_CTS:
        llap_handle_control(size, data);
        break;
      default:
        // Intentionally check for valid types and ingore packets with invalid types.
        // Sometimes, the bridge gets invalid packets from the GS, which tends to break the bridge.
        atbridge_printf("ATBridge: Dropping LLAP packet with invalid type.\n");
        llap_reset_dialog();
    }
  }
}

/** Transfer (receive) one LLAP packet to the GS. **/
void llap_dequeue_out(double dcycs, size_t* size, byte* data[]) {
  *size = 0;

  // The LocalTalk protocol requires a minimum 400us gap between dialogs (called the IDG).
  // If necessary, wait for the IDG.
  if (dialog_state == DIALOG_WAIT_IDG)
  {
    if ((dcycs - dialog_end_dcycs) >= LLAP_IDG)
      // The IDG is done.
      dialog_state = DIALOG_READY;
    else
      // Continue waiting for the IDG.
      return;
  }
  // The LocalTalk protocols requires a maximum 200us gap between frames within a dialog (called the IFG).
  // If we exceed the IFG, the bridge must be stuck in an incomplete or corrupt dialog.  In this case,
  // discard the current dialog and try again.
  if ((dialog_state != DIALOG_READY) && (last_frame_dcycs != 0) && ((dcycs - last_frame_dcycs) >= (GAP_TOLERANCE*LLAP_IFG)))
  {
    llap_reset_dialog();
    atbridge_printf("ATBridge: Dialog reset due to IFG violation.\n");
  }

  struct packet_t* packet = queue_peek(&llap_port.out);

  if ((dialog_state == DIALOG_READY) && (packet) && !(packet->type & 0x80) && (last_frame_dcycs != 0) && ((dcycs - last_frame_dcycs) >= (GAP_TOLERANCE*LLAP_IDG)))
  {
    llap_reset_dialog();
    packet = queue_peek(&llap_port.out);
    atbridge_printf("ATBridge: Dialog reset due to IDG violation.\n");
  }

  if (packet &&
      ((packet->type & 0x80) ||           /* Pass along control frames without waiting for a CTS. */
       (!(packet->type & 0x80) && (packet->dest.node == at_broadcast_node) && (dialog_state == DIALOG_READY)) ||           /* Pass along broadcast frames, which don't wait for CTS frames. */
       (!(packet->type & 0x80) && (packet->dest.node != at_broadcast_node) && (dialog_state == DIALOG_GOT_CTS))))           /* Pass along directed frames only after receiving a CTS handshake. */
  {
    dequeue(&llap_port.out);

    // Prepend the LLAP header.
    *size = packet->size + 3 + 2;
    *data = (byte*)malloc(*size);
    (*data)[0] = packet->dest.node;
    (*data)[1] = packet->source.node;
    (*data)[2] = packet->type;

    // Insert the data into the new LLAP packet.
    if (*size)
      memcpy((*data) + 3, packet->data, packet->size);

    // Fake a frame check sequence (FCS).  Since our SCC emulation doesn't actually
    // check the FCS, the value of the FCS doesn't matter.
    (*data)[packet->size + 3 + 0] = 0xff;
    (*data)[packet->size + 3 + 1] = 0xff;

    atbridge_printf("<%0.0f> RX: ", dcycs);
    llap_dump_packet(*size, *data);

    if (packet->type & 0x80)
      dialog_state = DIALOG_READY;
    else
    {
      // This was the last packet in the dialog.
      dialog_state = DIALOG_WAIT_IDG;
      dialog_end_dcycs = dcycs;
    }

    last_frame_dcycs = dcycs;

    free(packet->data);
    free(packet);
  }
}
