// Copyright 2015 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 "marbles/ui.h"

#include <algorithm>

#include "stmlib/system/system_clock.h"

#include "marbles/drivers/clock_inputs.h"
#include "marbles/cv_reader.h"
#include "marbles/scale_recorder.h"

namespace marbles {

const int32_t kLongPressDuration = 2000;

using namespace std;
using namespace stmlib;

/* static */
const LedColor Ui::palette_[4] = {
  LED_COLOR_GREEN,
  LED_COLOR_YELLOW,
  LED_COLOR_RED,
  LED_COLOR_OFF
};

/* static */
AlternateKnobMapping Ui::alternate_knob_mappings_[ADC_CHANNEL_LAST];

void Ui::Init(
    Settings* settings,
    CvReader* cv_reader,
    ScaleRecorder* scale_recorder,
    ClockInputs* clock_inputs) {
  settings_ = settings;
  cv_reader_ = cv_reader;
  scale_recorder_ = scale_recorder;
  clock_inputs_ = clock_inputs;
  
  leds_.Init();
  switches_.Init();
  queue_.Init();
  
  // Initialize generator from settings_->state();
  fill(&pot_value_[0], &pot_value_[ADC_CHANNEL_LAST], 0.0f);
  
  State* state = settings_->mutable_state();
  alternate_knob_mappings_[ADC_CHANNEL_T_BIAS].unlock_switch = SWITCH_T_MODEL;
  alternate_knob_mappings_[ADC_CHANNEL_T_BIAS].destination = &state->t_pulse_width_mean;
  alternate_knob_mappings_[ADC_CHANNEL_T_JITTER].unlock_switch = SWITCH_T_MODEL;
  alternate_knob_mappings_[ADC_CHANNEL_T_JITTER].destination = &state->t_pulse_width_std;
  alternate_knob_mappings_[ADC_CHANNEL_T_RATE].unlock_switch = SWITCH_X_MODE;
  alternate_knob_mappings_[ADC_CHANNEL_T_RATE].destination = &state->y_divider;
  alternate_knob_mappings_[ADC_CHANNEL_X_SPREAD].unlock_switch = SWITCH_X_MODE;
  alternate_knob_mappings_[ADC_CHANNEL_X_SPREAD].destination = &state->y_spread;
  alternate_knob_mappings_[ADC_CHANNEL_X_BIAS].unlock_switch = SWITCH_X_MODE;
  alternate_knob_mappings_[ADC_CHANNEL_X_BIAS].destination = &state->y_bias;
  alternate_knob_mappings_[ADC_CHANNEL_X_STEPS].unlock_switch = SWITCH_X_MODE;
  alternate_knob_mappings_[ADC_CHANNEL_X_STEPS].destination = &state->y_steps;
  
  setting_modification_flag_ = false;
  output_test_mode_ = false;
  
  if (switches_.pressed_immediate(SWITCH_X_MODE)) {
    if (state->color_blind == 1) {
      state->color_blind = 0;
    } else {
      state->color_blind = 1; 
    }
    settings_->SaveState();
  }
  
  if (switches_.pressed_immediate(SWITCH_X_DEJA_VU)) {
    settings_->ProgramOptionBytes();
  }
  
  deja_vu_lock_ = false;
}

void Ui::SaveState() {
  settings_->SaveState();
}

void Ui::Poll() {
  // 1kHz.
  system_clock.Tick();
  switches_.Debounce();
  
  for (int i = 0; i < SWITCH_LAST; ++i) {
    if (switches_.just_pressed(Switch(i))) {
      queue_.AddEvent(CONTROL_SWITCH, i, 0);
      press_time_[i] = system_clock.milliseconds();
      ignore_release_[i] = false;
    }
    if (switches_.pressed(Switch(i)) && !ignore_release_[i]) {
      int32_t pressed_time = system_clock.milliseconds() - press_time_[i];
      if (pressed_time > kLongPressDuration && !setting_modification_flag_) {
        queue_.AddEvent(CONTROL_SWITCH, i, pressed_time);
        ignore_release_[i] = true;
      }
    }
    if (switches_.released(Switch(i)) && !ignore_release_[i]) {
      queue_.AddEvent(
          CONTROL_SWITCH,
          i,
          system_clock.milliseconds() - press_time_[i] + 1);
      ignore_release_[i] = true;
    }
  }
  
  UpdateLEDs();
}

/* static */
LedColor Ui::MakeColor(uint8_t value, bool color_blind) {
  bool slow_blink = (system_clock.milliseconds() & 255) > 128;

  uint8_t bank = value >= 3 ? 1 : 0;
  value -= bank * 3;
  
  LedColor color = palette_[value];
  if (color_blind) {
    uint8_t pwm_counter = system_clock.milliseconds() & 15;
    uint8_t triangle = (system_clock.milliseconds() >> 5) & 31;
    triangle = triangle < 16 ? triangle : 31 - triangle;
  
    if (value == 0) {
      color = pwm_counter < (4 + (triangle >> 2))
          ? LED_COLOR_GREEN
          : LED_COLOR_OFF;
    } else if (value == 1) {
      color = LED_COLOR_YELLOW;
    } else {
      color = pwm_counter == 0 ? LED_COLOR_RED : LED_COLOR_OFF;
    }
  }

  return slow_blink || !bank ? color : LED_COLOR_OFF;
}

/* static */
LedColor Ui::DejaVuColor(DejaVuState state, bool lock) {
  if (state == DEJA_VU_OFF) {
    return LED_COLOR_OFF;
  } else if (state == DEJA_VU_ON) {
    if (lock) {
      int slow_triangle = (system_clock.milliseconds() & 1023) >> 5;
      slow_triangle = slow_triangle >= 16 ? 31 - slow_triangle : slow_triangle;
      int pw = system_clock.milliseconds() & 15;
      return slow_triangle >= pw ? LED_COLOR_GREEN : LED_COLOR_OFF;
    } else {
      return LED_COLOR_GREEN;
    }
  } else {
    int fast_triangle = (system_clock.milliseconds() & 511) >> 4;
    fast_triangle = fast_triangle >= 16 ? 31 - fast_triangle : fast_triangle;
    int pw = system_clock.milliseconds() & 15;

    return fast_triangle >= pw ? LED_COLOR_GREEN : LED_COLOR_OFF;
  }
}

void Ui::UpdateLEDs() {
  bool blink = (system_clock.milliseconds() & 127) > 64;
  bool slow_blink = (system_clock.milliseconds() & 255) > 128;
  bool fast_blink = (system_clock.milliseconds() & 63) > 32;
  const State& state = settings_->state();
  bool cb = state.color_blind == 1;
  
  LedColor scale_color = state.x_scale < 3
      ? (slow_blink ? palette_[state.x_scale] : LED_COLOR_OFF)
      : (fast_blink ? palette_[state.x_scale - 3] : LED_COLOR_OFF);
  
  if (cb) {
    int poly_counter = (system_clock.milliseconds() >> 6) % 12;
    if ((poly_counter >> 1) < (state.x_scale + 1) && (poly_counter & 1)) {
      scale_color = LED_COLOR_YELLOW;
    } else {
      scale_color = LED_COLOR_OFF;
    }
  }
  
  leds_.Clear();
  
  switch (mode_) {
    case UI_MODE_NORMAL:
    case UI_MODE_RECORD_SCALE:
      {
        leds_.set(LED_T_MODEL, MakeColor(state.t_model, cb));
        leds_.set(LED_T_RANGE, MakeColor(state.t_range, cb));
        leds_.set(
            LED_T_DEJA_VU,
            DejaVuColor(DejaVuState(state.t_deja_vu), deja_vu_lock_));
        
        leds_.set(LED_X_CONTROL_MODE, MakeColor(state.x_control_mode, cb));
        leds_.set(
            LED_X_DEJA_VU,
            DejaVuColor(DejaVuState(state.x_deja_vu), deja_vu_lock_));
        
        if (mode_ == UI_MODE_NORMAL) {
          leds_.set(LED_X_RANGE,
                    state.x_register_mode
                        ? LED_COLOR_OFF
                        : MakeColor(state.x_range, cb));
          leds_.set(LED_X_EXT,
                    state.x_register_mode ? LED_COLOR_GREEN : LED_COLOR_OFF);
        } else {
          leds_.set(LED_X_RANGE, scale_color);
          leds_.set(LED_X_EXT, LED_COLOR_GREEN);
        }
      }
      break;

    case UI_MODE_SELECT_SCALE:
      leds_.set(LED_X_RANGE, scale_color);
      break;
    
    case UI_MODE_CALIBRATION_1:
      leds_.set(LED_T_RANGE, blink ? MakeColor(0, cb) : LED_COLOR_OFF);
      break;

    case UI_MODE_CALIBRATION_2:
      leds_.set(LED_T_RANGE, blink ? MakeColor(1, cb) : LED_COLOR_OFF);
      break;

    case UI_MODE_CALIBRATION_3:
      leds_.set(LED_X_RANGE, blink ? MakeColor(0, cb) : LED_COLOR_OFF);
      break;

    case UI_MODE_CALIBRATION_4:
      leds_.set(LED_X_RANGE, blink ? MakeColor(1, cb) : LED_COLOR_OFF);
      break;
    
    case UI_MODE_PANIC:
      leds_.set(LED_T_MODEL, blink ? LED_COLOR_RED : LED_COLOR_OFF);
      leds_.set(LED_T_RANGE, !blink ? LED_COLOR_RED : LED_COLOR_OFF);
      leds_.set(LED_X_CONTROL_MODE, !blink ? LED_COLOR_RED : LED_COLOR_OFF);
      leds_.set(LED_X_RANGE, blink ? LED_COLOR_RED : LED_COLOR_OFF);
      break;
  }
  leds_.Write();
}

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

void Ui::OnSwitchPressed(const Event& e) {

}

void Ui::OnSwitchReleased(const Event& e) {
  if (setting_modification_flag_) {
    for (int i = 0; i < ADC_CHANNEL_LAST; ++i) {
      cv_reader_->mutable_channel(i)->UnlockPot();
    }
    setting_modification_flag_ = false;
    return;
  }
  
  // Check if the other switch is still pressed.
  if (e.control_id == SWITCH_T_RANGE && switches_.pressed(SWITCH_X_RANGE)) {
    mode_ = UI_MODE_CALIBRATION_1;
    ignore_release_[SWITCH_T_RANGE] = ignore_release_[SWITCH_X_RANGE] = true;
    return;
  }
  
  State* state = settings_->mutable_state();
  switch (e.control_id) {
    case SWITCH_T_DEJA_VU:
      if (state->t_deja_vu == DEJA_VU_OFF) {
        state->t_deja_vu = e.data >= kLongPressDuration
            ? DEJA_VU_LOCKED
            : DEJA_VU_ON;
      } else if (state->t_deja_vu == DEJA_VU_LOCKED) {
        state->t_deja_vu = DEJA_VU_ON;
      } else {
        state->t_deja_vu = e.data >= kLongPressDuration
            ? DEJA_VU_LOCKED
            : DEJA_VU_OFF;
      }
      break;

    case SWITCH_X_DEJA_VU:
      if (state->x_deja_vu == DEJA_VU_OFF) {
        state->x_deja_vu = e.data >= kLongPressDuration
            ? DEJA_VU_LOCKED
            : DEJA_VU_ON;
      } else if (state->x_deja_vu == DEJA_VU_LOCKED) {
        state->x_deja_vu = DEJA_VU_ON;
      } else {
        state->x_deja_vu = e.data >= kLongPressDuration
            ? DEJA_VU_LOCKED
            : DEJA_VU_OFF;
      }
      break;
    
    case SWITCH_T_MODEL:
      {
        uint8_t bank = state->t_model / 3;
        if (e.data >= kLongPressDuration) {
          if (!bank) {
            state->t_model += 3;
          }
        } else {
          if (bank) {
            state->t_model -= 3;
          } else {
            state->t_model = (state->t_model + 1) % 3;
          }
        }
        SaveState();
      }
      break;

    case SWITCH_T_RANGE:
      {
        if (mode_ >= UI_MODE_CALIBRATION_1 && mode_ <= UI_MODE_CALIBRATION_4) {
          NextCalibrationStep();
        } else {
          state->t_range = (state->t_range + 1) % 3;
        }
        SaveState();
      }
      break;
    
    case SWITCH_X_MODE:
      state->x_control_mode = (state->x_control_mode + 1) % 3;
      SaveState();
      break;
      
    case SWITCH_X_EXT:
      if (mode_ == UI_MODE_RECORD_SCALE) {
        int scale_index = settings_->state().x_scale;
        bool success = true;
        if (e.data >= kLongPressDuration) {
          settings_->ResetScale(scale_index);
        } else {
          success = scale_recorder_->ExtractScale(
              settings_->mutable_scale(scale_index));
        }
        if (success) {
          settings_->SavePersistentData();
          settings_->set_dirty_scale_index(scale_index);
        }
        mode_ = UI_MODE_NORMAL;
      } else if (e.data >= kLongPressDuration) {
        mode_ = UI_MODE_RECORD_SCALE;
        scale_recorder_->Clear();
      } else {
        state->x_register_mode = !state->x_register_mode;
        SaveState();
      }
      break;

    case SWITCH_X_RANGE:
      if (mode_ >= UI_MODE_CALIBRATION_1 && mode_ <= UI_MODE_CALIBRATION_4) {
        NextCalibrationStep();
      } else if (e.data >= kLongPressDuration) {
        if (mode_ == UI_MODE_NORMAL) {
          mode_ = UI_MODE_SELECT_SCALE;
        }
      } else if (mode_ == UI_MODE_SELECT_SCALE) {
        state->x_scale = (state->x_scale + 1) % kNumScales;
      } else {
        if (!state->x_register_mode) {
          state->x_range = (state->x_range + 1) % 3;
        }
      }
      SaveState();
      break;
  }
}

void Ui::TerminateScaleRecording() {
  for (int i = 0; i < ADC_CHANNEL_LAST; ++i) {
    cv_reader_->mutable_channel(i)->UnlockPot();
  }
  mode_ = UI_MODE_NORMAL;
}

void Ui::NextCalibrationStep() {
  switch (mode_) {
    case UI_MODE_CALIBRATION_1:
      cv_reader_->CalibrateOffsets();
      cv_reader_->CalibrateRateC1();
      mode_ = UI_MODE_CALIBRATION_2;
      break;

    case UI_MODE_CALIBRATION_2:
      cv_reader_->CalibrateRateC3();
      mode_ = UI_MODE_CALIBRATION_3;
      break;

    case UI_MODE_CALIBRATION_3:
      cv_reader_->CalibrateSpreadC1();
      mode_ = UI_MODE_CALIBRATION_4;
      break;
    
    case UI_MODE_CALIBRATION_4:
      if (cv_reader_->CalibrateSpreadC3()) {
        settings_->SavePersistentData();
        mode_ = UI_MODE_NORMAL;
      } else {
        mode_ = UI_MODE_PANIC;
      }
      break;
      
    default:
      break;
  }
}

void Ui::UpdateHiddenParameters() {
  // Check if some pots have been moved.
  for (int i = 0; i < ADC_CHANNEL_LAST; ++i) {
    float new_value = cv_reader_->channel(i).unscaled_pot();
    float old_value = pot_value_[i];
    bool changed = fabs(new_value - old_value) >= 0.008f;
    if (changed) {
      pot_value_[i] = new_value;
      AlternateKnobMapping mapping = alternate_knob_mappings_[i];
      if (switches_.pressed(mapping.unlock_switch)) {
        if (mapping.unlock_switch == SWITCH_T_RANGE && new_value < 0.1f) {
          new_value = 0.0f;
        }
        *mapping.destination = static_cast<uint8_t>(new_value * 255.0f);
        cv_reader_->mutable_channel(i)->LockPot();

        // The next time a switch is released, we unlock the pots.
        setting_modification_flag_ = true;
      }
    }
  }
}

void Ui::DoEvents() {
  while (queue_.available()) {
    Event e = queue_.PullEvent();
    if (e.control_type == CONTROL_SWITCH) {
      if (e.data == 0) {
        OnSwitchPressed(e);
      } else {
        OnSwitchReleased(e);
      }
    }
  }
  
  UpdateHiddenParameters();
  
  if (queue_.idle_time() > 800 && mode_ == UI_MODE_PANIC) {
    mode_ = UI_MODE_NORMAL;
  }
  if (mode_ == UI_MODE_SELECT_SCALE) {
    if (queue_.idle_time() > 4000) {
      mode_ = UI_MODE_NORMAL;
      queue_.Touch();
    }
  } else if (queue_.idle_time() > 1000) {
    queue_.Touch();
  }
}

uint8_t Ui::HandleFactoryTestingRequest(uint8_t command) {
  uint8_t argument = command & 0x1f;
  command = command >> 5;
  uint8_t reply = 0;
  switch (command) {
    case FACTORY_TESTING_READ_POT:
    case FACTORY_TESTING_READ_CV:
      reply = cv_reader_->adc_value(argument);
      break;
    
    case FACTORY_TESTING_READ_NORMALIZATION:
      reply = clock_inputs_->is_normalized(ClockInput(argument)) ? 255 : 0;
      break;      
    
    case FACTORY_TESTING_READ_GATE:
      reply = argument >= SWITCH_LAST
          ? clock_inputs_->value(ClockInput(argument - SWITCH_LAST))
          : switches_.pressed(Switch(argument));
      break;
      
    case FACTORY_TESTING_GENERATE_TEST_SIGNALS:
      output_test_mode_ = static_cast<bool>(argument);
      fill(
          &output_test_forced_dac_code_[0],
          &output_test_forced_dac_code_[4],
          0);
      break;
      
    case FACTORY_TESTING_CALIBRATE:
      if (argument == 0) {
        // Revert all settings before getting into calibration mode.
        settings_->mutable_state()->t_deja_vu = 0;
        settings_->mutable_state()->x_deja_vu = 0;
        settings_->mutable_state()->t_model = 0;
        settings_->mutable_state()->t_range = 1;
        settings_->mutable_state()->x_control_mode = 0;
        settings_->mutable_state()->x_range = 2;
        settings_->mutable_state()->x_register_mode = 0;
        settings_->SavePersistentData();
        
        mode_ = UI_MODE_CALIBRATION_1;
      } else {
        NextCalibrationStep();
      }
      {
        const CalibrationData& cal = settings_->calibration_data();
        float voltage = (argument & 1) == 0 ? 1.0f : 3.0f;
        for (int i = 0; i < 4; ++i) {
          output_test_forced_dac_code_[i] =  static_cast<uint16_t>(
              voltage * cal.dac_scale[i] + cal.dac_offset[i]);
        }
      }
      queue_.Touch();
      break;
      
    case FACTORY_TESTING_FORCE_DAC_CODE:
      {
        int channel = argument >> 2;
        int step = argument & 0x3;
        if (step == 0) {
          output_test_forced_dac_code_[channel] = 0xaf35;
        } else if (step == 1) {
          output_test_forced_dac_code_[channel] = 0x1d98;
        } else {
          CalibrationData* cal = settings_->mutable_calibration_data();
          cal->dac_offset[channel] = static_cast<float>(
              calibration_data_ & 0xffff);
          cal->dac_scale[channel] = static_cast<float>(
              calibration_data_ >> 16) * -0.125f;
          output_test_forced_dac_code_[channel] = static_cast<uint16_t>(cal->dac_scale[channel] + cal->dac_offset[channel]);
          settings_->SavePersistentData();
        }
      }
      break;
      
    case FACTORY_TESTING_WRITE_CALIBRATION_DATA_NIBBLE:
      calibration_data_ <<= 4;
      calibration_data_ |= argument & 0xf;
      break;
  }
  return reply;
}

}  // namespace marbles
