// Copyright 2012 Emilie Gillet.
//
// Author: Emilie Gillet (emilie.o.gillet@gmail.com)
//
// This program 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 <http://www.gnu.org/licenses/>.
//
// Settings written to eeprom

#ifndef EDGES_SETTINGS_H_
#define EDGES_SETTINGS_H_

#include "avrlibx/avrlibx.h"
#include "avrlibx/utils/op.h"

#include "edges/digital_oscillator.h"
#include "edges/hardware_config.h"
#include "edges/timer_oscillator.h"

namespace edges {

enum MidiMode {
  MIDI_MODE_MULTITIMBRAL,
  MIDI_MODE_POLYPHONIC,
  MIDI_MODE_3_1,
  MIDI_MODE_CHORDS,
  NUM_MIDI_MODES
};

static const uint16_t kHysteresisThreshold = 32;

struct ChannelData {
  uint8_t pw;
  bool quantized;
  bool arpeggio;
  uint8_t num_arpeggio_steps;
  
  int8_t arpeggio_step;
  // What are stored are not CV values; but instead values relative to the
  // first note of the sequence. For example, the sequence
  // 1000 2000 4000 2000
  // is stored as:
  // +1000, +2000, -2000
  // This sequence of values is added to the current CV. This allows
  // transposition.
  int16_t arpeggio_steps[7];
  
  int16_t offset;
  int16_t scale;
  int16_t fm_offset;
  
  inline int16_t dac_to_pitch(int16_t dac_code) const {
    int16_t pitch = S16U12MulShift12(scale, dac_code);
    pitch += offset;
    if (pitch < 0) {
      pitch = 0;
    }
    if (quantized) {
      pitch += 64;  // rounding
      pitch &= 0xff80;
    }
    if (pitch >= 16383) {
      pitch = 16383;
    }
    return pitch;
  }
  
  inline int16_t dac_to_fm(int16_t dac_code) const {
    return pw == PULSE_WIDTH_CV_CONTROLLED
        ? 0
        : S16U12MulShift12(scale, dac_code) + fm_offset;
  }

  inline int16_t arpeggio_offset() const {
    if (!arpeggio ||
        arpeggio_step == 0 ||
        arpeggio_step >= num_arpeggio_steps ||
        num_arpeggio_steps == 1) {
      return 0;
    } else {
      if (arpeggio_step == -1) {
        return arpeggio_steps[num_arpeggio_steps - 2];
      } else {
        return arpeggio_steps[arpeggio_step - 1];
      }
    }
  }
  
  inline void StartRecording() {
    num_arpeggio_steps = 1;
    arpeggio_step = -1;  // -1 = play the step currently being recorded
    arpeggio = true;
  }
  
  inline void UpdateArpeggiatorStep(
      uint16_t root_dac_code,
      uint16_t dac_code) {
    if (num_arpeggio_steps > 1) {
      int16_t this_note = dac_to_pitch(dac_code);
      int16_t root_note = dac_to_pitch(root_dac_code);
      arpeggio_steps[num_arpeggio_steps - 2] = this_note - root_note;
    }   
  }
  
  inline void StopRecording() {
    arpeggio_step = 0;
  }
  
  inline bool NextArpeggiatorStep() {
    if (num_arpeggio_steps < 8) {
      ++num_arpeggio_steps;
      return true;
    } else {
      StopRecording();
      return false;
    }
  }
};

struct SettingsData {
  ChannelData channel[4];
  uint8_t midi_channel;
  uint8_t midi_mode;
};

class Settings {
 public:
  Settings() { }
  ~Settings() { }
  
  static void Init(bool reset_to_factory_defaults);
  
  static PulseWidth pw(uint8_t channel) {
    return static_cast<PulseWidth>(data_.channel[channel].pw);
  }

  static bool quantized(uint8_t channel) {
    return data_.channel[channel].quantized;
  }

  static bool arpeggio(uint8_t channel) {
    return data_.channel[channel].arpeggio;
  }

  static OscillatorShape shape(uint8_t channel) {
    return static_cast<OscillatorShape>(data_.channel[channel].pw);
  }
 
  static inline uint8_t num_steps(uint8_t channel) {
    return data_.channel[channel].num_arpeggio_steps;
  }
  
  static inline int16_t dac_to_fm(uint8_t channel, int16_t dac_code) {
#ifdef OCTAL_ADC
    return data_.channel[channel].dac_to_fm(dac_code);
#else
    return 0;
#endif  // OCTAL_ADC
  }
  
  static int16_t dac_to_pitch(uint8_t channel, int16_t dac_code) {
    const ChannelData& data = data_.channel[channel];
    int16_t pitch = data.dac_to_pitch(dac_code);
    if (data.quantized) {
      // When pitch is quantized, apply some hysteresis to DAC code.
      int16_t delta = dac_code - previous_code_[channel];
      if (delta < 0) {
        delta = -delta;
      }
      if (delta < kHysteresisThreshold) {
        pitch = previous_pitch_[channel];
      } else {
        previous_pitch_[channel] = pitch;
        previous_code_[channel] = dac_code;
      }
    }
    return pitch + data.arpeggio_offset();
  }
  
  static inline uint8_t midi_channel() { return data_.midi_channel; }
  static inline uint8_t midi_mode() { return data_.midi_mode; }
  
  static void set_midi_channel(uint8_t midi_channel) {
    data_.midi_channel = midi_channel;
    Save();
  }

  static void ToggleMidiMode() {
    data_.midi_mode = (data_.midi_mode + 1) % NUM_MIDI_MODES;
    Save();
  }
  
  static void StepPW(uint8_t channel) {
    uint8_t value = data_.channel[channel].pw + 1;
    if (value > PULSE_WIDTH_CV_CONTROLLED) {
      value = PULSE_WIDTH_50;
    }
    data_.channel[channel].pw = value;
  }
  
  static void StepArpeggio(uint8_t channel) {
    ChannelData* data = &data_.channel[channel];
    if (data->arpeggio_step != -1)  {
      ++data->arpeggio_step;
      if (data->arpeggio_step >= data->num_arpeggio_steps) {
        data->arpeggio_step = 0;
      }
    }
  }
  
  static void ToggleQuantizer(uint8_t channel) {
    data_.channel[channel].quantized ^= true;
  }
  
  static void ToggleArpeggio(uint8_t channel) {
    data_.channel[channel].arpeggio ^= true;
  }
  
  static void Calibrate(
      uint8_t channel,
      int16_t dac_code_c2,
      int16_t dac_code_c4,
      int16_t dac_code_fm) {
    if (dac_code_c4 != dac_code_c2) {
      int16_t scale = (24 * 128 * 4096L) / (dac_code_c4 - dac_code_c2);
      data_.channel[channel].scale = scale;
      data_.channel[channel].offset = (60 << 7) - \
          S16U12MulShift12(scale, (dac_code_c2 + dac_code_c4) >> 1);
    }
    data_.channel[channel].fm_offset = \
        -S16U12MulShift12(data_.channel[channel].scale, dac_code_fm);
    Save();
  }
  
  static ChannelData* mutable_channel_data(uint8_t channel) {
    return &data_.channel[channel];
  }

  static void Save();
  
 private:
  static int16_t previous_code_[kNumChannels];
  static int16_t previous_pitch_[kNumChannels];
  static int16_t pitch_bend_[kNumChannels];
  static SettingsData data_;
  
  DISALLOW_COPY_AND_ASSIGN(Settings);
};

extern Settings settings;

}  // namespace edges

#endif  // EDGES_SETTINGS_H_
