/*
 * Author: klaute -Kai Lauterbach - @kailauterbach - me@klaute.de
 * Date: 09/2016
 * License: GPLv3
 */

/*****************************************************************************/

#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include <avr/wdt.h>

/*****************************************************************************/

void cc_init(void);
void cc_abort(void);
void cc_processData(uint8_t);
void cc_clearReadDataBuffer(void);

/*****************************************************************************/

void cc_setStartFreq(void);
void cc_setEndFreq(void);
void cc_setIntervall(void);
void cc_setFreqStep(void);
void cc_setDriveStrength(void);
void cc_startMeasurement(void);
void cc_getConfig(void);
void cc_enableClk(void);
void cc_disableClk(void);
void cc_saveDefaults(void);
void cc_setClkCorrection(void);
void cc_getClkCorrection(void);

/*****************************************************************************/

extern "C" {
  #include "globals.h"
}

/*****************************************************************************/

uint8_t cc_commands[] = { 
    CC_CMD_SET_START_FREQ,
    CC_CMD_SET_END_FREQ,
    CC_CMD_SET_INTERVALL,
    CC_CMD_SET_DRIVE_STRENGTH,
    CC_CMD_SET_FREQ_STEP,
    CC_CMD_START_MEASUREMENT,
    CC_CMD_GET_CONFIG,
    CC_CMD_EN_CLK,
    CC_CMD_DIS_CLK,
    CC_CMD_SAV_DFLT,
    CC_CMD_SET_CLK_CORR,
    CC_CMD_GET_CLK_CORR,
  };

void (*cc_cmd_functions[])() = {
    CC_CMD_SET_START_FREQ_FUNC,
    CC_CMD_SET_END_FREQ_FUNC,
    CC_CMD_SET_INTERVALL_FUNC,
    CC_CMD_SET_DRIVE_STRENGTH_FUNC,
    CC_CMD_SET_FREQ_STEP_FUNC,
    CC_CMD_START_MEASUREMENT_FUNC,
    CC_CMD_GET_CONFIG_FUNC,
    CC_CMD_EN_CLK_FUNC,
    CC_CMD_DIS_CLK_FUNC,
    CC_CMD_SAV_DFLT_FUNC,
    CC_CMD_SET_CLK_CORR_FUNC,
    CC_CMD_GET_CLK_CORR_FUNC,
  };

uint8_t cc_cmd_data_to_read[] = {
    CC_CMD_SET_START_FREQ_DATA_TO_READ,
    CC_CMD_SET_END_FREQ_DATA_TO_READ,
    CC_CMD_SET_INTERVALL_DATA_TO_READ,
    CC_CMD_SET_DRIVE_STRENGTH_DATA_TO_READ,
    CC_CMD_SET_FREQ_STEP_DATA_TO_READ,
    CC_CMD_START_MEASUREMENT_DATA_TO_READ,
    CC_CMD_GET_CONFIG_DATA_TO_READ,
    CC_CMD_EN_CLK_DATA_TO_READ,
    CC_CMD_DIS_CLK_DATA_TO_READ,
    CC_CMD_SAV_DFLT_DATA_TO_READ,
    CC_CMD_SET_CLK_CORR_DATA_TO_READ,
    CC_CMD_GET_CLK_CORR_DATA_TO_READ,
  };

uint8_t cc_read_data[CC_READ_DATA_MAX];

/*****************************************************************************/

uint8_t cc_state = CC_STATE_READ_SOM1;

uint8_t cc_cmd_to_call = CC_CMD_NO_CMD;

uint8_t cc_cmd_received_correct = MSG_INCOMPLETE;

uint8_t cc_cmd_data_read_cnt = 0;

/*****************************************************************************/

uint32_t read32BitDataFromBuffer(uint8_t pos)
{
  uint32_t tmp  = (uint32_t)cc_read_data[pos    ] << 24;
           tmp += (uint32_t)cc_read_data[pos + 1] << 16;
           tmp += (uint32_t)cc_read_data[pos + 2] <<  8;
           tmp += (uint32_t)cc_read_data[pos + 3];
  return tmp;
}

uint16_t read16BitDataFromBuffer(uint8_t pos)
{
  uint16_t tmp  = (uint16_t)cc_read_data[pos    ] << 8;
           tmp += (uint16_t)cc_read_data[pos + 1];
  return tmp;
}

/*****************************************************************************/

void cc_setStartFreq()
{
  uint32_t tmp_start_freq = read32BitDataFromBuffer(0);

  start_freq = keepFreqRange(tmp_start_freq);

  sendSOM();
  Serial.write(MSG_TYPE_ANSWER_OK);
  sendEOM();
}

void cc_setEndFreq()
{
  uint32_t tmp_end_freq = read32BitDataFromBuffer(0);

  end_freq = keepFreqRange(tmp_end_freq);

  sendSOM();
  Serial.write(MSG_TYPE_ANSWER_OK);
  sendEOM();
}

void cc_setFreqStep()
{
  uint32_t tmp_step_freq = read32BitDataFromBuffer(0);

  step_freq = keepFreqRange(tmp_step_freq);

  sendSOM();
  Serial.write(MSG_TYPE_ANSWER_OK);
  sendEOM();
}

void cc_setIntervall()
{
  intervall = read16BitDataFromBuffer(0);

  sendSOM();
  Serial.write(MSG_TYPE_ANSWER_OK);
  sendEOM();
}

void cc_setDriveStrength()
{
  enum si5351_drive tmp_ds  = (enum si5351_drive)cc_read_data[0];
  if (tmp_ds == SI5351_DRIVE_2MA ||
      tmp_ds == SI5351_DRIVE_4MA ||
      tmp_ds == SI5351_DRIVE_6MA ||
      tmp_ds == SI5351_DRIVE_8MA)
  {
    drive_str = tmp_ds;

    sendSOM();
    Serial.write(MSG_TYPE_ANSWER_OK);
    sendEOM();
  } else {
    sendSOM();
    Serial.write(MSG_TYPE_ANSWER_NOK);
    sendEOM();
  }
}

void cc_startMeasurement()
{

  // 1. send measurement info to host
  //    MSG_TYPE_CONFIG
  cc_getConfig();

  if (start_freq == 0 || start_freq > 150000000 ||
      end_freq   == 0 || end_freq   > 150000000 ||
      step_freq  == 0 || step_freq  > 150000000 ||
      start_freq >= end_freq ||
      intervall  == 0)
  {
    // on error

    sendSOM();
    Serial.write(MSG_TYPE_ANSWER_NOK);
    sendEOM();

    return;
  }

  // 2. start a for loop from the frequence to start to the end frequence
  si5351.drive_strength(SI5351_CLK0, drive_str); // 2 4 6 8ma

  uint8_t t = 0;
  for (t = 0; t < 100; t++)
  {
    uint16_t tmp = analogRead(A0);
             tmp = analogRead(A1);
  }

  uint32_t freq = 0;
  for (freq = start_freq; freq <= end_freq; freq += step_freq)
  {
    if (freq > end_freq)
    {
      // prevent to step over the end frequency
      // maybe the user is not allowed to send data in the frequency band
      freq = end_freq;
    }
    uint32_t a0_sum  = 0;
    uint32_t a1_sum  = 0;
    uint16_t i = 0;

    si5351.set_freq((uint64_t)freq * 100, SI5351_PLL_FIXED, SI5351_CLK0);
    si5351.output_enable(SI5351_CLK0, 1); // enable clock output 0
    delay(1);

    for (i = 0; i < intervall; i++)
    {
      // 3. on every loop read the analog input A0 and A1 for the in intervall (milliseconds)
      //    and generate the average value, read the ADC value every milli second.
      uint8_t t = 0;
      uint16_t ta0 = 0;
      uint16_t ta1 = 0;
      for (t = 0; t < MEAS_LOOP_CNT; t++)
      {
        ta0 += analogRead(A0);
        ta1 += analogRead(A1);
      }
      a0_sum += (ta0 / MEAS_LOOP_CNT);
      a1_sum += (ta1 / MEAS_LOOP_CNT);

      delay(1);
    }

    a0_sum = a0_sum / intervall;
    a1_sum = a1_sum / intervall;

    // 4. send the current output frequency, the drive strength and the measured ADC values from A0 and A1 to the host
    //    MSG_TYPE_MEAS_FREQ_INFO
    sendSOM();
    Serial.write(MSG_TYPE_MEAS_FREQ_INFO);
    Serial.write((uint8_t)((freq & 0xff000000) >> 24));
    Serial.write((uint8_t)((freq & 0x00ff0000) >> 16));
    Serial.write((uint8_t)((freq & 0x0000ff00) >>  8));
    Serial.write((uint8_t) (freq & 0x000000ff));
    Serial.write((uint8_t)((a0_sum & 0xff00) >> 8));
    Serial.write((uint8_t) (a0_sum & 0x00ff));
    Serial.write((uint8_t)((a1_sum & 0xff00) >> 8));
    Serial.write((uint8_t) (a1_sum & 0x00ff));
    sendEOM();

    si5351.output_enable(SI5351_CLK0, 0); // disable clock output 0

    if (freq >= end_freq)
      break; // abort the loop because all is done
  }

  // 5. send a measurement end message to the host
  //    MSG_TYPE_MEAS_END_INFO
  sendSOM();
  Serial.write(MSG_TYPE_MEAS_END_INFO);
  sendEOM();

  si5351.output_enable(SI5351_CLK0, 0); // disable clock output 0

}

void cc_getConfig()
{
  sendSOM();
  Serial.write(MSG_TYPE_CONFIG);
  Serial.write((uint8_t)((start_freq & 0xff000000) >> 24));
  Serial.write((uint8_t)((start_freq & 0x00ff0000) >> 16));
  Serial.write((uint8_t)((start_freq & 0x0000ff00) >>  8));
  Serial.write((uint8_t) (start_freq & 0x000000ff));
  Serial.write((uint8_t)((end_freq & 0xff000000) >> 24));
  Serial.write((uint8_t)((end_freq & 0x00ff0000) >> 16));
  Serial.write((uint8_t)((end_freq & 0x0000ff00) >>  8));
  Serial.write((uint8_t) (end_freq & 0x000000ff));
  Serial.write((uint8_t)((step_freq & 0xff000000) >> 24));
  Serial.write((uint8_t)((step_freq & 0x00ff0000) >> 16));
  Serial.write((uint8_t)((step_freq & 0x0000ff00) >>  8));
  Serial.write((uint8_t) (step_freq & 0x000000ff));
  Serial.write((uint8_t)((intervall & 0xff00) >> 8));
  Serial.write((uint8_t) (intervall & 0x00ff));
  Serial.write((uint8_t) drive_str);
  sendEOM();
}

/*****************************************************************************/

void cc_enableClk(void)
{
  sendSOM();
  if (cc_read_data[0] == SI5351_CLK0)
  {
    si5351.set_freq((uint64_t)start_freq * 100, SI5351_PLL_FIXED, SI5351_CLK0);
    si5351.output_enable(SI5351_CLK0, 1); // enable clock output 0
    Serial.write(MSG_TYPE_ANSWER_OK);
  }
  else if (cc_read_data[0] == SI5351_CLK1)
  {
    si5351.set_freq((uint64_t)start_freq * 100, SI5351_PLL_FIXED, SI5351_CLK1);
    si5351.output_enable(SI5351_CLK1, 1); // enable clock output 1
    Serial.write(MSG_TYPE_ANSWER_OK);
  }
  else if (cc_read_data[0] == SI5351_CLK2)
  {
    si5351.set_freq((uint64_t)start_freq * 100, SI5351_PLL_FIXED, SI5351_CLK2);
    si5351.output_enable(SI5351_CLK2, 1); // enable clock output 2
    Serial.write(MSG_TYPE_ANSWER_OK);
  } else {
    Serial.write(MSG_TYPE_ANSWER_NOK);
  }
  sendEOM();
}

/*****************************************************************************/

void cc_disableClk(void)
{
  sendSOM();
  if (cc_read_data[0] == SI5351_CLK0)
  {
    si5351.output_enable(SI5351_CLK0, 0); // disable clock output 0
    Serial.write(MSG_TYPE_ANSWER_OK);
  }
  else if (cc_read_data[0] == SI5351_CLK1)
  {
    si5351.output_enable(SI5351_CLK1, 0); // disable clock output 1
    Serial.write(MSG_TYPE_ANSWER_OK);
  }
  else if (cc_read_data[0] == SI5351_CLK2)
  {
    si5351.output_enable(SI5351_CLK2, 0); // disable clock output 2
    Serial.write(MSG_TYPE_ANSWER_OK);
  } else {
    Serial.write(MSG_TYPE_ANSWER_NOK);
  }
  sendEOM();
}

/*****************************************************************************/

void cc_saveDefaults(void)
{

  writeEEPROMConfig();

  sendSOM();
  Serial.write(MSG_TYPE_ANSWER_OK);
  sendEOM();
}

/*****************************************************************************/

void cc_setClkCorrection(void)
{
  uint32_t tmp_corr  = (uint32_t)cc_read_data[0] << 24;
           tmp_corr += (uint32_t)cc_read_data[1] << 16;
           tmp_corr += (uint32_t)cc_read_data[2] <<  8;
           tmp_corr += (uint32_t)cc_read_data[3];

  si5351.set_correction(tmp_corr);

  sendSOM();
  Serial.write(MSG_TYPE_ANSWER_OK);
  sendEOM();
}

void cc_getClkCorrection(void)
{
  uint32_t tmp_corr = si5351.get_correction();

  sendSOM();
  Serial.write((uint8_t)((tmp_corr & 0xff000000) >> 24));
  Serial.write((uint8_t)((tmp_corr & 0x00ff0000) >> 16));
  Serial.write((uint8_t)((tmp_corr & 0x0000ff00) >>  8));
  Serial.write((uint8_t) (tmp_corr & 0x000000ff));
  sendEOM();
}

/*****************************************************************************/

void cc_init()
{
  cc_state                = CC_STATE_READ_SOM1;
  cc_cmd_to_call          = CC_CMD_NO_CMD;
  cc_cmd_data_read_cnt    = 0;
  cc_cmd_received_correct = MSG_INCOMPLETE;
}

/*****************************************************************************/

void cc_abort()
{
  // send abort message, then init
  char* tmp = "     ";
  sprintf(tmp, "%c%c%c%c%c", MSG_SOM1, MSG_SOM2,
                             MSG_TYPE_ANSWER_NOK,
                             MSG_EOM1, MSG_EOM2);
  Serial.write(tmp);

  cc_init();
}

/*****************************************************************************/

void cc_processData(uint8_t c)
{
  uint8_t i = 0;

  switch (cc_state)
  {
    //*********************************//
    case CC_STATE_READ_SOM1:;
      if (c == MSG_SOM1)
        cc_state = CC_STATE_READ_SOM2;
      else
        cc_abort();
      break;

    //*********************************//
    case CC_STATE_READ_SOM2:;
      if (c == MSG_SOM2)
        cc_state = CC_STATE_READ_CMD;
      else
        cc_abort();
      break;

    //*********************************//
    case CC_STATE_READ_CMD:;
      for (i = 0; i < sizeof(cc_commands)/sizeof(uint8_t); i++)
      {
        if (cc_commands[i] == c)
        {
          if (cc_cmd_data_to_read[i] > 0)
            cc_state = CC_STATE_READ_DATA;
          else
            cc_state = CC_STATE_READ_EOM1;

          cc_cmd_to_call = i; // remember the index of command to call

          break; // break the loop
        }
      }
      break;

    //*********************************//
    case CC_STATE_READ_DATA:;
      // write the variable c to the input buffer
      cc_read_data[cc_cmd_data_read_cnt] = c;

      if (cc_cmd_data_read_cnt >= cc_cmd_data_to_read[cc_cmd_to_call]-1)
      {
        cc_state = CC_STATE_READ_EOM1;
      }

      cc_cmd_data_read_cnt++;

      break;

    //*********************************//
    case CC_STATE_READ_EOM1:;
      if (c == MSG_EOM1)
        cc_state = CC_STATE_READ_EOM2;
      else
        cc_abort();
      break;

    //*********************************//
    case CC_STATE_READ_EOM2:;
      if (c == MSG_EOM2)
      {
        cc_cmd_received_correct = MSG_COMPLETE;
      } else
        cc_abort();
      break;

    default:
      cc_abort();
  }

  //*********************************//
  if (cc_cmd_received_correct == MSG_COMPLETE)
  {
    // call the function using the received data
    (*cc_cmd_functions[cc_cmd_to_call])();
    // clear the read buffer
    cc_clearReadDataBuffer();
    cc_init();
  }
}

/*****************************************************************************/

void cc_clearReadDataBuffer()
{
  uint8_t i = 0;
  for (i = 0; i < CC_READ_DATA_MAX; i++)
  {
    cc_read_data[i] = 0x00;
  }
}

/*****************************************************************************/

void sendSOM()
{
  Serial.write(MSG_SOM1);
  Serial.write(MSG_SOM2);
}

void sendEOM()
{
  Serial.write(MSG_EOM1);
  Serial.write(MSG_EOM2);
}

/*****************************************************************************/

