#include "Pager.h"

#include <string.h>
#include <math.h>

#if defined(ESP_PLATFORM)
#include "esp_attr.h"
#endif

#if !RADIOLIB_EXCLUDE_PAGER

#if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE
// this is a massive hack, but we need a global-scope ISR to manage the bit reading
// let's hope nobody ever tries running two POCSAG receivers at the same time
static PhysicalLayer* readBitInstance = NULL;
static uint32_t readBitPin = RADIOLIB_NC;

#if defined(ESP8266) || defined(ESP32)
  IRAM_ATTR
#endif
static void PagerClientReadBit(void) {
  if(readBitInstance) {
    readBitInstance->readBit(readBitPin);
  }
}
#endif

PagerClient::PagerClient(PhysicalLayer* phy) {
  phyLayer = phy;
  #if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE
  readBitInstance = phyLayer;
  #endif
}

int16_t PagerClient::begin(float base, uint16_t speed, bool invert, uint16_t shift) {
  // calculate duration of 1 bit in us
  dataRate = (float)speed/1000.0f;
  bitDuration = (RadioLibTime_t)1000000/speed;

  // calculate 24-bit frequency
  baseFreq = base;
  baseFreqRaw = (baseFreq * 1000000.0) / phyLayer->getFreqStep();

  // calculate module carrier frequency resolution
  uint16_t step = round(phyLayer->getFreqStep());

  // calculate raw frequency shift
  shiftFreqHz = shift;
  shiftFreq = shiftFreqHz/step;
  inv = invert;

  // initialize BCH encoder
  RadioLibBCHInstance.begin(RADIOLIB_PAGER_BCH_N, RADIOLIB_PAGER_BCH_K, RADIOLIB_PAGER_BCH_PRIMITIVE_POLY);

  // configure for direct mode
  return(phyLayer->startDirect());
}

int16_t PagerClient::sendTone(uint32_t addr) {
  return(PagerClient::transmit(NULL, 0, addr));
}

#if defined(RADIOLIB_BUILD_ARDUINO)
int16_t PagerClient::transmit(String& str, uint32_t addr, uint8_t encoding, uint8_t function) {
  return(PagerClient::transmit(str.c_str(), addr, encoding, function));
}
#endif

int16_t PagerClient::transmit(const char* str, uint32_t addr, uint8_t encoding, uint8_t function) {
  return(PagerClient::transmit((uint8_t*)str, strlen(str), addr, encoding, function));
}

int16_t PagerClient::transmit(uint8_t* data, size_t len, uint32_t addr, uint8_t encoding, uint8_t function) {
  if(addr > RADIOLIB_PAGER_ADDRESS_MAX) {
    return(RADIOLIB_ERR_INVALID_ADDRESS_WIDTH);
  }

  if(((data == NULL) && (len > 0)) || ((data != NULL) && (len == 0))) {
    return(RADIOLIB_ERR_INVALID_PAYLOAD);
  }

  // get symbol bit length based on encoding
  uint8_t symbolLength = 0;
  if(encoding == RADIOLIB_PAGER_BCD) {
    symbolLength = 4;

  } else if(encoding == RADIOLIB_PAGER_ASCII) {
    symbolLength = 7;

  } else {
    return(RADIOLIB_ERR_INVALID_ENCODING);

  }

  // Automatically set function bits based on given encoding
  if (function == RADIOLIB_PAGER_FUNC_AUTO) {
    if(encoding == RADIOLIB_PAGER_BCD) {
      function = RADIOLIB_PAGER_FUNC_BITS_NUMERIC;

    } else if(encoding == RADIOLIB_PAGER_ASCII) {
      function = RADIOLIB_PAGER_FUNC_BITS_ALPHA;

    } else {
    return(RADIOLIB_ERR_INVALID_ENCODING);

    }
    if(len == 0) {
      function = RADIOLIB_PAGER_FUNC_BITS_TONE;
    }
  }
  if (function > RADIOLIB_PAGER_FUNC_BITS_ALPHA) {
    return(RADIOLIB_ERR_INVALID_FUNCTION);
  }

  // get target position in batch (3 LSB from address determine frame position in batch)
  uint8_t framePos = 2*(addr & 0x07);

  // get address that will be written into address frame
  uint32_t frameAddr = ((addr >> 3) << RADIOLIB_PAGER_ADDRESS_POS) | (function << RADIOLIB_PAGER_FUNC_BITS_POS);

  // calculate the number of 20-bit data blocks
  size_t numDataBlocks = (len * symbolLength) / RADIOLIB_PAGER_MESSAGE_BITS_LENGTH;
  if((len * symbolLength) % RADIOLIB_PAGER_MESSAGE_BITS_LENGTH > 0) {
    numDataBlocks += 1;
  }

  // calculate number of batches
  size_t numBatches = (1 + framePos + numDataBlocks) / RADIOLIB_PAGER_BATCH_LEN + 1;
  if((1 + numDataBlocks) % RADIOLIB_PAGER_BATCH_LEN == 0) {
    numBatches -= 1;
  }

  // calculate message length in 32-bit code words
  size_t msgLen = RADIOLIB_PAGER_PREAMBLE_LENGTH + (1 + RADIOLIB_PAGER_BATCH_LEN) * numBatches;

  #if RADIOLIB_STATIC_ONLY
    uint32_t msg[RADIOLIB_STATIC_ARRAY_SIZE];
  #else
    uint32_t* msg = new uint32_t[msgLen];
  #endif

  // build the message
  memset(msg, 0x00, msgLen*sizeof(uint32_t));

  // set preamble
  for(size_t i = 0; i < RADIOLIB_PAGER_PREAMBLE_LENGTH; i++) {
    msg[i] = RADIOLIB_PAGER_PREAMBLE_CODE_WORD;
  }

  // start by setting everything after preamble to idle
  for(size_t i = RADIOLIB_PAGER_PREAMBLE_LENGTH; i < msgLen; i++) {
    msg[i] = RADIOLIB_PAGER_IDLE_CODE_WORD;
  }

  // set frame synchronization code words
  for(size_t i = 0; i < numBatches; i++) {
    msg[RADIOLIB_PAGER_PREAMBLE_LENGTH + i*(1 + RADIOLIB_PAGER_BATCH_LEN)] = RADIOLIB_PAGER_FRAME_SYNC_CODE_WORD;
  }

  // write address code word
  msg[RADIOLIB_PAGER_PREAMBLE_LENGTH + 1 + framePos] = RadioLibBCHInstance.encode(frameAddr);

  // write the data as 20-bit code blocks
  if(len > 0) {
    int8_t remBits = 0;
    uint8_t dataPos = 0;
    for(size_t i = 0; i < numDataBlocks + numBatches - 1; i++) {
      uint8_t blockPos = RADIOLIB_PAGER_PREAMBLE_LENGTH + 1 + framePos + 1 + i;

      // check if we need to skip a frame sync marker
      if(((blockPos - (RADIOLIB_PAGER_PREAMBLE_LENGTH + 1)) % RADIOLIB_PAGER_BATCH_LEN) == 0) {
        blockPos++;
        i++;
      }

      // mark this as a message code word
      msg[blockPos] = RADIOLIB_PAGER_MESSAGE_CODE_WORD << (RADIOLIB_PAGER_CODE_WORD_LEN - 1);

      // first insert the remainder from previous code word (if any)
      if(remBits > 0) {
        // this doesn't apply to BCD messages, so no need to check that here
        uint8_t prev = rlb_reflect(data[dataPos - 1], 8);
        prev >>= 1;
        msg[blockPos] |= (uint32_t)prev << (RADIOLIB_PAGER_CODE_WORD_LEN - 1 - remBits);
      }

      // set all message symbols until we overflow to the next code word or run out of message symbols
      int8_t symbolPos = RADIOLIB_PAGER_CODE_WORD_LEN - 1 - symbolLength - remBits;
      while(symbolPos > (RADIOLIB_PAGER_FUNC_BITS_POS - symbolLength)) {

        // for BCD, encode the symbol
        uint8_t symbol = data[dataPos++];
        if(encoding == RADIOLIB_PAGER_BCD) {
          symbol = encodeBCD(symbol);
        }
        symbol = rlb_reflect(symbol, 8);
        symbol >>= (8 - symbolLength);

        // insert the next message symbol
        msg[blockPos] |= (uint32_t)symbol << symbolPos;
        symbolPos -= symbolLength;

        // check if we ran out of message symbols
        if(dataPos >= len) {
          // in BCD mode, pad the rest of the code word with spaces (0xC)
          if(encoding == RADIOLIB_PAGER_BCD) {
            uint8_t numSteps = (symbolPos - RADIOLIB_PAGER_FUNC_BITS_POS + symbolLength)/symbolLength;
            for(uint8_t j = 0; j < numSteps; j++) {
              symbol = encodeBCD(' ');
              symbol = rlb_reflect(symbol, 8);
              symbol >>= (8 - symbolLength);
              msg[blockPos] |= (uint32_t)symbol << symbolPos;
              symbolPos -= symbolLength;
            }
          }
          break;
        }
      }

      // ensure the parity bits are not set due to overflow
      msg[blockPos] &= ~(RADIOLIB_PAGER_BCH_BITS_MASK);

      // save the number of overflown bits
      remBits = RADIOLIB_PAGER_FUNC_BITS_POS - symbolPos - symbolLength;

      // do the FEC
      msg[blockPos] = RadioLibBCHInstance.encode(msg[blockPos]);
    }
  }

  // transmit the message
  PagerClient::write(msg, msgLen);

  #if !RADIOLIB_STATIC_ONLY
    delete[] msg;
  #endif

  // turn transmitter off
  phyLayer->standby();

  return(RADIOLIB_ERR_NONE);
}

#if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE
int16_t PagerClient::startReceive(uint32_t pin, uint32_t addr, uint32_t mask) {
  // save the variables
  readBitPin = pin;
  filterAddr = addr;
  filterMask = mask;
  filterAddresses = NULL;
  filterMasks = NULL;
  filterNumAddresses = 0;
  return(startReceiveCommon());
}

int16_t PagerClient::startReceive(uint32_t pin, uint32_t *addrs, uint32_t *masks, size_t numAddresses) {
  // save the variables
  readBitPin = pin;
  filterAddr = 0;
  filterMask = 0;
  filterAddresses = addrs;
  filterMasks = masks;
  filterNumAddresses = numAddresses;
  return(startReceiveCommon());
}

int16_t PagerClient::startReceiveCommon() {
  // set the carrier frequency
  int16_t state = phyLayer->setFrequency(baseFreq);
  RADIOLIB_ASSERT(state);

  // set bitrate
  state = phyLayer->setBitRate(dataRate);
  RADIOLIB_ASSERT(state);

  // set frequency deviation to 4.5 khz
  state = phyLayer->setFrequencyDeviation((float)shiftFreqHz / 1000.0f);
  RADIOLIB_ASSERT(state);

  // now set up the direct mode reception
  Module* mod = phyLayer->getMod();
  mod->hal->pinMode(readBitPin, mod->hal->GpioModeInput);

  // set direct sync word to the frame sync word
  // the logic here is inverted, because modules like SX1278
  // assume high frequency to be logic 1, which is opposite to POCSAG
  if(!inv) {
    phyLayer->setDirectSyncWord(~RADIOLIB_PAGER_FRAME_SYNC_CODE_WORD, 32);
  } else {
    phyLayer->setDirectSyncWord(RADIOLIB_PAGER_FRAME_SYNC_CODE_WORD, 32);
  }

  phyLayer->setDirectAction(PagerClientReadBit);
  phyLayer->receiveDirect();

  return(state);
}

size_t PagerClient::available() {
  return(phyLayer->available() + sizeof(uint32_t))/(sizeof(uint32_t) * (RADIOLIB_PAGER_BATCH_LEN + 1));
}

#if defined(RADIOLIB_BUILD_ARDUINO)
int16_t PagerClient::readData(String& str, size_t len, uint32_t* addr) {
  int16_t state = RADIOLIB_ERR_NONE;

  // determine the message length, based on user input or the amount of received data
  size_t length = len;
  if(length == 0) {
    // one batch can contain at most 80 message symbols
    length = available()*80;
  }

  // build a temporary buffer
  #if RADIOLIB_STATIC_ONLY
    uint8_t data[RADIOLIB_STATIC_ARRAY_SIZE + 1];
  #else
    uint8_t* data = new uint8_t[length + 1];
    RADIOLIB_ASSERT_PTR(data);
  #endif

  // read the received data
  state = readData(data, &length, addr);

  if(state == RADIOLIB_ERR_NONE) {
    // check tone-only tramsissions
    if(length == 0) {
      length = 6;
      strncpy((char*)data, "<tone>", length + 1);
    }

    // add null terminator
    data[length] = 0;

    // initialize Arduino String class
    str = String((char*)data);
  }

  // deallocate temporary buffer
  #if !RADIOLIB_STATIC_ONLY
    delete[] data;
  #endif

  return(state);
}
#endif

int16_t PagerClient::readData(uint8_t* data, size_t* len, uint32_t* addr) {
  // find the correct address
  bool match = false;
  uint8_t framePos = 0;
  uint8_t symbolLength = 0;
  while(!match && phyLayer->available()) {
    uint32_t cw = read();
    framePos++;

    // check if it's the idle code word
    if(cw == RADIOLIB_PAGER_IDLE_CODE_WORD) {
      continue;
    }

    // check if it's the sync word
    if(cw == RADIOLIB_PAGER_FRAME_SYNC_CODE_WORD) {
      framePos = 0;
      continue;
    }

    // not an idle code word, check if it's an address word
    if(cw & (RADIOLIB_PAGER_MESSAGE_CODE_WORD << (RADIOLIB_PAGER_CODE_WORD_LEN - 1))) {
      // this is pretty weird, it seems to be a message code word without address
      continue;
    }

    // should be an address code word, extract the address
    uint32_t addr_found = ((cw & RADIOLIB_PAGER_ADDRESS_BITS_MASK) >> (RADIOLIB_PAGER_ADDRESS_POS - 3)) | (framePos/2);
    if (addressMatched(addr_found)) {
      match = true;
      if(addr) {
        *addr = addr_found;
      }

      // determine the encoding from the function bits
      if((cw & RADIOLIB_PAGER_FUNCTION_BITS_MASK) >> RADIOLIB_PAGER_FUNC_BITS_POS == RADIOLIB_PAGER_FUNC_BITS_NUMERIC) {
        symbolLength = 4;
      } else {
        symbolLength = 7;
      }
    }
  }

  if(!match) {
    // address not found
    return(RADIOLIB_ERR_ADDRESS_NOT_FOUND);
  }

  // we have the address, start pulling out the message
  size_t decodedBytes = 0;
  uint32_t prevCw = 0;
  bool overflow = false;
  int8_t ovfBits = 0;
  while(phyLayer->available()) {
    uint32_t cw = read();

    // check if it's the idle code word
    if(cw == RADIOLIB_PAGER_IDLE_CODE_WORD) {
      break;
    }

    // skip the sync words
    if(cw == RADIOLIB_PAGER_FRAME_SYNC_CODE_WORD) {
      continue;
    }

    // check overflow from previous code word
    uint8_t bitPos = RADIOLIB_PAGER_CODE_WORD_LEN - 1 - symbolLength;
    if(overflow) {
      overflow = false;

      // this is a bit convoluted - first, build masks for both previous and current code word
      uint8_t currPos = RADIOLIB_PAGER_CODE_WORD_LEN - 1 - symbolLength + ovfBits;
      uint8_t prevPos = RADIOLIB_PAGER_MESSAGE_END_POS;
      uint32_t prevMask = (0x7FUL << prevPos) & ~((uint32_t)0x7FUL << (RADIOLIB_PAGER_MESSAGE_END_POS + ovfBits));
      uint32_t currMask = (0x7FUL << currPos) & ~((uint32_t)1 << (RADIOLIB_PAGER_CODE_WORD_LEN - 1));

      // next, get the two parts of the message symbol and stick them together
      uint8_t prevSymbol = (prevCw & prevMask) >> prevPos;
      uint8_t currSymbol = (cw & currMask) >> currPos;
      uint32_t symbol = prevSymbol << (symbolLength - ovfBits) | currSymbol;

      // finally, we can flip the bits
      symbol = rlb_reflect((uint8_t)symbol, 8);
      symbol >>= (8 - symbolLength);

      // decode BCD and we're done
      if(symbolLength == 4) {
        symbol = decodeBCD(symbol);
      }
      data[decodedBytes++] = symbol;

      // adjust the bit position of the next message symbol
      bitPos += ovfBits;
      bitPos -= symbolLength;
    }

    // get the message symbols based on the encoding type
    while(bitPos >= RADIOLIB_PAGER_MESSAGE_END_POS) {
      // get the message symbol from the code word and reverse bits
      uint32_t symbol = (cw & (0x7FUL << bitPos)) >> bitPos;
      symbol = rlb_reflect((uint8_t)symbol, 8);
      symbol >>= (8 - symbolLength);

      // decode BCD if needed
      if(symbolLength == 4) {
        symbol = decodeBCD(symbol);
      }
      data[decodedBytes++] = symbol;

      // now calculate if the next symbol is overflowing to the following code word
      int8_t remBits = bitPos - RADIOLIB_PAGER_MESSAGE_END_POS;
      if(remBits < symbolLength) {
        // overflow!
        prevCw = cw;
        overflow = true;
        ovfBits = remBits;
      }
      bitPos -= symbolLength;
    }

  }

  // save the number of decoded bytes
  *len = decodedBytes;
  return(RADIOLIB_ERR_NONE);
}
#endif

bool PagerClient::addressMatched(uint32_t addr) {
  // check whether to match single or multiple addresses/masks
  if(filterNumAddresses == 0) {
    return((addr & filterMask) == (filterAddr & filterMask));
  }
  
  // multiple addresses, check there are some to match
  if((filterAddresses == NULL) || (filterMasks == NULL)) {
    return(false);
  }

  for(size_t i = 0; i < filterNumAddresses; i++) {
    if((filterAddresses[i] & filterMasks[i]) == (addr & filterMasks[i])) {
      return(true);
    }
  }

  return(false);
}

void PagerClient::write(uint32_t* data, size_t len) {
  // write code words from buffer
  for(size_t i = 0; i < len; i++) {
    PagerClient::write(data[i]);
  }
}

void PagerClient::write(uint32_t codeWord) {
  // write single code word
  Module* mod = phyLayer->getMod();
  for(int8_t i = 31; i >= 0; i--) {
    uint32_t mask = (uint32_t)0x01 << i;
    RadioLibTime_t start = mod->hal->micros();

    // figure out the shift direction - start by assuming the bit is 0
    int16_t change = shiftFreq;

    // now check if it's actually 1
    if(codeWord & mask) {
      change = -shiftFreq;
    }

    // finally, check if inversion is enabled
    if(inv) {
      change = -change;
    }

    // now transmit the shifted frequency
    phyLayer->transmitDirect(baseFreqRaw + change);

    // this is pretty silly, while(mod->hal->micros() ... ) would be enough
    // but for some reason, MegaCore throws a linker error on it
    // "relocation truncated to fit: R_AVR_7_PCREL against `no symbol'"
    RadioLibTime_t now = mod->hal->micros();
    while(now - start < bitDuration) {
      now = mod->hal->micros();
    }
  }
}

#if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE
uint32_t PagerClient::read() {
  uint32_t codeWord = 0;
  codeWord |= (uint32_t)phyLayer->read() << 24;
  codeWord |= (uint32_t)phyLayer->read() << 16;
  codeWord |= (uint32_t)phyLayer->read() << 8;
  codeWord |= (uint32_t)phyLayer->read();

  // check if we need to invert bits
  // the logic here is inverted, because modules like SX1278
  // assume high frequency to be logic 1, which is opposite to POCSAG
  if(!inv) {
    codeWord = ~codeWord;
  }

  RADIOLIB_DEBUG_PROTOCOL_PRINTLN("R\t%lX", (long unsigned int)codeWord);
  // TODO BCH error correction here
  return(codeWord);
}
#endif

uint8_t PagerClient::encodeBCD(char c) {
  switch(c) {
    case '*':
      return(0x0A);
    case 'U':
      return(0x0B);
    case ' ':
      return(0x0C);
    case '-':
      return(0x0D);
    case ')':
      return(0x0E);
    case '(':
      return(0x0F);
  }
  return(c - '0');
}

char PagerClient::decodeBCD(uint8_t b) {
  switch(b) {
    case 0x0A:
      return('*');
    case 0x0B:
      return('U');
    case 0x0C:
      return(' ');
    case 0x0D:
      return('-');
    case 0x0E:
      return(')');
    case 0x0F:
      return('(');
  }
  return(b + '0');
}

#endif
