#include "UI.h"

#include "cpuload.h"
#include "debug.h"
#include "log.h"

#include <algorithm>
#include <iostream>
#include <string>

#include <fmt/color.h>
#include <fmt/format.h>

#include <stdio.h>
#include <sys/poll.h>

const int midi_monitor_max_size = 3;

using namespace std::chrono_literals;

static std::vector<std::string> mode_names{
  "NoteClick", "Clock", "Internal"
};

static std::vector<std::string> output_names{
  "Off", "On"
};

static std::vector<std::string> active_sensing_names{
  "Not Detected", "Detected"
};

IntervalCounter::IntervalCounter()
{
}

int IntervalCounter::get_count_per_second()
{
  // calculate result
  std::chrono::time_point<clock_type> now = clock_type::now();

  uint64_t diff_ms = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_checkpoint_timestamp).count();
  uint64_t count_per_second = (diff_ms == 0 || m_count_checkpoint == 0) ? 0 : ((m_count - m_count_checkpoint) * 1000 / diff_ms);

  // update state
  m_checkpoint_timestamp = now;
  m_count_checkpoint = m_count;

  return count_per_second;
}

void IntervalCounter::count()
{
  m_count++;
}

UI::UI(Config& config):
  m_config(config),
  m_main_loops{},
  m_midi_events{},
  m_active_sensing_timestamp{},
  m_midi_timestamp{},
  m_note_bpm{},
  m_clock_bpm{}
{
}

bool UI::key_available() {
  struct pollfd fds{};
  int ret;
  fds.fd = 0; // stdin
  fds.events = POLLIN;
  ret = poll(&fds, 1, 0);
  if (ret == 0)
    return false;
  else if (ret == 1)
    return true;
  else
    log_cout << "Error" << std::endl;
  return false;
}

int read_key() {
  char buf[1];
  int got = read(0, buf, 1);
  if (got != 1) {
    log_cout << "Bad input size: " << std::to_string(got) << std::endl;
  }

  return buf[0];
}

void UI::draw()
{
  std::vector<int> cpuloads = get_cpu_loads();
  int main_loops_per_second = m_main_loops.get_count_per_second();
  int midi_events_per_second = m_midi_events.get_count_per_second();

  int mode = m_config.get_mode();
  int channel = m_config.get_midi_channel();
  int note = m_config.get_midi_note();
  int internal_bpm = m_config.get_bpm();
  int bpm;
  if (mode == 0) {
    bpm = m_note_bpm;
  } else if (mode == 1) {
    bpm = m_clock_bpm;
  } else if (mode == 2) {
    bpm = m_config.get_bpm();
  }

  int output = m_config.get_output();

  int active_sensing_detected = (clock_type::now() - m_active_sensing_timestamp) < 2s ? 1 : 0;

  fmt::text_style editable_color{fg(fmt::color::crimson) | fmt::emphasis::bold};
  fmt::text_style value_color{fg(fmt::color::blue) | fmt::emphasis::bold};

  // clear screen
  std::cout << "\x1B[2J\x1B[H";
  //std::cout << std::endl;

  std::cout << "- " << fmt::format(editable_color, "{:3}", bpm) << " BPM +" << std::endl;
  std::cout << "Mode: ";
  for (int i = 0; i < mode_names.size(); ++i) {
    if (i == mode) {
      std::cout << fmt::format(editable_color, " {}", mode_names[i]);
    } else {
      std::cout << fmt::format(" {}", mode_names[i]);
    }
  }
  std::cout << std::endl;
  std::cout << "MIDI Note: " << fmt::format(editable_color, "{}/{}", channel, note);
  std::cout << std::endl;
  std::cout << "Audio Output: " << fmt::format(editable_color, "{}", output_names[output]);
  std::cout << std::endl;
  std::cout << std::endl;
  std::cout << "Status:" << std::endl;

  std::cout << "  CPU:";
  for (auto& i: cpuloads) {
    std::cout << fmt::format(" {:2}%", i);
  }
  int max = *std::max_element(cpuloads.begin(), cpuloads.end());
  std::cout << fmt::format(", max. {:2}%", max) << std::endl;

  std::cout << "  MIDI Notes: ";
  if (m_midi_monitor.empty()) {
    std::cout << "--";
  } else {
    int count{};
    for (const auto& i: m_midi_monitor) {
      if (count == 0) {
        std::cout << fmt::format(editable_color, " {}/{}", i.first, i.second);
      } else {
        std::cout << fmt::format(" {}/{}", i.first, i.second);
      }
      ++count;
    }
    if (count >= midi_monitor_max_size) {
      std::cout << " ...";
    }
  }
  std::cout << std::endl;
  std::cout << fmt::format("  MIDI Timestamp: {}", m_midi_timestamp) << std::endl;
  std::cout << "  MIDI Active Sensing: " << fmt::format(value_color, "{}", active_sensing_names[active_sensing_detected]) << std::endl;
  std::cout << fmt::format("  MIDI Events/s: {}", midi_events_per_second) << std::endl;
  std::cout << fmt::format("  MIDI Clock: {:3} BPM", m_clock_bpm) << std::endl;
  std::cout << fmt::format("  MIDI Click: {:3} BPM", m_note_bpm) << std::endl;
  std::cout << fmt::format("  Internal:   {:3} BPM", internal_bpm) << std::endl;

  std::cout << fmt::format("  Main loops/s: {}", main_loops_per_second) << std::endl;

  std::cout << std::endl;
  std::cout << "Log:" << std::endl;
  std::cout << log_cout.get_log() << std::endl;
}

void UI::handle_input()
{
  if (key_available()) {
    char c;
    std::cin >> c;

    debug_cout << "Key: " << std::to_string(c) << std::endl;

    if (c == '+') {
      if (m_config.get_bpm() < 240) {
        m_config.set_bpm(m_config.get_bpm() + 1);
      }
    } else if (c == '-') {
      if (m_config.get_bpm() > 10) {
        m_config.set_bpm(m_config.get_bpm() - 1);
      }
    } else if (c == 'm') {
      m_config.set_mode((m_config.get_mode() + 1) % 3);
    } else if (c == 'n') {
      if (!m_midi_monitor.empty()) {
        m_config.set_midi_channel(m_midi_monitor.front().first);
        m_config.set_midi_note(m_midi_monitor.front().second);
      }
    } else if (c == 'o') {
      m_config.set_output(1 - m_config.get_output());
    } else {
      log_cout << fmt::format("Unknown key: {}", c) << std::endl;
    }
  }
}

void UI::count_main_loops()
{
  m_main_loops.count();
}

void UI::count_midi_events()
{
  m_midi_events.count();
}

void UI::slot_active_sensing()
{
  m_active_sensing_timestamp = clock_type::now();
}

void UI::slot_midi_note(int channel, int note, uint64_t timestamp)
{
  m_midi_timestamp = timestamp;

  m_midi_monitor.emplace_front(channel, note);

  while (m_midi_monitor.size() > midi_monitor_max_size) {
    m_midi_monitor.pop_back();
  }
}

void UI::slot_note_bpm(int bpm)
{
  m_note_bpm = bpm;
}

void UI::slot_clock_bpm(int bpm)
{
  m_clock_bpm = bpm;
}