/*
  Copyright (c) 2016-2020 Peter Antypas

  This file is part of the MAIANAâ„¢ transponder firmware.

  The firmware is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <https://www.gnu.org/licenses/>
*/


#include "Receiver.hpp"
#include "EZRadioPRO.h"
#include "Events.hpp"
#include "EventQueue.hpp"
#include "NoiseFloorDetector.hpp"
#include "bsp.hpp"

Receiver::Receiver(GPIO_TypeDef *sdnPort, uint32_t sdnPin, GPIO_TypeDef *csPort, uint32_t csPin,
    GPIO_TypeDef *dataPort, uint32_t dataPin,
    GPIO_TypeDef *clockPort, uint32_t clockPin)
: RFIC(sdnPort, sdnPin, csPort, csPin, dataPort, dataPin, clockPort, clockPin)
{
  mSlotBitNumber = 0xffff;
  mSwitchAtNextSlot = false;
  mOneBitCount = 0;
  mChannel = CH_88;
  mBitCount = 0;
  mBitState = BIT_STATE_PREAMBLE_SYNC;
  mLastNRZIBit=0x00;
  mSwitchToChannel = mChannel;
  mRXByte = 0;
  mBitWindow = 0;
}

Receiver::~Receiver()
{
}

VHFChannel Receiver::channel()
{
  return mChannel;
}

bool Receiver::init()
{
  //DBG("Configuring IC\r\n");
  configure();
  resetBitScanner();
  return true;
}

void Receiver::startReceiving(VHFChannel channel)
{
  mChannel = channel;
  startListening(mChannel);
  resetBitScanner();
}

void Receiver::switchToChannel(VHFChannel channel)
{
  mSwitchAtNextSlot = true;
  mSwitchToChannel = channel;
}

void Receiver::startListening(VHFChannel channel)
{
  configureGPIOsForRX();

  mChannel = channel;
  RX_OPTIONS options;
  options.channel = AIS_CHANNELS[channel].ordinal;
  options.condition = 0;
  options.rx_len = 0;
  options.next_state1 = 0;
  options.next_state2 = 0;
  options.next_state3 = 0;

  sendCmd (START_RX, &options, sizeof options, NULL, 0);
}

void Receiver::resetBitScanner()
{
  mBitWindow = 0;
  mBitCount = 0;
  mOneBitCount = 0;
  mLastNRZIBit = 0xff;
  mRXByte = 0;
  mBitState = BIT_STATE_PREAMBLE_SYNC;

  mRXPacket.reset();
}

void Receiver::onBitClock()
{
  // Don't waste time processing bits when the transceiver is transmitting
  if ( gRadioState == RADIO_TRANSMITTING )
    return;


  uint8_t bit = HAL_GPIO_ReadPin(mDataPort, mDataPin);
  processNRZIBit(bit);
  if ( (mSlotBitNumber != 0xffff) && (mSlotBitNumber++ == CCA_SLOT_BIT) )
    {
      uint8_t rssi = reportRSSI();
      mRXPacket.setRSSI(rssi);
    }
}

void Receiver::timeSlotStarted(uint32_t slot)
{
  // This should never be called while transmitting. Transmissions start after the slot boundary and end before the end of it.
  //assert(gRadioState == RADIO_RECEIVING);
  //if ( gRadioState != RADIO_RECEIVING )
    //DBG("    **** WTF??? Transmitting past slot boundary? **** \r\n");

  mSlotBitNumber = 0;
  if ( mBitState == BIT_STATE_IN_PACKET )
    return;

  mRXPacket.setSlot(slot);
  if ( mSwitchAtNextSlot )
    {
      mSwitchAtNextSlot = false;
      startReceiving(mSwitchToChannel);
    }
}

void Receiver::processNRZIBit(uint8_t bit)
{
  if ( mLastNRZIBit == 0xff )
    {
      mLastNRZIBit = bit;
      return;
    }

  uint8_t decodedBit = !(mLastNRZIBit ^ bit);

  switch (mBitState) {
  case BIT_STATE_PREAMBLE_SYNC:
    {
      mLastNRZIBit = bit;
      mBitWindow <<= 1;
      mBitWindow |= decodedBit;

      /*
       * By checking for the last few training bits plus the HDLC start flag,
       * we gain enough confidence that this is not random noise.
       */
      if ( mBitWindow == 0b1010101001111110 || mBitWindow == 0b0101010101111110 )
        {
          mBitState = BIT_STATE_IN_PACKET;
          mRXPacket.setChannel(mChannel);
        }

      break;
    }
  case BIT_STATE_IN_PACKET:
    {
      if ( mRXPacket.size() >= MAX_AIS_RX_PACKET_SIZE )
        {
          // Start over
          startReceiving(mChannel);

          return;
        }

      if ( mOneBitCount >= 7 )
        {
          // Bad packet!
          startReceiving(mChannel);
          return;
        }

      mLastNRZIBit = bit;
      mBitWindow <<= 1;
      mBitWindow |= decodedBit;



      if ( (mBitWindow & 0x00ff) == 0x7E )
        {
          mBitState = BIT_STATE_PREAMBLE_SYNC;
          pushPacket();
          startReceiving(mChannel);
        }
      else
        {
          addBit(decodedBit);
        }

      break;
    }
  }

}


bool Receiver::addBit(uint8_t bit)
{
  bool result = true;
  if ( bit )
    {
      ++mOneBitCount;
    }
  else
    {
      // Don't put stuffed bits into the packet
      if ( mOneBitCount == 5 )
        result = false;

      mOneBitCount = 0;
    }

  if ( result )
    {
      mRXByte <<= 1;
      mRXByte |= bit;
      ++mBitCount;
    }

  if ( mBitCount == 8 )
    {
      // Commit to the packet!
      mRXPacket.addByte(mRXByte);
      mBitCount = 0;
      mRXByte = 0;
    }

  return result;
}

void Receiver::pushPacket()
{
#ifndef TX_TEST_MODE
  Event *p = EventPool::instance().newEvent(AIS_PACKET_EVENT);
  if ( p == nullptr )
    {
      //DBG("AISPacket allocation failed\r\n");
      return;
    }

  p->rxPacket = mRXPacket;
  mRXPacket.reset();
  EventQueue::instance().push(p);
#else
  mRXPacket.reset();
#endif
}

uint8_t Receiver::reportRSSI()
{
  uint8_t rssi = readRSSI();
  Event *e = EventPool::instance().newEvent(RSSI_SAMPLE_EVENT);
  if ( e )
    {
      e->rssiSample.channel = mChannel;
      e->rssiSample.rssi = rssi;
      EventQueue::instance().push(e);
    }
  return rssi;
}

void Receiver::configureGPIOsForRX()
{
  /*
   * Configure radio GPIOs for RX:
   * GPIO 0: Don't care
   * GPIO 1: RX_DATA
   * GPIO 2: Don't care
   * GPIO 3: RX_TX_DATA_CLK
   * NIRQ  : SYNC_WORD_DETECT
   */

  GPIO_PIN_CFG_PARAMS gpiocfg;
  gpiocfg.GPIO0 = 0x00;       // No change
  gpiocfg.GPIO1 = 0x14;       // RX data bits
  gpiocfg.GPIO2 = 0x00;       // No change
  gpiocfg.GPIO3 = 0x1F;       // RX/TX data clock
  gpiocfg.NIRQ  = 0x1A;       // Sync word detect
  gpiocfg.SDO   = 0x00;       // No change
  gpiocfg.GENCFG = 0x00;      // No change
  sendCmd(GPIO_PIN_CFG, &gpiocfg, sizeof gpiocfg, &gpiocfg, sizeof gpiocfg);

  bsp_set_rx_mode();
}