// Copyright 2013 Emilie Gillet.
//
// Author: Emilie Gillet (emilie.o.gillet@gmail.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// 
// See http://creativecommons.org/licenses/MIT/ for more information.
//
// -----------------------------------------------------------------------------
//
// QPSK demodulator for firmware updater.

#ifndef STM_AUDIO_BOOTLOADER_DEMODULATOR_H_
#define STM_AUDIO_BOOTLOADER_DEMODULATOR_H_

#include "stmlib/stmlib.h"
#include "stmlib/utils/ring_buffer.h"

namespace stm_audio_bootloader {

enum DemodulatorState {
  DEMODULATOR_STATE_ESTIMATE_DC_COMPONENT,
  DEMODULATOR_STATE_ESTIMATE_POWER,
  DEMODULATOR_STATE_CARRIER_SYNC,
  DEMODULATOR_STATE_DECISION_SYNC,
  DEMODULATOR_STATE_OVERFLOW,
  DEMODULATOR_STATE_OK
};

const uint16_t kFilterSize = 7;
const uint16_t kHistorySize = 256;
// Works because kHistorySize is a power of 2!
const uint16_t kHistoryMask = kHistorySize - 1;

class Demodulator {
 public:
  Demodulator() { }
  ~Demodulator() { }
  
  void Init(
      uint32_t phase_increment,
      uint16_t cutoff_reciprocal,
      uint16_t symbol_duration);
  
  inline void PushSample(int16_t sample) {
    if (!samples_.writable()) {
      state_ = DEMODULATOR_STATE_OVERFLOW;
    }
    samples_.Overwrite(sample);
  }
  
  inline void SyncCarrier(bool discover) {
    skipped_samples_ = skipped_symbols_ = 0;
    samples_.Flush();
    symbols_.Flush();
    state_ = discover
        ? DEMODULATOR_STATE_ESTIMATE_DC_COMPONENT
        : DEMODULATOR_STATE_CARRIER_SYNC;
    phase_ = phase_error_ = 0;
  }
  
  inline void SyncDecision() {
    symbols_.Flush();
    state_ = DEMODULATOR_STATE_DECISION_SYNC;
    correlation_history_[0] = correlation_history_[1] = -1024;
    correlation_maximum_ = 0;
    skipped_symbols_ = 0;
  }
  
  inline size_t available() const {
    return symbols_.readable();
  }
  
  DemodulatorState state() const { return state_; }
  
  inline void ProcessAtLeast(size_t size) {
    if (samples_.readable() >= size) {
      while (samples_.readable()) {
        if (state_ == DEMODULATOR_STATE_ESTIMATE_DC_COMPONENT) {
          EstimateDcComponent();
        } else if (state_ == DEMODULATOR_STATE_ESTIMATE_POWER) {
          EstimatePower();
        } else {
          Demodulate();
        }
      }
    }
  }
  
  inline uint8_t NextSymbol() {
    return symbols_.ImmediateRead();
  }
  
  inline int32_t q() const { return q_history_[history_ptr_]; }
  inline int32_t i() const { return i_history_[history_ptr_]; }
  
 private:
  void Reset();
  void EstimateDcComponent();
  void EstimatePower();
  void Demodulate();
  int16_t DecideSymbol(bool adjust_timing);
  
  DemodulatorState state_;
   
  int32_t dc_offset_;
  uint32_t skipped_samples_;
  uint32_t power_;

  // Local oscillator.
  uint32_t phase_;
  uint32_t phase_increment_;
  int32_t phase_error_;
  
  // Demodulator filter.
  int32_t filter_coefficients_[kFilterSize];
  int32_t q_taps_[kFilterSize];
  int32_t i_taps_[kFilterSize];
  
  // History of demodulated signal for matched filter and decision.
  int16_t q_history_[kHistorySize];
  int16_t i_history_[kHistorySize];
  uint16_t history_ptr_;

  // Correlator between the demodulated signal and the synchronization
  // template.
  int32_t correlation_history_[2];
  int32_t correlation_maximum_;
  
  // Decision timing.
  uint16_t symbol_duration_;
  uint16_t decision_counter_;
  uint16_t skipped_symbols_;
  
  stmlib::RingBuffer<int16_t, 1024> samples_;
  stmlib::RingBuffer<uint8_t, 128> symbols_;
  
  DISALLOW_COPY_AND_ASSIGN(Demodulator);
};

}  // namespace stm_audio_bootloader

#endif // STM_AUDIO_BOOTLOADER_DEMODULATOR_H_
