/*
* Copyright 2008, 2009, 2012 Free Software Foundation, Inc.
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU Affero 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 Affero General Public License for more details.

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

*/

#include "radioInterface.h"
#include <Logger.h>
#include "RTMD.h"

bool started = false;

/* Device side buffers */
static short *rx_buf[CHAN_MAX];
static short *tx_buf[CHAN_MAX];

/* Complex float to short conversion */
static void floatToShort(short *out, float *in, int num)
{
  for (int i = 0; i < num; i++) {
    out[2 * i + 0] = (short) in[2 * i + 0];
    out[2 * i + 1] = (short) in[2 * i + 1];
  }
}

/* Complex short to float conversion */
static void shortToFloat(float *out, short *in, int num)
{
  for (int i = 0; i < num; i++) {
    out[2 * i + 0] = (float) in[2 * i + 0];
    out[2 * i + 1] = (float) in[2 * i + 1];
  }
}

RadioInterface::RadioInterface(RadioDevice *wRadio,
			       int wChanM,
			       int wSPS,
                               int wReceiveOffset,
			       GSM::Time wStartTime)
  : mChanM(wChanM), underrun(false), sendCursor(0), rcvCursor(0), mOn(false),
    mRadio(wRadio), receiveOffset(wReceiveOffset), samplesPerSymbol(wSPS),
    powerScaling(1.0), loadTest(false)
{
  mClock.set(wStartTime);
}

RadioInterface::~RadioInterface(void)
{
  if (mOn) {
    mRadio->stop();
    close();

    for (int i = 0; i < mChanM; i++) {
      if (rcvBuffer[i] != NULL)
        delete rcvBuffer[i];
      if (sendBuffer[i] != NULL)
        delete sendBuffer[i];
    }
  }
}

double RadioInterface::fullScaleInputValue(void) {
  return mRadio->fullScaleInputValue();
}

double RadioInterface::fullScaleOutputValue(void) {
  return mRadio->fullScaleOutputValue();
}


void RadioInterface::setPowerAttenuation(double atten, int chan)
{
  double rfGain, digAtten;

  rfGain = mRadio->setTxGain(mRadio->maxTxGain() - atten, chan);
  digAtten = atten - mRadio->maxTxGain() + rfGain;

  if (digAtten < 1.0)
    powerScaling = 1.0;
  else
    powerScaling = 1.0/sqrt(pow(10, (digAtten/10.0)));
}

int RadioInterface::radioifyVector(signalVector &wVector,
				   float *retVector,
				   float scale,
				   bool zero)
{
  int i;
  signalVector::iterator itr = wVector.begin();

  if (zero) {
    memset(retVector, 0, wVector.size() * 2 * sizeof(float));
    return wVector.size();
  }

  for (i = 0; i < wVector.size(); i++) {
    retVector[2 * i + 0] = itr->real() * scale;
    retVector[2 * i + 1] = itr->imag() * scale;
    itr++;
  }

  return wVector.size();
}

int RadioInterface::unRadioifyVector(float *floatVector, int offset,
				     signalVector &newVector)
{
  int i;
  signalVector::iterator itr = newVector.begin();

  for (i = 0; i < newVector.size(); i++) {
    *itr++ = Complex<float>(floatVector[offset + 2 * i + 0],
			    floatVector[offset + 2 * i + 1]);
  }

  return newVector.size();
}

bool RadioInterface::tuneTx(double freq, int chan)
{
  return mRadio->setTxFreq(freq, chan);
}

bool RadioInterface::tuneRx(double freq, int chan)
{
  return mRadio->setRxFreq(freq, chan);
}


bool RadioInterface::start()
{
  int i;

  if (mOn)
    return false;

  mOn = true;
#ifdef USRP1
  mAlignRadioServiceLoopThread = new Thread(32768);
  mAlignRadioServiceLoopThread->start((void * (*)(void*))AlignRadioServiceLoopAdapter,
                                      (void*)this);
#endif
  writeTimestamp = mRadio->initialWriteTimestamp();
  readTimestamp = mRadio->initialReadTimestamp();
  for (i = 0; i < mChanM; i++) {
    sendBuffer[i] = new float[8*2*INCHUNK];
    rcvBuffer[i] = new float[8*2*OUTCHUNK];
  }

  /* Init I/O specific variables if applicable */ 
  init();

  mRadio->start(); 
  LOG(DEBUG) << "Radio started";
  mRadio->updateAlignment(writeTimestamp-10000); 
  mRadio->updateAlignment(writeTimestamp-10000);

  return true;
}

bool RadioInterface::stop()
{
  if (!mOn)
    return false;

  mOn = false;
  mRadio->stop();
}

#ifdef USRP1
void *AlignRadioServiceLoopAdapter(RadioInterface *radioInterface)
{
  while (radioInterface->on()) {
    radioInterface->alignRadio();
    pthread_testcancel();
  }
  return NULL;
}

void RadioInterface::alignRadio() {
  sleep(60);
  mRadio->updateAlignment(writeTimestamp+ (TIMESTAMP) 10000);
}
#endif

void RadioInterface::driveTransmitRadio(signalVector **radioBurst, bool *zeroBurst)
{
  int i;
  RTMD_SET("drvTxRadio");

  if (!mOn) {
    RTMD_VAL("drvTxRadio", -1);
    RTMD_CLEAR("drvTxRadio");
    return;
  }

  for (i = 0; i < mChanM; i++) {
    radioifyVector(*radioBurst[i], sendBuffer[i] + 2 * sendCursor,
                   powerScaling, zeroBurst[i]);
  }

  /* 
   * All bursts should be the same size since all transceivers are
   * tied with a single clock in the radio interface.
   */
  sendCursor += radioBurst[0]->size();

  pushBuffer();
  RTMD_CLEAR("drvTxRadio");
}

static inline void shiftRxBuffers(float **buf, int offset, int len, int chanM)
{
  for (int i = 0; i < chanM; i++)
      memmove(buf[i], buf[i] + offset, sizeof(float) * len);
}

void RadioInterface::loadVectors(unsigned tN, int samplesPerBurst,
                                 int idx, GSM::Time rxClock)
{
  int i;

  for (i = 0; i < mChanM; i++) {
    signalVector rxVector(samplesPerBurst);
    unRadioifyVector(rcvBuffer[i], idx * 2, rxVector);
    radioVector *rxBurst = new radioVector(rxVector, rxClock);
    mReceiveFIFO[i].write(rxBurst);
  }
}

void RadioInterface::driveReceiveRadio()
{
  RTMD_SET("drvRxRadio");
  if (!mOn) {
    RTMD_VAL("drvRxRadio", -1);
    RTMD_CLEAR("drvRxRadio");
    return;
  }

  if (mReceiveFIFO[0].size() > 8) {
    RTMD_VAL("drvRxRadio", 2);
    RTMD_CLEAR("drvRxRadio");
    return;
  }

  pullBuffer();

  GSM::Time rcvClock = mClock.get();
  rcvClock.decTN(receiveOffset);
  unsigned tN = rcvClock.TN();
  int rcvSz = rcvCursor;
  int readSz = 0;
  const int symbolsPerSlot = gSlotLen + 8;
  int samplesPerBurst = (symbolsPerSlot + (tN % 4 == 0)) * samplesPerSymbol;

  // while there's enough data in receive buffer, form received 
  //    GSM bursts and pass up to Transceiver
  // Using the 157-156-156-156 symbols per timeslot format.
  while (rcvSz >= samplesPerBurst) { 
    if (rcvClock.FN() >= 0) {
      loadVectors(tN, samplesPerBurst, readSz, rcvClock);
    }

    mClock.incTN();
    rcvClock.incTN();

    readSz += samplesPerBurst;
    rcvSz -= samplesPerBurst;

    tN = rcvClock.TN();
    samplesPerBurst = (symbolsPerSlot + (tN % 4 == 0)) * samplesPerSymbol;
  }

  if (readSz > 0) {
    rcvCursor -= readSz;
    shiftRxBuffers(rcvBuffer, 2 * readSz, 2 * rcvCursor, mChanM);
  }
  RTMD_CLEAR("drvRxRadio");
}

double RadioInterface::setRxGain(double dB, int chan)
{
  if (mRadio)
    return mRadio->setRxGain(dB, chan);
  else
    return -1;
}

double RadioInterface::getRxGain(int chan)
{
  if (mRadio)
    return mRadio->getRxGain(chan);
  else
    return -1;
}

bool RadioInterface::init()
{
	for (int i = 0; i < CHAN_MAX; i++) {
		rx_buf[i] = new short[2 * OUTCHUNK];
		tx_buf[i] = new short[4 * 2 * INCHUNK];
	}
}

void RadioInterface::close()
{
	for (int i = 0; i < CHAN_MAX; i++) {
		delete rx_buf[i];
		delete tx_buf[i];
	}
}

/* Receive a timestamped chunk from the device */ 
void RadioInterface::pullBuffer()
{
  bool local_underrun;
  RTMD_SET("RI-PullBuff");

  /* Read samples. Fail if we don't get what we want. */
  int num_rd = mRadio->readSamples(rx_buf, mChanM, OUTCHUNK, readTimestamp);

  LOG(DEBUG) << "Rx read " << num_rd << " samples from device";
  assert(num_rd == OUTCHUNK);

  underrun |= local_underrun;
  readTimestamp += (TIMESTAMP) num_rd;

  for (int i = 0; i < mChanM; i++)
    shortToFloat(rcvBuffer[i] + 2 * rcvCursor, rx_buf[i], num_rd);

  rcvCursor += num_rd;
  RTMD_CLEAR("RI-PullBuff");
}

/* Send timestamped chunk to the device with arbitrary size */ 
void RadioInterface::pushBuffer()
{
  RTMD_SET("RI-PushBuff");
  if (sendCursor < INCHUNK) {
    RTMD_VAL("RI-PushBuff", -1);
    RTMD_CLEAR("RI-PushBuff");
    return;
  }

  for (int i = 0; i < mChanM; i++)
    floatToShort(tx_buf[i], sendBuffer[i], sendCursor);

  /* Write samples. Fail if we don't get what we want. */
  int num_smpls = mRadio->writeSamples(tx_buf, mChanM, sendCursor,
                                       writeTimestamp, &underrun);
  assert(num_smpls == sendCursor);

  writeTimestamp += (TIMESTAMP) num_smpls;
  sendCursor = 0;
  RTMD_CLEAR("RI-PushBuff");
}
