/* Copyright 2013-2019 Matt Tytel
 *
 * vital 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.
 *
 * vital 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 vital.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "oscillator_section.h"

#include "bar_renderer.h"
#include "curve_look_and_feel.h"
#include "skin.h"
#include "paths.h"
#include "fonts.h"
#include "full_interface.h"
#include "load_save.h"
#include "preset_selector.h"
#include "producers_module.h"
#include "synth_strings.h"
#include "synth_button.h"
#include "synth_oscillator.h"
#include "synth_gui_interface.h"
#include "synth_slider.h"
#include "text_look_and_feel.h"
#include "text_selector.h"
#include "wavetable_3d.h"

namespace {
  const std::string kSpectralMorphTypes[vital::SynthOscillator::kNumSpectralMorphTypes] = {
    "---",
    "VOCODE",
    "FORM SCALE",
    "HARM STRETCH",
    "INHARMONIC",
    "SMEAR",
    "RAND AMP",
    "LOW PASS",
    "HIGH PASS",
    "PHASE DISP",
    "SHEPARD TONE",
    "TIME SKEW",
  };

  const std::string kDistortionTypes[vital::SynthOscillator::kNumDistortionTypes] = {
    "---",
    "SYNC",
    "FORMANT",
    "QUANTIZE",
    "BEND",
    "SQUEEZE",
    "PULSE",
    "FM <- OSC",
    "FM <- OSC",
    "FM <- SAMPLE",
    "RM <- OSC",
    "RM <- OSC",
    "RM <- SAMPLE"
  };

  String getDistortionSuffix(int type, int index) {
    if (type == vital::SynthOscillator::kFmOscillatorA || type == vital::SynthOscillator::kRmOscillatorA)
      return " " + String(1 + vital::ProducersModule::getFirstModulationIndex(index));
    if (type == vital::SynthOscillator::kFmOscillatorB || type == vital::SynthOscillator::kRmOscillatorB)
      return " " + String(1 + vital::ProducersModule::getSecondModulationIndex(index));

    return "";
  }

  bool isSpectralMenuSeparator(int index) {
    return index == (vital::SynthOscillator::kNoSpectralMorph + 1);
  }

  bool isDistortionMenuSeparator(int index) {
    return index == (vital::SynthOscillator::kNone + 1) || index == vital::SynthOscillator::kFmOscillatorA;
  }

  String getDistortionMenuString(int type, int index) {
    return String(strings::kPhaseDistortionNames[type]) + getDistortionSuffix(type, index);
  }

  String getDistortionString(int type, int index) {
    return String(kDistortionTypes[type]) + getDistortionSuffix(type, index);
  }

  bool isBipolarDistortionType(int distortion_type) {
    return distortion_type == vital::SynthOscillator::kNone ||
           distortion_type == vital::SynthOscillator::kSqueeze ||
           distortion_type == vital::SynthOscillator::kSync ||
           distortion_type == vital::SynthOscillator::kFormant ||
           distortion_type == vital::SynthOscillator::kBend;
  }

  bool isBipolarSpectralMorphType(int morph_type) {
    return morph_type == vital::SynthOscillator::kNoSpectralMorph ||
           morph_type == vital::SynthOscillator::kVocode ||
           morph_type == vital::SynthOscillator::kFormScale ||
           morph_type == vital::SynthOscillator::kHarmonicScale ||
           morph_type == vital::SynthOscillator::kInharmonicScale ||
           morph_type == vital::SynthOscillator::kPhaseDisperse;
  }
} // namespace

class UnisonViewer : public BarRenderer {
  public:
    UnisonViewer(int index, const vital::output_map& mono_modulations, const vital::output_map& poly_modulations) :
        BarRenderer(vital::SynthOscillator::kMaxUnison) {
      std::string prefix = std::string("osc_") + std::to_string(index + 1);
      voices_ = { mono_modulations.at(prefix + "_unison_voices"), poly_modulations.at(prefix + "_unison_voices") };
      detune_ = { mono_modulations.at(prefix + "_unison_detune"), poly_modulations.at(prefix + "_unison_detune") };
      detune_power_ = { mono_modulations.at(prefix + "_detune_power"), poly_modulations.at(prefix + "_detune_power") };

      voices_slider_ = nullptr;
      detune_slider_ = nullptr;
      detune_power_slider_ = nullptr;
    }

    static inline vital::poly_float getOutputsTotal(std::pair<vital::Output*, vital::Output*> outputs,
                                                    vital::poly_float default_value, bool animate) {
      if (!outputs.first->owner->enabled() || !animate)
        return default_value;
      return outputs.first->trigger_value + outputs.second->trigger_value;
    }

    void setVoicesSlider(SynthSlider* slider) { voices_slider_ = slider; }
    void setDetuneSlider(SynthSlider* slider) { detune_slider_ = slider; }
    void setDetunePowerSlider(SynthSlider* slider) { detune_power_slider_ = slider; }

    void render(OpenGlWrapper& open_gl, bool animate) override {
      static constexpr float kHeightRatio = 0.7f;
      static constexpr int kMaxUnison = vital::SynthOscillator::kMaxUnison;

      int voices = getOutputsTotal(voices_, voices_slider_->getValue(), animate)[0];
      voices = std::min(std::max(voices, 1), kMaxUnison);
      float detune = 0.1f * getOutputsTotal(detune_, detune_slider_->getValue(), animate)[0];
      float detune_power = getOutputsTotal(detune_power_, detune_power_slider_->getValue(), animate)[0];

      setColor(findColour(Skin::kWidgetPrimary1, true).withMultipliedAlpha(0.5f));
      setBarWidth(1.0f / getWidth());
      float percent_active = 1.0f - getHeight() / (2.0f * getWidth());

      for (int i = 0; i < kMaxUnison; ++i) {
        setBottom(i, -kHeightRatio);
        setY(i, kHeightRatio);
      }
      
      float offset = -1.0f / getWidth();
      if (voices == 1)
        setX(0, offset);
      else {
        for (int i = 0; i < voices; ++i) {
          float t = 2.0f * i / (voices - 1.0f) - 1.0f;
          float center_offset = fabsf(t);
          float power_scale = vital::futils::powerScale(center_offset, detune_power);
          if (t < 0.0f)
            power_scale = -power_scale;
          setX(i, power_scale * percent_active * detune + offset);
        }
      }
      for (int i = voices; i < kMaxUnison; ++i)
        setX(i, -2.0f);

      BarRenderer::render(open_gl, animate);
    }

  private:
    std::pair<vital::Output*, vital::Output*> voices_;
    std::pair<vital::Output*, vital::Output*> detune_;
    std::pair<vital::Output*, vital::Output*> detune_power_;

    SynthSlider* voices_slider_;
    SynthSlider* detune_slider_;
    SynthSlider* detune_power_slider_;
};

class InvisibleSlider : public SynthSlider {
  public:
    InvisibleSlider(String name) : SynthSlider(std::move(name)) { }

    void paint(Graphics& g) override { }
    void drawShadow(Graphics& g) override { }
};

OscillatorSection::OscillatorSection(int index,
                                     const vital::output_map& mono_modulations,
                                     const vital::output_map& poly_modulations) :
    SynthSection(String("OSC ") + String(index + 1)), index_(index) {
  std::string number = std::to_string(index + 1);
  wavetable_ = std::make_unique<Wavetable3d>(index, mono_modulations, poly_modulations);
  addOpenGlComponent(wavetable_.get());
  wavetable_->addListener(this);

  transpose_quantize_button_ = std::make_unique<TransposeQuantizeButton>();
  addOpenGlComponent(transpose_quantize_button_.get());
  transpose_quantize_button_->addQuantizeListener(this);

  transpose_ = std::make_unique<SynthSlider>("osc_" + number + "_transpose");
  addSlider(transpose_.get());
  transpose_->setSliderStyle(Slider::RotaryHorizontalVerticalDrag);
  transpose_->setLookAndFeel(TextLookAndFeel::instance());
  transpose_->setSensitivity(kTransposeMouseSensitivity);
  transpose_->setTextEntrySizePercent(1.0f, 0.7f);
  transpose_->setShiftIndexAmount(vital::kNotesPerOctave);
  transpose_->overrideValue(Skin::kTextComponentOffset, 0.0f);
  transpose_->setModulationBarRight(false);

  tune_ = std::make_unique<SynthSlider>("osc_" + number + "_tune");
  addSlider(tune_.get());
  tune_->setSliderStyle(Slider::RotaryHorizontalVerticalDrag);
  tune_->setLookAndFeel(TextLookAndFeel::instance());
  tune_->setBipolar();
  tune_->setMaxDisplayCharacters(3);
  tune_->setMaxDecimalPlaces(0);
  tune_->setTextEntrySizePercent(1.0f, 0.7f);
  tune_->overrideValue(Skin::kTextComponentOffset, 0.0f);

  unison_detune_ = std::make_unique<SynthSlider>("osc_" + number + "_unison_detune");
  addSlider(unison_detune_.get());
  unison_detune_->setSliderStyle(Slider::RotaryHorizontalVerticalDrag);
  unison_detune_->setLookAndFeel(TextLookAndFeel::instance());
  unison_detune_->setMaxDisplayCharacters(3);
  unison_detune_->setMaxDecimalPlaces(0);
  unison_detune_->setTextEntrySizePercent(1.0f, 0.7f);
  unison_detune_->overrideValue(Skin::kTextComponentOffset, 0.0f);

  unison_detune_power_ = std::make_unique<InvisibleSlider>("osc_" + number + "_detune_power");
  addSlider(unison_detune_power_.get());
  unison_detune_power_->setSliderStyle(Slider::RotaryHorizontalVerticalDrag);
  unison_detune_power_->setLookAndFeel(CurveLookAndFeel::instance());
  unison_detune_power_->setPopupPrefix("Unison Detune Power: ");
  unison_detune_power_->setTextEntrySizePercent(1.0f, 0.7f);

  unison_voices_ = std::make_unique<SynthSlider>("osc_" + number + "_unison_voices");
  addSlider(unison_voices_.get());
  unison_voices_->setSliderStyle(Slider::RotaryHorizontalVerticalDrag);
  unison_voices_->setLookAndFeel(TextLookAndFeel::instance());
  unison_voices_->setTextEntrySizePercent(1.0f, 0.7f);
  unison_voices_->overrideValue(Skin::kTextComponentOffset, 0.0f);
  unison_voices_->setModulationBarRight(false);

  phase_ = std::make_unique<SynthSlider>("osc_" + number + "_phase");
  addSlider(phase_.get());
  phase_->setSliderStyle(Slider::RotaryHorizontalVerticalDrag);
  phase_->setLookAndFeel(TextLookAndFeel::instance());
  phase_->setTextEntrySizePercent(1.0f, 0.7f);
  phase_->overrideValue(Skin::kTextComponentOffset, 0.0f);
  phase_->setMaxDisplayCharacters(3);
  phase_->setMaxDecimalPlaces(0);

  random_phase_ = std::make_unique<SynthSlider>("osc_" + number + "_random_phase");
  addSlider(random_phase_.get());
  random_phase_->setSliderStyle(Slider::RotaryHorizontalVerticalDrag);
  random_phase_->setLookAndFeel(TextLookAndFeel::instance());
  random_phase_->setTextEntrySizePercent(1.0f, 0.7f);
  random_phase_->overrideValue(Skin::kTextComponentOffset, 0.0f);
  random_phase_->setMaxDisplayCharacters(3);
  random_phase_->setMaxDecimalPlaces(0);

  distortion_phase_ = std::make_unique<SynthSlider>("osc_" + number + "_distortion_phase");
  addSlider(distortion_phase_.get());
  distortion_phase_->setSliderStyle(Slider::LinearBar);
  distortion_phase_->setVisible(false);
  distortion_phase_->setBipolar(true);
  distortion_phase_->setModulationPlacement(BubbleComponent::above);

  level_ = std::make_unique<SynthSlider>("osc_" + number + "_level");
  addSlider(level_.get());
  level_->setSliderStyle(Slider::RotaryHorizontalVerticalDrag);

  pan_ = std::make_unique<SynthSlider>("osc_" + number + "_pan");
  addSlider(pan_.get());
  pan_->setSliderStyle(Slider::RotaryHorizontalVerticalDrag);
  pan_->setBipolar();

  distortion_control_name_ = "osc_" + number + "_distortion_type";
  current_distortion_type_ = 0;
  distortion_type_text_ = std::make_unique<PlainTextComponent>("Distortion Text", "---");
  addOpenGlComponent(distortion_type_text_.get());

  spectral_morph_control_name_ = "osc_" + number + "_spectral_morph_type";
  current_spectral_morph_type_ = 0;
  spectral_morph_type_text_ = std::make_unique<PlainTextComponent>("Frequency Morph Text", "---");
  addOpenGlComponent(spectral_morph_type_text_.get());

  quantize_control_name_ = "osc_" + number + "_transpose_quantize";

  distortion_amount_ = std::make_unique<SynthSlider>("osc_" + number + "_distortion_amount");
  addSlider(distortion_amount_.get());
  distortion_amount_->setSliderStyle(Slider::RotaryHorizontalVerticalDrag);
  distortion_amount_->setKnobSizeScale(1.5f);
  distortion_amount_->setPopupPrefix("Wave Morph: ");

  spectral_morph_amount_ = std::make_unique<SynthSlider>("osc_" + number + "_spectral_morph_amount");
  addSlider(spectral_morph_amount_.get());
  spectral_morph_amount_->setSliderStyle(Slider::RotaryHorizontalVerticalDrag);
  spectral_morph_amount_->setKnobSizeScale(1.5f);
  spectral_morph_amount_->setPopupPrefix("Spectral Morph: ");

  spectral_morph_type_selector_ = std::make_unique<ShapeButton>("Spectral Morph", Colour(0xff666666),
                                                                 Colour(0xffaaaaaa), Colour(0xff888888));
  addAndMakeVisible(spectral_morph_type_selector_.get());
  spectral_morph_type_selector_->addListener(this);
  spectral_morph_type_selector_->setTriggeredOnMouseDown(true);
  spectral_morph_type_selector_->setShape(Path(), true, true, true);

  distortion_type_selector_ = std::make_unique<ShapeButton>("Distortion", Colour(0xff666666),
                                                            Colour(0xffaaaaaa), Colour(0xff888888));
  addAndMakeVisible(distortion_type_selector_.get());
  distortion_type_selector_->addListener(this);
  distortion_type_selector_->setTriggeredOnMouseDown(true);
  distortion_type_selector_->setShape(Path(), true, true, true);

  wave_frame_ = std::make_unique<SynthSlider>("osc_" + number + "_wave_frame");
  addSlider(wave_frame_.get());
  wave_frame_->setSliderStyle(Slider::LinearBarVertical);
  wave_frame_->setPopupPlacement(BubbleComponent::right);
  wave_frame_->setMouseWheelMovement(8.0);
  wave_frame_->setModulationPlacement(BubbleComponent::left);
  wave_frame_->setExtraModulationTarget(wavetable_.get());
  wave_frame_->setPopupPrefix("Frame: ");

  destination_selector_ = std::make_unique<ShapeButton>("Destination", Colour(0xff666666),
                                                        Colour(0xffaaaaaa), Colour(0xff888888));

  destination_control_name_ = "osc_" + number + "_destination";
  current_destination_ = 0;
  destination_text_ = std::make_unique<PlainTextComponent>("Destination Text", "---");
  addOpenGlComponent(destination_text_.get());
  
  addAndMakeVisible(destination_selector_.get());
  destination_selector_->addListener(this);
  destination_selector_->setTriggeredOnMouseDown(true);
  destination_selector_->setShape(Path(), true, true, true);

  prev_destination_ = std::make_unique<OpenGlShapeButton>("Prev Destination");
  addAndMakeVisible(prev_destination_.get());
  addOpenGlComponent(prev_destination_->getGlComponent());
  prev_destination_->addListener(this);
  prev_destination_->setShape(Paths::prev());

  next_destination_ = std::make_unique<OpenGlShapeButton>("Next Destination");
  addAndMakeVisible(next_destination_.get());
  addOpenGlComponent(next_destination_->getGlComponent());
  next_destination_->addListener(this);
  next_destination_->setShape(Paths::next());

  preset_selector_ = std::make_unique<PresetSelector>();
  addSubSection(preset_selector_.get());
  preset_selector_->addListener(this);
  setPresetSelector(preset_selector_.get(), true);

  edit_button_ = std::make_unique<OpenGlShapeButton>("edit");
  addAndMakeVisible(edit_button_.get());
  addOpenGlComponent(edit_button_->getGlComponent());
  edit_button_->addListener(this);
  edit_button_->setShape(Paths::pencil());

  dimension_button_ = std::make_unique<SynthButton>("osc_" + number + "_dimension");
  addButton(dimension_button_.get());
  dimension_button_->setNoBackground();
  dimension_button_->setShowOnColors(false);
  dimension_button_->setLookAndFeel(TextLookAndFeel::instance());

  dimension_value_ = std::make_unique<SynthSlider>("osc_" + number + "_view_2d");
  addSlider(dimension_value_.get());
  dimension_value_->setVisible(false);

  oscillator_on_ = std::make_unique<SynthButton>("osc_" + number + "_on");
  addButton(oscillator_on_.get());
  setActivator(oscillator_on_.get());

  wavetable_->setFrameSlider(wave_frame_.get());
  wavetable_->setSpectralMorphSlider(spectral_morph_amount_.get());
  wavetable_->setDistortionSlider(distortion_amount_.get());
  wavetable_->setDistortionPhaseSlider(distortion_phase_.get());

  setSkinOverride(Skin::kOscillator);

  unison_viewer_ = std::make_unique<UnisonViewer>(index, mono_modulations, poly_modulations);
  addOpenGlComponent(unison_viewer_.get());
  unison_viewer_->setVoicesSlider(unison_voices_.get());
  unison_viewer_->setDetuneSlider(unison_detune_.get());
  unison_viewer_->setDetunePowerSlider(unison_detune_power_.get());
  unison_viewer_->setInterceptsMouseClicks(false, false);

  prev_spectral_ = std::make_unique<OpenGlShapeButton>("Prev Spectral");
  addAndMakeVisible(prev_spectral_.get());
  addOpenGlComponent(prev_spectral_->getGlComponent());
  prev_spectral_->addListener(this);
  prev_spectral_->setShape(Paths::prev());

  next_spectral_ = std::make_unique<OpenGlShapeButton>("Next Spectral");
  addAndMakeVisible(next_spectral_.get());
  addOpenGlComponent(next_spectral_->getGlComponent());
  next_spectral_->addListener(this);
  next_spectral_->setShape(Paths::next());

  prev_distortion_ = std::make_unique<OpenGlShapeButton>("Prev Distortion");
  addAndMakeVisible(prev_distortion_.get());
  addOpenGlComponent(prev_distortion_->getGlComponent());
  prev_distortion_->addListener(this);
  prev_distortion_->setShape(Paths::prev());

  next_distortion_ = std::make_unique<OpenGlShapeButton>("Next Distortion");
  addAndMakeVisible(next_distortion_.get());
  addOpenGlComponent(next_distortion_->getGlComponent());
  next_distortion_->addListener(this);
  next_distortion_->setShape(Paths::next());
}

OscillatorSection::~OscillatorSection() = default;

void OscillatorSection::setSkinValues(const Skin& skin, bool top_level) {
  SynthSection::setSkinValues(skin, top_level);
  float horizontal_angle = skin.getValue(Skin::kWavetableHorizontalAngle);
  float vertical_angle = skin.getValue(Skin::kWavetableVerticalAngle);
  float draw_width = skin.getValue(Skin::kWavetableDrawWidth);
  float wave_height = skin.getValue(Skin::kWavetableWaveHeight);
  float y_offset = skin.getValue(Skin::kWavetableYOffset);
  wavetable_->setViewSettings(horizontal_angle, vertical_angle, draw_width, wave_height, y_offset);
}

void OscillatorSection::paintBackground(Graphics& g) {
  if (getWidth() == 0)
    return;

  paintContainer(g);
  paintHeadingText(g);

  paintChildrenBackgrounds(g);
  paintBorder(g);

  int title_width = findValue(Skin::kTitleWidth);
  setLabelFont(g);
  drawLabelConnectionForComponents(g, level_.get(), pan_.get());
  drawLabelForComponent(g, TRANS("PAN"), pan_.get());
  drawLabelForComponent(g, TRANS("LEVEL"), level_.get());

  int widget_margin = findValue(Skin::kWidgetMargin);
  int level_pan_x = title_width;
  int level_pan_width = getWidth() * kSectionWidthRatio;
  int knob_section_height = getKnobSectionHeight();
  int top_row_width = level_pan_width - 2 * widget_margin;
  int section2_x = getWidth() - 2 * top_row_width - 2 * widget_margin;
  int top_row_y = widget_margin;
  int top_row_height = level_->getY() - top_row_y;
  int phase_x = section2_x + top_row_width + widget_margin;
  int unison_x = section2_x;

  g.setColour(findColour(Skin::kTextComponentBackground, true));
  int label_rounding = findValue(Skin::kLabelBackgroundRounding);
  int morph_y = getHeight() - knob_section_height + widget_margin;
  Rectangle<int> spectral_normal_bounds(spectral_morph_amount_->getX(), morph_y,
                                        spectral_morph_amount_->getWidth(), knob_section_height - 2 * widget_margin);
  g.fillRoundedRectangle(getLabelBackgroundBounds(spectral_normal_bounds, false).toFloat(), label_rounding);
  Rectangle<int> distortion_normal_bounds(distortion_amount_->getX(), morph_y,
                                          distortion_amount_->getWidth(), knob_section_height - 2 * widget_margin);
  g.fillRoundedRectangle(getLabelBackgroundBounds(distortion_normal_bounds, false).toFloat(), label_rounding);
  g.fillRoundedRectangle(destination_selector_->getBounds().toFloat(), label_rounding);

  paintKnobShadows(g);

  paintJointControl(g, level_pan_x + widget_margin, top_row_y, top_row_width, top_row_height, "PITCH");
  paintJointControl(g, unison_x, top_row_y, top_row_width, top_row_height, "UNISON");
  paintJointControl(g, phase_x, top_row_y, top_row_width, top_row_height, "PHASE");
  wavetable_->setDirty();
}

void OscillatorSection::resized() {
  if (getWidth() <= 0 || getHeight() <= 0)
    return;

  SynthSection::resized();

  preset_selector_->setColour(Skin::kIconButtonOff, findColour(Skin::kUiButton, true));
  preset_selector_->setColour(Skin::kIconButtonOffHover, findColour(Skin::kUiButtonHover, true));
  preset_selector_->setColour(Skin::kIconButtonOffPressed, findColour(Skin::kUiButtonPressed, true));
  dimension_button_->setColour(Skin::kIconButtonOff, findColour(Skin::kUiButton, true));
  dimension_button_->setColour(Skin::kIconButtonOffHover, findColour(Skin::kUiButtonHover, true));
  dimension_button_->setColour(Skin::kIconButtonOffPressed, findColour(Skin::kUiButtonPressed, true));
  edit_button_->setColour(Skin::kIconButtonOff, findColour(Skin::kUiButton, true));
  edit_button_->setColour(Skin::kIconButtonOffHover, findColour(Skin::kUiButtonHover, true));
  edit_button_->setColour(Skin::kIconButtonOffPressed, findColour(Skin::kUiButtonPressed, true));

  int label_height = findValue(Skin::kLabelBackgroundHeight);
  float label_text_height = findValue(Skin::kLabelHeight);
  Colour body_text = findColour(Skin::kBodyText, true);
  destination_text_->setColor(body_text);
  spectral_morph_type_text_->setColor(body_text);
  distortion_type_text_->setColor(body_text);

  int title_width = getTitleWidth();
  int widget_margin = getWidgetMargin();
  int text_height = findValue(Skin::kTextButtonHeight);
  int slider_width = getSliderWidth();

  int knob_section_height = getKnobSectionHeight();
  int slider_overlap = getSliderOverlap();
  int overlap_with_space = getSliderOverlapWithSpace();
  int wave_section_height = getHeight() - 2 * widget_margin;
  if (distortion_phase_->isVisible())
    wave_section_height -= slider_width - slider_overlap - overlap_with_space;

  int level_pan_x = title_width;
  int level_pan_width = getWidth() * kSectionWidthRatio;
  int top_row_width = level_pan_width - 2 * widget_margin;
  int knob_y = getHeight() - label_height - widget_margin - knob_section_height;
  int big_knob_height = getHeight() - knob_y;
  placeKnobsInArea(Rectangle<int>(level_pan_x, knob_y, level_pan_width, knob_section_height),
                   { level_.get(), pan_.get() });

  int section2_x = getWidth() - 2 * top_row_width - 2 * widget_margin;
  int wave_frame_x = section2_x - slider_width + overlap_with_space;
  int wavetable_x = level_pan_width + level_pan_x;
  int wavetable_width = wave_frame_x - wavetable_x + overlap_with_space;

  wavetable_->setBounds(wavetable_x, widget_margin, wavetable_width, wave_section_height);
  preset_selector_->setBounds(wavetable_x, widget_margin, wavetable_width, title_width - 2 * widget_margin);
  dimension_button_->setBounds(wavetable_x, widget_margin + wave_section_height - text_height,
                               text_height, text_height);

  int wave_frame_height = wave_section_height + 2 * widget_margin;
  wave_frame_->setBounds(wave_frame_x, 0, slider_width, wave_frame_height);
  int edit_x = wavetable_->getRight() - text_height;
  edit_button_->setBounds(edit_x, widget_margin + wave_section_height - text_height, text_height, text_height);

  int top_row_y = widget_margin;
  int text_section_height = knob_y - widget_margin;
  placeJointControls(title_width + widget_margin, top_row_y, level_pan_width - 2 * widget_margin, text_section_height,
                     transpose_.get(), tune_.get(), transpose_quantize_button_.get());

  int section2_width = getWidth() - section2_x;
  int unison_x = section2_x;
  placeJointControls(unison_x, top_row_y, top_row_width, text_section_height,
                     unison_voices_.get(), unison_detune_.get(), unison_detune_power_.get());
  unison_viewer_->setBounds(unison_detune_power_->getBounds());

  int phase_x = unison_x + top_row_width + widget_margin;
  placeJointControls(phase_x, top_row_y, top_row_width, text_section_height,
                     phase_.get(), random_phase_.get(), nullptr);

  placeKnobsInArea(Rectangle<int>(section2_x - widget_margin, knob_y, section2_width + widget_margin, big_knob_height),
                   { spectral_morph_amount_.get(), distortion_amount_.get() });

  int morph_y = getHeight() - knob_section_height + widget_margin;
  Rectangle<int> spectral_normal_bounds(spectral_morph_amount_->getX(), morph_y,
                                        spectral_morph_amount_->getWidth(), knob_section_height - 2 * widget_margin);
  Rectangle<int> spectral_label_bounds = getLabelBackgroundBounds(spectral_normal_bounds);
  int browse_width = spectral_label_bounds.getHeight();
  int browse_y = spectral_label_bounds.getY();
  prev_spectral_->setBounds(spectral_morph_amount_->getX(), browse_y, browse_width, browse_width);
  next_spectral_->setBounds(spectral_morph_amount_->getRight() - browse_width, browse_y,
                            browse_width, browse_width);

  prev_distortion_->setBounds(distortion_amount_->getX(), browse_y, browse_width, browse_width);
  next_distortion_->setBounds(distortion_amount_->getRight() - browse_width, browse_y,
                              browse_width, browse_width);

  spectral_morph_type_text_->setBounds(spectral_label_bounds);
  spectral_morph_type_text_->setTextSize(label_text_height);
  int spectral_menu_x = prev_spectral_->getRight();
  spectral_morph_type_selector_->setBounds(spectral_menu_x, prev_spectral_->getY(),
                                           next_spectral_->getX() - spectral_menu_x, prev_spectral_->getHeight());

  Rectangle<int> distortion_normal_bounds(distortion_amount_->getX(), morph_y,
                                          distortion_amount_->getWidth(), knob_section_height - 2 * widget_margin);
  distortion_type_text_->setBounds(getLabelBackgroundBounds(distortion_normal_bounds));
  distortion_type_text_->setTextSize(label_text_height);
  int distortion_menu_x = prev_distortion_->getRight();
  distortion_type_selector_->setBounds(distortion_menu_x, prev_distortion_->getY(),
                                       next_distortion_->getX() - distortion_menu_x, prev_distortion_->getHeight());

  distortion_phase_->setBounds(wavetable_->getX() - widget_margin,
                               wavetable_->getBottom() - slider_overlap + widget_margin,
                               wavetable_->getWidth() + 2 * widget_margin, slider_width);

  int destination_x = level_pan_x + widget_margin;
  int destination_y = getHeight() - label_height - widget_margin;
  destination_selector_->setBounds(destination_x, destination_y, top_row_width, label_height);
  destination_text_->setBounds(destination_selector_->getBounds());
  destination_text_->setTextSize(label_text_height);

  prev_destination_->setBounds(destination_x, destination_y, browse_width, browse_width);
  next_destination_->setBounds(destination_x + top_row_width - browse_width, destination_y,
                               browse_width, browse_width);
}

void OscillatorSection::buttonClicked(Button* clicked_button) {
  if (clicked_button == edit_button_.get()) {
    FullInterface* parent = findParentComponentOfClass<FullInterface>();
    if (parent)
      parent->showWavetableEditSection(index_);
  }
  else if (clicked_button == spectral_morph_type_selector_.get()) {
    PopupItems options;

    for (int i = 0; i < vital::SynthOscillator::kNumSpectralMorphTypes; ++i) {
      if (isSpectralMenuSeparator(i))
        options.addItem(-1, "");

      options.addItem(i, strings::kSpectralMorphNames[i]);
    }

    Point<int> position(spectral_morph_type_selector_->getX(), spectral_morph_type_selector_->getBottom());
    showPopupSelector(this, position, options, [=](int selection) { setSpectralMorphSelected(selection); });
  }
  else if (clicked_button == distortion_type_selector_.get()) {
    PopupItems options;
    
    for (int i = 0; i < vital::SynthOscillator::kNumDistortionTypes; ++i) {
      if (isDistortionMenuSeparator(i))
        options.addItem(-1, "");

      options.addItem(i, getDistortionMenuString(i, index_).toStdString());
    }

    Point<int> position(distortion_type_selector_->getX(), distortion_type_selector_->getBottom());
    showPopupSelector(this, position, options, [=](int selection) { setDistortionSelected(selection); });
  }
  else if (clicked_button == destination_selector_.get()) {
    PopupItems options;

    int num_source_destinations = vital::constants::kNumSourceDestinations;
    for (int i = 0; i < num_source_destinations; ++i)
      options.addItem(i, strings::kDestinationMenuNames[i]);

    Point<int> position(destination_selector_->getX(), destination_selector_->getBottom());
    showPopupSelector(this, position, options, [=](int selection) { setDestinationSelected(selection); });
  }
  else if (clicked_button == dimension_button_.get()) {
    int render_type = (wavetable_->getRenderType() + Wavetable3d::kNumRenderTypes - 1) % Wavetable3d::kNumRenderTypes;
    dimension_button_->setText(strings::kWavetableDimensionNames[render_type]);
    dimension_value_->setValue(render_type, sendNotificationSync);
    wavetable_->setRenderType(static_cast<Wavetable3d::RenderType>(render_type));
  }
  else if (clicked_button == prev_destination_.get()) {
    int new_destination = current_destination_ - 1 + vital::constants::kNumSourceDestinations;
    setDestinationSelected(new_destination % vital::constants::kNumSourceDestinations);
  }
  else if (clicked_button == next_destination_.get()) {
    int new_destination = (current_destination_ + 1) % vital::constants::kNumSourceDestinations;
    setDestinationSelected(new_destination);
  }
  else if (clicked_button == prev_spectral_.get()) {
    int new_morph_type = current_spectral_morph_type_ - 1 + vital::SynthOscillator::kNumSpectralMorphTypes;
    setSpectralMorphSelected(new_morph_type % vital::SynthOscillator::kNumSpectralMorphTypes);
  }
  else if (clicked_button == next_spectral_.get()) {
    int new_morph_type = (current_spectral_morph_type_ + 1) % vital::SynthOscillator::kNumSpectralMorphTypes;
    setSpectralMorphSelected(new_morph_type);
  }
  else if (clicked_button == prev_distortion_.get()) {
    int new_distortion_type = current_distortion_type_ - 1 + vital::SynthOscillator::kNumDistortionTypes;
    setDistortionSelected(new_distortion_type % vital::SynthOscillator::kNumDistortionTypes);
  }
  else if (clicked_button == next_distortion_.get()) {
    int new_distortion_type = (current_distortion_type_ + 1) % vital::SynthOscillator::kNumDistortionTypes;
    setDistortionSelected(new_distortion_type);
  }
  else
    SynthSection::buttonClicked(clicked_button);
}

void OscillatorSection::setAllValues(vital::control_map& controls) {
  SynthSection::setAllValues(controls);
  current_spectral_morph_type_ = controls[spectral_morph_control_name_]->value();
  current_distortion_type_ = controls[distortion_control_name_]->value();
  current_destination_ = controls[destination_control_name_]->value();
  transpose_quantize_button_->setValue(static_cast<int>(controls[quantize_control_name_]->value()));
  setupSpectralMorph();
  setupDistortion();
  setupDestination();
  vital::SynthOscillator::DistortionType type = (vital::SynthOscillator::DistortionType)current_distortion_type_;

  setDistortionPhaseVisible(vital::SynthOscillator::usesDistortionPhase(type));

  wavetable_->setSpectralMorphType(current_spectral_morph_type_);
  wavetable_->setDistortionType(current_distortion_type_);
  int render_type = dimension_value_->getValue();
  dimension_button_->setText(strings::kWavetableDimensionNames[render_type]);
  wavetable_->setRenderType(static_cast<Wavetable3d::RenderType>(render_type));
}

void OscillatorSection::setActive(bool active) {
  wavetable_->setActive(active);
  SynthSection::setActive(active);
  spectral_morph_amount_->setActive(active && current_spectral_morph_type_ != vital::SynthOscillator::kNoSpectralMorph);
  distortion_amount_->setActive(active && current_distortion_type_ != vital::SynthOscillator::kNone);
}

void OscillatorSection::setName(String name) {
  preset_selector_->setText(name);
}

Slider* OscillatorSection::getWaveFrameSlider() {
  return wave_frame_.get();
}

void OscillatorSection::setDistortionSelected(int selection) {
  current_distortion_type_ = selection;
  wavetable_->setDistortionType(selection);
  vital::SynthOscillator::DistortionType type = (vital::SynthOscillator::DistortionType)current_distortion_type_;
  setDistortionPhaseVisible(vital::SynthOscillator::usesDistortionPhase(type));
  notifyDistortionTypeChange();
}

void OscillatorSection::setSpectralMorphSelected(int selection) {
  current_spectral_morph_type_ = selection;
  wavetable_->setSpectralMorphType(selection);
  notifySpectralMorphTypeChange();
}

void OscillatorSection::setDestinationSelected(int selection) {
  current_destination_ = selection;
  notifyDestinationChange();
}

void OscillatorSection::toggleFilterInput(int filter_index, bool on) {
  vital::constants::SourceDestination current_destination = (vital::constants::SourceDestination)current_destination_;
  if (filter_index == 0)
    current_destination_ = vital::constants::toggleFilter1(current_destination, on);
  else
    current_destination_ = vital::constants::toggleFilter2(current_destination, on);
  notifyDestinationChange();
}

void OscillatorSection::loadBrowserState() {
  FullInterface* parent = findParentComponentOfClass<FullInterface>();
  if (parent)
    preset_selector_->setText(parent->getWavetableName(index_));
}

void OscillatorSection::setIndexSelected() {
  FullInterface* parent = findParentComponentOfClass<FullInterface>();
  if (parent == nullptr)
    return;
}

void OscillatorSection::prevClicked() {
  File wavetable_file = LoadSave::getShiftedFile(LoadSave::kWavetableFolderName, vital::kWavetableExtensionsList,
                                                 LoadSave::kAdditionalWavetableFoldersName, current_file_, -1);
  if (wavetable_file.exists())
    loadFile(wavetable_file);
  
  updatePopupBrowser(this);
}

void OscillatorSection::nextClicked() {
  File wavetable_file = LoadSave::getShiftedFile(LoadSave::kWavetableFolderName, vital::kWavetableExtensionsList,
                                                 LoadSave::kAdditionalWavetableFoldersName, current_file_, 1);
  if (wavetable_file.exists())
    loadFile(wavetable_file);
  
  updatePopupBrowser(this);
}

void OscillatorSection::textMouseDown(const MouseEvent& e) {
  static constexpr int kBrowserWidth = 600;
  static constexpr int kBrowserHeight = 400;
  Rectangle<int> bounds(unison_voices_->getX(), preset_selector_->getY(),
                        kBrowserWidth * size_ratio_, kBrowserHeight * size_ratio_);
  bounds = getLocalArea(this, bounds);
  showPopupBrowser(this, bounds, LoadSave::getWavetableDirectories(), vital::kWavetableExtensionsList,
                   LoadSave::kWavetableFolderName, LoadSave::kAdditionalWavetableFoldersName);
}

void OscillatorSection::quantizeUpdated() {
  int value = transpose_quantize_button_->getValue();
  SynthGuiInterface* parent = findParentComponentOfClass<SynthGuiInterface>();
  if (parent)
    parent->getSynth()->valueChangedInternal(quantize_control_name_, value);
}

void OscillatorSection::resetOscillatorModulationDistortionType() {
  if (vital::SynthOscillator::isFirstModulation(current_distortion_type_) ||
      vital::SynthOscillator::isSecondModulation(current_distortion_type_)) {
    current_distortion_type_ = vital::SynthOscillator::kNone;
    notifyDistortionTypeChange();
  }
}

bool OscillatorSection::loadAudioAsWavetable(String name, InputStream* audio_stream,
                                             WavetableCreator::AudioFileLoadStyle style) {
  preset_selector_->setText(name);

  FullInterface* parent = findParentComponentOfClass<FullInterface>();
  if (parent == nullptr) {
    delete audio_stream;
    return false;
  }

  wavetable_->setLoadingWavetable(true);
  bool success = parent->loadAudioAsWavetable(index_, name, audio_stream, style);
  wavetable_->setLoadingWavetable(false);
  wavetable_->repaintBackground();
  return success;
}

void OscillatorSection::loadWavetable(json& wavetable_data) {
  FullInterface* parent = findParentComponentOfClass<FullInterface>();
  if (parent == nullptr)
    return;

  wavetable_->setLoadingWavetable(true);
  parent->loadWavetable(index_, wavetable_data);
  wavetable_->setLoadingWavetable(false);
  std::string name = wavetable_data["name"];
  preset_selector_->setText(name);
}

void OscillatorSection::loadDefaultWavetable() {
  FullInterface* parent = findParentComponentOfClass<FullInterface>();
  wavetable_->setLoadingWavetable(true);
  if (parent)
    parent->loadDefaultWavetable(index_);
  wavetable_->setLoadingWavetable(false);

  preset_selector_->setText("Init");
}

void OscillatorSection::resynthesizeToWavetable() {
  FullInterface* parent = findParentComponentOfClass<FullInterface>();
  wavetable_->setLoadingWavetable(true);
  if (parent)
    parent->resynthesizeToWavetable(index_);
  wavetable_->setLoadingWavetable(false);
}

void OscillatorSection::saveWavetable() {
  FullInterface* parent = findParentComponentOfClass<FullInterface>();
  if (parent)
    parent->saveWavetable(index_);
}

void OscillatorSection::loadFile(const File& wavetable_file) {
  FullInterface* parent = findParentComponentOfClass<FullInterface>();
  if (parent == nullptr)
    return;

  current_file_ = wavetable_file;
  if (wavetable_file.getFileExtension() == String(".") + vital::kWavetableExtension) {
    wavetable_->setLoadingWavetable(true);
    parent->loadWavetableFile(index_, wavetable_file);
    wavetable_->setLoadingWavetable(false);
    preset_selector_->setText(wavetable_file.getFileNameWithoutExtension());
    wavetable_->repaintBackground();
  }
  else {
    FileInputStream* input_stream = new FileInputStream(wavetable_file);
    loadAudioAsWavetable(wavetable_file.getFileNameWithoutExtension(), input_stream,
                         WavetableCreator::kWavetableSplice);
  }
}

Rectangle<float> OscillatorSection::getWavetableRelativeBounds() {
  Rectangle<int> wavetable_bounds = wavetable_->getBounds();
  float width_ratio = 1.0f / getWidth();
  float height_ratio = 1.0f / getHeight();
  return Rectangle<float>(wavetable_bounds.getX() * width_ratio, wavetable_bounds.getY() * height_ratio,
                          wavetable_bounds.getWidth() * width_ratio, wavetable_bounds.getHeight() * height_ratio);
}

void OscillatorSection::setupSpectralMorph() {
  bool bipolar = isBipolarSpectralMorphType(current_spectral_morph_type_);
  spectral_morph_amount_->setBipolar(bipolar);
  spectral_morph_amount_->setDoubleClickReturnValue(true, bipolar ? 0.5f : 0.0f);
  spectral_morph_amount_->setActive(isActive() &&
                                    current_spectral_morph_type_ != vital::SynthOscillator::kNoSpectralMorph);
  spectral_morph_amount_->redoImage();
  spectral_morph_type_text_->setText(kSpectralMorphTypes[current_spectral_morph_type_]);
}

void OscillatorSection::setupDistortion() {
  bool bipolar = isBipolarDistortionType(current_distortion_type_);
  distortion_amount_->setBipolar(bipolar);
  distortion_amount_->setDoubleClickReturnValue(true, bipolar ? 0.5f : 0.0f);
  distortion_amount_->setActive(isActive() && current_distortion_type_ != vital::SynthOscillator::kNone);
  distortion_amount_->redoImage();
  distortion_type_text_->setText(getDistortionString(current_distortion_type_, index_));
}

void OscillatorSection::setupDestination() {
  for (Listener* listener : listeners_)
    listener->oscillatorDestinationChanged(this, current_destination_);

  destination_text_->setText(strings::kDestinationNames[current_destination_]);
}

void OscillatorSection::setDistortionPhaseVisible(bool visible) {
  if (visible == distortion_phase_->isVisible())
    return;
  distortion_phase_->setVisible(visible);
  resized();
  repaintBackground();
}

void OscillatorSection::notifySpectralMorphTypeChange() {
  setupSpectralMorph();

  SynthGuiInterface* parent = findParentComponentOfClass<SynthGuiInterface>();
  if (parent)
    parent->getSynth()->valueChangedInternal(spectral_morph_control_name_, current_spectral_morph_type_);
}

void OscillatorSection::notifyDistortionTypeChange() {
  setupDistortion();

  for (Listener* listener : listeners_)
    listener->distortionTypeChanged(this, current_distortion_type_);

  SynthGuiInterface* parent = findParentComponentOfClass<SynthGuiInterface>();
  if (parent)
    parent->getSynth()->valueChangedInternal(distortion_control_name_, current_distortion_type_);
}

void OscillatorSection::notifyDestinationChange() {
  setupDestination();

  SynthGuiInterface* parent = findParentComponentOfClass<SynthGuiInterface>();
  if (parent)
    parent->getSynth()->valueChangedInternal(destination_control_name_, current_destination_);
}

