// Copyright 2012 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.
//
// -----------------------------------------------------------------------------
//
// Settings

#ifndef BRAIDS_SETTINGS_H_
#define BRAIDS_SETTINGS_H_

#include "stmlib/stmlib.h"

namespace braids {

enum MacroOscillatorShape {
  MACRO_OSC_SHAPE_CSAW,
  MACRO_OSC_SHAPE_MORPH,
  MACRO_OSC_SHAPE_SAW_SQUARE,
  MACRO_OSC_SHAPE_SINE_TRIANGLE,
  MACRO_OSC_SHAPE_BUZZ,
  
  MACRO_OSC_SHAPE_SQUARE_SUB,
  MACRO_OSC_SHAPE_SAW_SUB,
  MACRO_OSC_SHAPE_SQUARE_SYNC,
  MACRO_OSC_SHAPE_SAW_SYNC,
  MACRO_OSC_SHAPE_TRIPLE_SAW,
  MACRO_OSC_SHAPE_TRIPLE_SQUARE,
  MACRO_OSC_SHAPE_TRIPLE_TRIANGLE,
  MACRO_OSC_SHAPE_TRIPLE_SINE,
  MACRO_OSC_SHAPE_TRIPLE_RING_MOD,
  MACRO_OSC_SHAPE_SAW_SWARM,
  MACRO_OSC_SHAPE_SAW_COMB,
  MACRO_OSC_SHAPE_TOY,

  MACRO_OSC_SHAPE_DIGITAL_FILTER_LP,
  MACRO_OSC_SHAPE_DIGITAL_FILTER_PK,
  MACRO_OSC_SHAPE_DIGITAL_FILTER_BP,
  MACRO_OSC_SHAPE_DIGITAL_FILTER_HP,
  MACRO_OSC_SHAPE_VOSIM,
  MACRO_OSC_SHAPE_VOWEL,
  MACRO_OSC_SHAPE_VOWEL_FOF,
  
  MACRO_OSC_SHAPE_HARMONICS,

  MACRO_OSC_SHAPE_FM,
  MACRO_OSC_SHAPE_FEEDBACK_FM,
  MACRO_OSC_SHAPE_CHAOTIC_FEEDBACK_FM,

  MACRO_OSC_SHAPE_PLUCKED,
  MACRO_OSC_SHAPE_BOWED,
  MACRO_OSC_SHAPE_BLOWN,
  MACRO_OSC_SHAPE_FLUTED,
  MACRO_OSC_SHAPE_STRUCK_BELL,
  MACRO_OSC_SHAPE_STRUCK_DRUM,
  MACRO_OSC_SHAPE_KICK,
  MACRO_OSC_SHAPE_CYMBAL,
  MACRO_OSC_SHAPE_SNARE,

  MACRO_OSC_SHAPE_WAVETABLES,
  MACRO_OSC_SHAPE_WAVE_MAP,
  MACRO_OSC_SHAPE_WAVE_LINE,
  MACRO_OSC_SHAPE_WAVE_PARAPHONIC,

  MACRO_OSC_SHAPE_FILTERED_NOISE,
  MACRO_OSC_SHAPE_TWIN_PEAKS_NOISE,
  MACRO_OSC_SHAPE_CLOCKED_NOISE,
  MACRO_OSC_SHAPE_GRANULAR_CLOUD,
  MACRO_OSC_SHAPE_PARTICLE_NOISE,
  
  MACRO_OSC_SHAPE_DIGITAL_MODULATION,

  MACRO_OSC_SHAPE_QUESTION_MARK,
  // MACRO_OSC_SHAPE_YOUR_ALGO
  MACRO_OSC_SHAPE_LAST,
  MACRO_OSC_SHAPE_LAST_ACCESSIBLE_FROM_META = MACRO_OSC_SHAPE_DIGITAL_MODULATION
};

enum Resolution {
  RESOLUTION_2_BIT,
  RESOLUTION_3_BIT,
  RESOLUTION_4_BIT,
  RESOLUTION_6_BIT,
  RESOLUTION_8_BIT,
  RESOLUTION_12_BIT,
  RESOLUTION_16_BIT,
  RESOLUTION_LAST
};

enum SampleRate {
  SAMPLE_RATE_4K,
  SAMPLE_RATE_8K,
  SAMPLE_RATE_16K,
  SAMPLE_RATE_24k,
  SAMPLE_RATE_32k,
  SAMPLE_RATE_48K,
  SAMPLE_RATE_96K,
  SAMPLE_RATE_LAST
};

enum PitchRange {
  PITCH_RANGE_EXTERNAL,
  PITCH_RANGE_FREE,
  PITCH_RANGE_EXTENDED,
  PITCH_RANGE_440,
  PITCH_RANGE_LFO  // This setting is hidden by default!
};

enum Setting {
  SETTING_OSCILLATOR_SHAPE,
  SETTING_RESOLUTION,
  SETTING_SAMPLE_RATE,
  SETTING_AD_TIMBRE,
  SETTING_TRIG_SOURCE,
  SETTING_TRIG_DELAY,
  SETTING_META_MODULATION,
  SETTING_PITCH_RANGE,
  SETTING_PITCH_OCTAVE,
  SETTING_QUANTIZER_SCALE,
  SETTING_VCO_FLATTEN,
  SETTING_VCO_DRIFT,
  SETTING_SIGNATURE,
  SETTING_BRIGHTNESS,
  SETTING_AD_ATTACK,
  SETTING_AD_DECAY,
  SETTING_AD_FM,
  SETTING_AD_COLOR,
  SETTING_AD_VCA,
  SETTING_QUANTIZER_ROOT,
  SETTING_LAST_EDITABLE_SETTING = SETTING_QUANTIZER_ROOT,
  
  // Not settings per-se, but used for menu display!
  SETTING_CALIBRATION,
  SETTING_CV_TESTER,
  SETTING_MARQUEE,
  SETTING_VERSION,
  SETTING_LAST
};

struct SettingsData {
  uint8_t shape;
  uint8_t resolution;
  uint8_t sample_rate;
  uint8_t ad_timbre;
  uint8_t auto_trig;
  uint8_t trig_delay;
  uint8_t meta_modulation;
  uint8_t pitch_range;
  uint8_t pitch_octave;
  uint8_t quantizer_scale;
  uint8_t vco_flatten;
  uint8_t vco_drift;
  uint8_t signature;
  uint8_t brightness;
  uint8_t ad_attack;
  uint8_t ad_decay;
  uint8_t ad_fm;
  uint8_t ad_color;
  uint8_t ad_vca;
  uint8_t quantizer_root;
  
  int32_t pitch_cv_offset;
  int32_t pitch_cv_scale;
  int32_t fm_cv_offset;
  
  int16_t parameter_cv_offset[2];
  uint16_t parameter_cv_scale[2];
  
  char marquee_text[55];
  char magic_byte;
};

struct SettingMetadata {
  uint8_t min_value;
  uint8_t max_value;
  const char name[5];
  const char* const* strings;
  
  int16_t Clip(int16_t value) const {
    if (value > max_value) {
      value = max_value;
    } else if (value < min_value) {
      value = min_value;
    }
    return value;
  }
};

class Settings {
 public:
  Settings() { }
  ~Settings() { }
  
  void Init();
  void Save();
  void Reset();
  
  void SetValue(Setting setting, uint8_t value) {
    uint8_t* data = static_cast<uint8_t*>(static_cast<void*>(&data_));
    data[setting] = value;
  }
  
  uint8_t GetValue(Setting setting) const {
    const uint8_t* data = static_cast<const uint8_t*>(
        static_cast<const void*>(&data_));
    return data[setting];
  }
  
  inline MacroOscillatorShape shape() const {
    return static_cast<MacroOscillatorShape>(data_.shape);
  }
  
  inline Resolution resolution() const {
    return static_cast<Resolution>(data_.resolution);
  }
  
  inline SampleRate sample_rate() const {
    return static_cast<SampleRate>(data_.sample_rate);
  }
  
  inline bool vco_flatten() const {
    return data_.vco_flatten;
  }

  inline uint8_t vco_drift() const {
    return data_.vco_drift;
  }

  inline uint8_t signature() const {
    return data_.signature;
  }

  inline bool meta_modulation() const {
    return data_.meta_modulation;
  }
  
  inline uint8_t trig_delay() const {
    return data_.trig_delay;
  }
  
  inline int32_t quantizer_root() const {
    return data_.quantizer_root;
  }
  
  inline const char* marquee_text() const {
    return data_.marquee_text;
  }
  
  inline char* mutable_marquee_text() {
    return data_.marquee_text;
  }
  
  inline const SettingsData& data() const { return data_; }
  inline SettingsData* mutable_data() { return &data_; }
  
  void Calibrate(
      int32_t adc_code_c2,
      int32_t adc_code_c4,
      int32_t adc_code_fm,
      int32_t adc_code_p0_min,
      int32_t adc_code_p0_max,
      int32_t adc_code_p1_min,
      int32_t adc_code_p1_max) {
    if (adc_code_c4 != adc_code_c2) {
      int32_t scale = (24 * 128 * 4096L) / (adc_code_c4 - adc_code_c2);
      data_.pitch_cv_scale = scale;
      data_.pitch_cv_offset = (60 << 7) - 
          (scale * ((adc_code_c2 + adc_code_c4) >> 1) >> 12);
      data_.fm_cv_offset = adc_code_fm;
    }
    
    // int32_t min_code[2] = { adc_code_p0_min, adc_code_p1_min };
    // int32_t max_code[2] = { adc_code_p0_max, adc_code_p1_max };
    //
    // for (int i = 0; i < 2; ++i) {
    //   int32_t d = max_code[i] - min_code[i];
    //   if (d > 3700) {
    //     int32_t scale = (32768 * 4106) / d;
    //     int32_t offset = -(min_code[i] * scale >> 12) - 40;
    //     data_.parameter_cv_offset[i] = offset;
    //     data_.parameter_cv_scale[i] = scale;
    //   }
    // }
    Save();
  }
  
  inline int32_t adc_to_pitch(int32_t pitch_adc_code) const {
    if (data_.pitch_range == PITCH_RANGE_EXTERNAL ||
        data_.pitch_range == PITCH_RANGE_LFO) {
      pitch_adc_code = pitch_adc_code * data_.pitch_cv_scale >> 12;
      pitch_adc_code += data_.pitch_cv_offset;
    } else if (data_.pitch_range == PITCH_RANGE_FREE) {
      pitch_adc_code = (pitch_adc_code - 1638);
      pitch_adc_code = pitch_adc_code * data_.pitch_cv_scale >> 12;
      pitch_adc_code += 60 << 7;
    } else if (data_.pitch_range == PITCH_RANGE_440) {
      pitch_adc_code = 69 << 7;
    } else {
      pitch_adc_code = (pitch_adc_code - 1638) * 9 >> 1;
      pitch_adc_code += 60 << 7;
    }
    return pitch_adc_code;
  }
  
  inline int32_t pitch_transposition() const {
    int32_t t = data_.pitch_range == PITCH_RANGE_LFO ? (unsigned)-36 << 7 : 0;
    t += (static_cast<int32_t>(data_.pitch_octave) - 2) * 12 * 128;
    return t;
  }
  
  inline int32_t adc_to_fm(int32_t fm_adc_code) const {
    fm_adc_code -= data_.fm_cv_offset;
    fm_adc_code = fm_adc_code * 7680 >> 12;
    if (data_.pitch_range == PITCH_RANGE_440) {
      fm_adc_code = 0;
    }
    return fm_adc_code;
  }
  
  inline int32_t adc_to_parameter(int index, int32_t adc_code) const {
    int32_t scale = static_cast<int32_t>(data_.parameter_cv_scale[index]);
    int32_t offset = static_cast<int32_t>(data_.parameter_cv_offset[index]);
    return (scale * adc_code >> 12) + offset;
  }

  inline bool paques() const {
    return paques_;
  }
  
  static const SettingMetadata& metadata(Setting setting) {
    return metadata_[setting];
  }

  static const Setting& setting_at_index(int16_t index) {
    return settings_order_[index];
  }
  
 private:
  void CheckPaques();

  SettingsData data_;
  
  uint16_t version_token_;
  bool paques_;
  
  static const SettingMetadata metadata_[SETTING_LAST];
  static const Setting settings_order_[SETTING_LAST];

  DISALLOW_COPY_AND_ASSIGN(Settings);
};

extern Settings settings;

}  // namespace braids

#endif  // BRAIDS_SETTINGS_H_
