// 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.
//
// -----------------------------------------------------------------------------
//
// User interface.

#include "streams/ui.h"

#include <algorithm>

#include "stmlib/system/storage.h"
#include "stmlib/system/system_clock.h"

#include "streams/drivers/adc.h"
#include "streams/processor.h"

namespace streams {

const int32_t kLongPressDuration = 1000;

using namespace std;
using namespace stmlib;

Storage<0x801fc00, 4> ui_settings_storage;

void Ui::Init(Adc* adc, CvScaler* cv_scaler, Processor* processor) {
  leds_.Init();
  switches_.Init();
  adc_ = adc;
  cv_scaler_ = cv_scaler;
  processor_ = processor;
  
  fill(&pot_value_[0], &pot_value_[kNumPots], 0);
  fill(&pot_threshold_[0], &pot_threshold_[kNumPots], 0);
  
  if (!ui_settings_storage.ParsimoniousLoad(&ui_settings_, &version_token_)) {
    // Flash is not formatted. Initialize.
    for (uint8_t i = 0; i < kNumChannels; ++i) {
      ui_settings_.function[i] = PROCESSOR_FUNCTION_ENVELOPE;
      ui_settings_.alternate[i] = false;
    }
    ui_settings_.monitor_mode = MONITOR_MODE_OUTPUT;
    ui_settings_.linked = false;
  }
  
  // Initialize from settings in flash.
  monitor_mode_ = static_cast<MonitorMode>(ui_settings_.monitor_mode);
  for (uint8_t i = 0; i < kNumChannels; ++i) {
    meter_[i].Init();
    processor_[i].set_alternate(ui_settings_.alternate[i]);
    processor_[i].set_linked(ui_settings_.linked);
    processor_[i].set_function(
        static_cast<ProcessorFunction>(ui_settings_.function[i]));
    display_mode_[i] = DISPLAY_MODE_MONITOR;
  }
  
  secret_handshake_counter_ = 0;
  factory_testing_ = switches_.pressed_immediate(SWITCH_MONITOR);
}

void Ui::SaveState() {
  ui_settings_.monitor_mode = monitor_mode_;
  ui_settings_.linked = processor_[0].linked();
  ui_settings_.function[0] = processor_[0].function();
  ui_settings_.function[1] = processor_[1].function();
  ui_settings_.alternate[0] = processor_[0].alternate();
  ui_settings_.alternate[1] = processor_[1].alternate();
  ui_settings_storage.ParsimoniousSave(ui_settings_, &version_token_);
}

void Ui::PaintAdaptive(uint8_t channel, int32_t sample, int32_t gain) {
  meter_[channel].Process(sample);
  if (meter_[channel].cv()) {
    sample = sample * lut_2164_gain[-gain >> 9] >> 15;
    leds_.PaintCv(channel, sample * 5 >> 2);
  } else {
    leds_.PaintPositiveBar(channel, wav_db[meter_[channel].peak() >> 7] + gain);
  }
}

void Ui::PaintMonitor(uint8_t channel) {
  switch (monitor_mode_) {
    case MONITOR_MODE_EXCITE_IN:
      PaintAdaptive(channel, cv_scaler_->excite_sample(channel), 0);
      break;
      
    case MONITOR_MODE_AUDIO_IN:
      PaintAdaptive(channel, cv_scaler_->audio_sample(channel), 0);
      break;
      
    case MONITOR_MODE_VCA_CV:
      leds_.PaintPositiveBar(channel, 32768 + cv_scaler_->gain_sample(channel));
      break;
    
    case MONITOR_MODE_OUTPUT:
      if (processor_[channel].function() == PROCESSOR_FUNCTION_COMPRESSOR) {
        leds_.PaintNegativeBar(channel, processor_[channel].gain_reduction());
      } else {
        PaintAdaptive(
            channel,
            cv_scaler_->audio_sample(channel),
            cv_scaler_->gain_sample(channel));
      }
      break;
      
    default:
      break;
  }
}

void Ui::PaintTestStatus() {
  int32_t value = 0;
  value += pot_value_[0] - 32768;
  value += pot_value_[1] - 32768;
  value += pot_value_[2] - 32768;
  value += pot_value_[3] - 32768;
  value -= cv_scaler_->excite_sample(0) > 0 ? 0 : cv_scaler_->excite_sample(0);
  value -= cv_scaler_->excite_sample(1) > 0 ? 0 : cv_scaler_->excite_sample(1);
  value -= cv_scaler_->gain_sample(0);
  value -= cv_scaler_->gain_sample(1);
  value -= cv_scaler_->audio_sample(0) - 12000;
  value -= cv_scaler_->audio_sample(1) - 12000;
  if (switches_.pressed(0)) value = -32767;
  if (switches_.pressed(1)) value = 32767;
  CONSTRAIN(value, -32767, 32767);
  if (value < 12288 && value > -12288) value = 0;
  uint8_t r = value >= 0 ? 0 : ((-1 - value) >> 7);
  uint8_t g = value > 0 ? (value >> 7) : 0;
  for (uint8_t i = 0; i < 8; ++i) {
    leds_.set(i, r, g);
  }
}

void Ui::PaintLeds() {
  leds_.Clear();
  
  if (calibrating_) {
    for (uint8_t i = 0; i < kNumChannels; ++i) {
      uint8_t red, green;
      if (show_offset_level_ & (1 << i)) {
        int32_t gain_sample = cv_scaler_->raw_gain_sample(i);
        bool nulled = gain_sample > 59000;
        green = nulled ? 255 : 0;
        red = nulled ? 0 : 255;
      } else {
        red = 0;
        green = 255;
      }
      uint8_t pattern = i == 0 ? 255 : 9;
      for (uint8_t j = 0; j < 4; ++j) {
        bool on = (pattern & (1 << j)) != 0;
        leds_.set(i * 4 + j, on ? red : 0, on ? green : 0);
      }
    }
    return;
  }
  
  if (factory_testing_) {
    PaintTestStatus();
    return;
  }
  
  for (uint8_t i = 0; i < kNumChannels; ++i) {
    uint8_t bank = i * 4;
    switch (display_mode_[i]) {
      case DISPLAY_MODE_FUNCTION:
        {
          bool alternate = processor_[i].alternate();
          uint8_t intensity = 255;
          if (processor_[i].linked()) {
            uint8_t phase = system_clock.milliseconds() >> 1;
            phase += i * 128;
            phase = phase < 128 ? phase : (255 - phase);
            intensity = (phase * 224 >> 7) + 32;
            intensity = intensity * intensity >> 8;
          }
          uint8_t function = processor_[i].function();
          if (function == PROCESSOR_FUNCTION_FILTER_CONTROLLER) {
            for (uint8_t j = 0; j < 4; ++j) {
              leds_.set(bank + j,
                  alternate ? intensity : 0,
                  alternate ? 0 : intensity);
            }
          } else if (function < PROCESSOR_FUNCTION_LORENZ_GENERATOR) {
            leds_.set(
                bank + function,
                alternate ? intensity : 0,
                alternate ? 0 : intensity);
          } else {
            uint8_t index = (processor_[i].last_gain() >> 4) * 5 >> 4;
            if (index > 3) index = 3;
            int16_t color = processor_[i].last_frequency();
            color = color - 128;
            color *= 2;
            if (color < 0) {
              if (color < -127) color = -127;
              leds_.set(bank + index, 255 + (color * 2), 255);
            } else {
              if (color > 127) color = 127;
              leds_.set(bank + index, 255, 255 - (color * 2));
            }
          }
        }
        break;
        
      case DISPLAY_MODE_MONITOR_FUNCTION:
        {
          uint8_t position = static_cast<uint8_t>(monitor_mode_);
          leds_.set(position * 2, 255, 0);
          leds_.set(position * 2 + 1, 255, 0);
        }
        break;
      
      case DISPLAY_MODE_MONITOR:
        PaintMonitor(i);
        break;
    }
  }
}

void Ui::Poll() {
  // SysTick is at 4kHz to get a fast bargraph refresh.
  ++divider_;
  if ((divider_ & 3) == 0) {
    system_clock.Tick();
    switches_.Debounce();
  
    for (uint8_t i = 0; i < kNumSwitches; ++i) {
      if (switches_.just_pressed(i)) {
        queue_.AddEvent(CONTROL_SWITCH, i, 0);
        press_time_[i] = system_clock.milliseconds();
      }
      if (switches_.pressed(i) && press_time_[i] != 0) {
        int32_t pressed_time = system_clock.milliseconds() - press_time_[i];
        if (pressed_time > kLongPressDuration) {
          queue_.AddEvent(CONTROL_SWITCH, i, pressed_time);
          press_time_[i] = 0;
        }
      }
      if (switches_.released(i) && press_time_[i] != 0) {
        queue_.AddEvent(
            CONTROL_SWITCH,
            i,
            system_clock.milliseconds() - press_time_[i] + 1);
        press_time_[i] = 0;
      }
    }
  
    adc_->ScanPots();
    for (uint8_t i = 0; i < kNumPots; ++i) {
      int32_t value = adc_->pot(i);
      int32_t current_value = pot_value_[i];
      if (value >= current_value + pot_threshold_[i] ||
          value <= current_value - pot_threshold_[i] ||
          !pot_threshold_[i]) {
        Event e;
        e.control_id = i;
        e.data = value;
        queue_.AddEvent(CONTROL_POT, i, e.data);
        pot_value_[i] = value;
        pot_threshold_[i] = 256;
      }
    }
  }
  PaintLeds();
  leds_.Write();
}

void Ui::FlushEvents() {
  queue_.Flush();
}

void Ui::Link(uint8_t index) {
  if (processor_[0].linked()) {
    for (uint8_t i = 0; i < kNumChannels; ++i) {
      if (i != index) {
        display_mode_[i] = display_mode_[index];
        processor_[i].set_function(processor_[index].function());
        processor_[i].set_alternate(processor_[index].alternate());
      }
    }
  }
}

void Ui::OnPotMoved(const Event& e) {
  if (calibrating_) {
    if ((e.control_id & 1) == 0) {
      int32_t min = kDefaultOffset >> 1;
      int32_t max = 3 * min + 256;
      int32_t value = min + ((max - min) * e.data >> 16);
      cv_scaler_->set_dac_offset(e.control_id >> 1, value);
      show_offset_level_ |= (1 << (e.control_id >> 1));
    }
  } else {
    processor_[0].set_global(e.control_id, e.data);
    processor_[1].set_global(e.control_id, e.data);
    processor_[e.control_id >> 1].set_parameter(e.control_id & 1, e.data);
  }
}

void Ui::OnSwitchPressed(const Event& e) {
  if (factory_testing_) {
    if (e.control_id == SWITCH_MONITOR) {
      ++secret_handshake_counter_;
      if (secret_handshake_counter_ == 4) {
        factory_testing_ = false;
      }
    }
    return;
  }
  if (calibrating_) {
    cv_scaler_->SaveCalibrationData();
    calibrating_ = false;
    show_offset_level_ = 0;
    return;
  }
  // Double press!
  if ((e.control_id == SWITCH_MODE_1 && press_time_[SWITCH_MODE_2]) ||
      (e.control_id == SWITCH_MODE_2 && press_time_[SWITCH_MODE_1])) {
    press_time_[SWITCH_MODE_1] = press_time_[SWITCH_MODE_2] = 0;
    bool linked = !processor_[0].linked();
    for (uint8_t i = 0; i < kNumChannels; ++i) {
      display_mode_[i] = DISPLAY_MODE_FUNCTION;
      processor_[i].set_linked(linked);
    }
    Link(1 - e.control_id);
    SaveState();
    return;
  }
  
  switch (e.control_id) {
    case SWITCH_MONITOR:
      {
        if (display_mode_[0] == DISPLAY_MODE_MONITOR &&
            display_mode_[1] == DISPLAY_MODE_MONITOR) {
          display_mode_[0] = display_mode_[1] = DISPLAY_MODE_MONITOR_FUNCTION;
        } else if (display_mode_[0] == DISPLAY_MODE_MONITOR_FUNCTION &&
                   display_mode_[1] == DISPLAY_MODE_MONITOR_FUNCTION) {
          monitor_mode_ = static_cast<MonitorMode>(monitor_mode_ + 1);
          if (monitor_mode_ == MONITOR_MODE_LAST) {
            monitor_mode_ = static_cast<MonitorMode>(0);
          }
          SaveState();
        } else {
          display_mode_[0] = display_mode_[1] = DISPLAY_MODE_MONITOR;
        }
      }
      break;
      
    default:
      break;
  }
}

void Ui::OnSwitchReleased(const Event& e) {
  if (factory_testing_) {
    return;
  }
  
  // Detect secret handshake for easter egg...
  uint8_t secret_handshake_code = e.control_id;
  secret_handshake_code |= e.data >= kLongPressDuration ? 2 : 0;
  if ((secret_handshake_counter_ & 3) == secret_handshake_code) {
    ++secret_handshake_counter_;
    if (secret_handshake_counter_ == 16) {
      for (uint8_t i = 0; i < kNumChannels; ++i) {
        processor_[i].set_alternate(false);
        processor_[i].set_function(PROCESSOR_FUNCTION_LORENZ_GENERATOR);
      }
      SaveState();
      secret_handshake_counter_ = 0;
      return;
    }
  } else {
    secret_handshake_counter_ = 0;
  }
  
  if (e.data >= kLongPressDuration) {
    // Handle long presses.
    switch (e.control_id) {
      case SWITCH_MONITOR:
        calibrating_ = cv_scaler_->can_calibrate();
        if (calibrating_) {
          cv_scaler_->CaptureAdcOffsets();
          show_offset_level_ = 0;
        }
        break;
        
      case SWITCH_MODE_1:
      case SWITCH_MODE_2:
        {
          processor_[e.control_id].set_alternate(
              !processor_[e.control_id].alternate());
          if (processor_[e.control_id].function() >
              PROCESSOR_FUNCTION_COMPRESSOR) {
            processor_[e.control_id].set_function(PROCESSOR_FUNCTION_ENVELOPE);
          }
          display_mode_[e.control_id] = DISPLAY_MODE_FUNCTION;
          int32_t other = 1 - e.control_id;
          if (display_mode_[other] == DISPLAY_MODE_MONITOR_FUNCTION) {
            display_mode_[other] = DISPLAY_MODE_MONITOR;
          }
          Link(e.control_id);
          SaveState();
        }
        break;
    }
  } else {
    switch (e.control_id) {
      case SWITCH_MODE_1:
      case SWITCH_MODE_2:
        {
          if (display_mode_[e.control_id] == DISPLAY_MODE_FUNCTION) {
            ProcessorFunction index = processor_[e.control_id].function();
            index = static_cast<ProcessorFunction>(index + 1);
            ProcessorFunction limit = processor_[e.control_id].alternate()
                ? PROCESSOR_FUNCTION_FILTER_CONTROLLER
                : PROCESSOR_FUNCTION_COMPRESSOR;
            if (index > limit) {
              index = static_cast<ProcessorFunction>(0);
            }
            processor_[e.control_id].set_function(index);
            SaveState();
          } else {
            display_mode_[e.control_id] = DISPLAY_MODE_FUNCTION;
            int32_t other = 1 - e.control_id;
            if (display_mode_[other] == DISPLAY_MODE_MONITOR_FUNCTION) {
              display_mode_[other] = DISPLAY_MODE_MONITOR;
            }
          }
          Link(e.control_id);
        }
        break;
      
      default:
        break;
    }
  }
}

void Ui::DoEvents() {
  bool refresh = false;
  while (queue_.available()) {
    Event e = queue_.PullEvent();
    if (e.control_type == CONTROL_SWITCH) {
      if (e.data == 0) {
        OnSwitchPressed(e);
      } else {
        OnSwitchReleased(e);
      }
    } else if (e.control_type == CONTROL_POT) {
      OnPotMoved(e);
    }
    refresh = true;
  }
  if (queue_.idle_time() > 1000) {
    queue_.Touch();
    if (display_mode_[0] == DISPLAY_MODE_MONITOR_FUNCTION && 
        display_mode_[1] == DISPLAY_MODE_MONITOR_FUNCTION) {
       display_mode_[0] = display_mode_[1] = DISPLAY_MODE_MONITOR;   
    }
    refresh = true;
  }
  
  // Recompute processor parameters if necessary.
  if (refresh) {
    for (uint8_t i = 0; i < kNumChannels; ++i) {
      processor_[i].Configure();
    }
  }
}

}  // namespace streams
