#include "KISS_TO_TNC2.h"

bool validateTNC2Frame(const String &tnc2FormattedFrame);

String encode_address_ax25(String tnc2Address);
String decode_address_ax25(const String &ax25Address, bool &isLast, bool isRelay);

bool validateKISSFrame(const String &kissFormattedFrame);

String decapsulateKISS(const String &frame);

/*
 * https://ham.zmailer.org/oh2mqk/aprx/PROTOCOLS

	After successfull login, communication carries "TNC2" format
	APRS messages.  Namely text encoding of AX.25 UI frames in
	what became known as "TNC2 monitor style":

	    SOURCE>DESTIN:payload
	    SOURCE>DESTIN,VIA,VIA:payload

	The SOURCE, DESTIN, and VIA fields are AX.25 address fields,
        and have "-SSID" value annexed if the SSID is not zero.
	Also in VIA-fields, if the "HAS BEEN DIGIPEATED" bit is set
	(AX.25 v2 protocol feature) a star ('*') character is appended.
        VIA-fields are separated by comma (',') from DESTIN, and each
        other.

	A double-colon (':') separates address data from payload.
	The payload is passed _AS_IS_ without altering any message
	content bytes, however ending at first CR or LF character
	encountered in the packet.

 */

String encode_kiss(const String &tnc2FormattedFrame) {
  String ax25Frame = "";

  if (validateTNC2Frame(tnc2FormattedFrame)) {
    String address = "";
    bool dst_addres_written = false;
    for (int p = 0; p <= tnc2FormattedFrame.indexOf(':'); p++) {
      char currentChar = tnc2FormattedFrame.charAt(p);
      if (currentChar == ':' || currentChar == '>' || currentChar == ',') {
        if (!dst_addres_written && (currentChar == ',' || currentChar == ':')) {
          // ax25 frame DST SRC
          // tnc2 frame SRC DST
          ax25Frame = encode_address_ax25(address) + ax25Frame;
          dst_addres_written = true;
        } else {
          ax25Frame += encode_address_ax25(address);
        }
        address = "";
      } else {
        address += currentChar;
      }
    }
    auto lastAddressChar = (uint8_t) ax25Frame.charAt(ax25Frame.length() - 1);
    ax25Frame.setCharAt(ax25Frame.length() - 1, (char) (lastAddressChar | IS_LAST_ADDRESS_POSITION_MASK));
    ax25Frame += (char) APRS_CONTROL_FIELD;
    ax25Frame += (char) APRS_INFORMATION_FIELD;
    ax25Frame += tnc2FormattedFrame.substring(tnc2FormattedFrame.indexOf(':') + 1);
  }

  String kissFrame = encapsulateKISS(ax25Frame, CMD_DATA);
  return kissFrame;
}

String encapsulateKISS(const String &ax25Frame, uint8_t TNCCmd) {
  String kissFrame = "";
  kissFrame += (char) FEND; // start of frame
  kissFrame += (char) (0x0f & TNCCmd); // TNC0, cmd
  for (int i = 0; i < ax25Frame.length(); ++i) {
    char currentChar = ax25Frame.charAt(i);
    if (currentChar == (char) FEND) {
      kissFrame += (char) FESC;
      kissFrame += (char) TFEND;
    } else if (currentChar == (char) FESC) {
      kissFrame += (char) FESC;
      kissFrame += (char) TFESC;
    } else {
      kissFrame += currentChar;
    }
  }
  kissFrame += (char) FEND; // end of frame
  return kissFrame;
}


String decapsulateKISS(const String &frame) {
  String ax25Frame = "";
  for (int i = 2; i < frame.length() - 1; ++i) {
    char currentChar = frame.charAt(i);
    if (currentChar == (char) FESC) {
      char nextChar = frame.charAt(i + 1);
      if (nextChar == (char) TFEND) {
        ax25Frame += (char) FEND;
      } else if (nextChar == (char) TFESC) {
        ax25Frame += (char) FESC;
      }
      i++;
    } else {
      ax25Frame += currentChar;
    }
  }

  return ax25Frame;
}

/**
 *
 * @param inputKISSTNCFrame
 * @param dataFrame
 * @return Decapsulated TNC2KISS APRS data frame, or raw command data frame
 */
String decode_kiss(const String &inputKISSTNCFrame, bool &dataFrame) {
  String TNC2Frame = "";

  if (validateKISSFrame(inputKISSTNCFrame)) {
    dataFrame = inputKISSTNCFrame.charAt(1) == CMD_DATA;
    if (dataFrame){
      String ax25Frame = decapsulateKISS(inputKISSTNCFrame);
      bool isLast = false;
      String dst_addr = decode_address_ax25(ax25Frame.substring(0, 7), isLast, false);
      String src_addr = decode_address_ax25(ax25Frame.substring(7, 14), isLast, false);
      TNC2Frame = src_addr + ">" + dst_addr;
      int digi_info_index = 14;
      while (!isLast && digi_info_index + 7 < ax25Frame.length()) {
        String digi_addr = decode_address_ax25(ax25Frame.substring(digi_info_index, digi_info_index + 7), isLast, true);
        TNC2Frame += ',' + digi_addr;
        digi_info_index += 7;
      }
      TNC2Frame += ':';
      TNC2Frame += ax25Frame.substring(digi_info_index + 2);
    } else {
      // command frame, currently ignored
      TNC2Frame += inputKISSTNCFrame;
    }
  }

  return TNC2Frame;
}

/**
 * Encode adress in TNC2 monitor format to ax.25 format
 * @param tnc2Address
 * @return
 */
String encode_address_ax25(String tnc2Address) {
  bool hasBeenDigipited = tnc2Address.indexOf('*') != -1;

  if (tnc2Address.indexOf('-') == -1) {
    if (hasBeenDigipited) {
      // ex. TCPIP* in tnc2Address
      // so we skip last char
      tnc2Address = tnc2Address.substring(0, tnc2Address.length() - 1);
    }
    tnc2Address += "-0";
  }

  int separatorIndex = tnc2Address.indexOf('-');
  int ssid = tnc2Address.substring(separatorIndex + 1).toInt();
  // TODO: SSID should not be > 16
  String kissAddress = "";
  for (int i = 0; i < 6; ++i) {
    char addressChar;
    if (tnc2Address.length() > i && i < separatorIndex) {
      addressChar = tnc2Address.charAt(i);
    } else {
      addressChar = ' ';
    }
    kissAddress += (char) (addressChar << 1);
  }
  kissAddress += (char) ((ssid << 1) | 0b01100000 | (hasBeenDigipited ? HAS_BEEN_DIGIPITED_MASK : 0));
  return kissAddress;
}

/**
 * Decode address from ax.25 format to TNC2 monitor format
 * @param ax25Address
 * @return
 */
String decode_address_ax25(const String &ax25Address, bool &isLast, bool isRelay) {
  String TNCAddress = "";
  for (int i = 0; i < 6; ++i) {
    uint8_t currentCharacter = ax25Address.charAt(i);
    currentCharacter >>= 1;
    if (currentCharacter != ' ') {
      TNCAddress += (char) currentCharacter;
    }
  }
  auto ssid_char = (uint8_t) ax25Address.charAt(6);
  bool hasBeenDigipited = ssid_char & HAS_BEEN_DIGIPITED_MASK;
  isLast = ssid_char & IS_LAST_ADDRESS_POSITION_MASK;
  ssid_char >>= 1;

  int ssid = 0b1111 & ssid_char;
  if (ssid) {
    TNCAddress += '-';
    TNCAddress += ssid;
  }
  if (isRelay && hasBeenDigipited) {
    TNCAddress += '*';
  }
  return TNCAddress;
}

bool validateTNC2Frame(const String &tnc2FormattedFrame) {
  return (tnc2FormattedFrame.indexOf(':') != -1) && (tnc2FormattedFrame.indexOf('>') != -1);
}

bool validateKISSFrame(const String &kissFormattedFrame) {
  return kissFormattedFrame.charAt(0) == (char) FEND &&
         kissFormattedFrame.charAt(kissFormattedFrame.length() - 1) == (char) FEND;
}
