#include <math.h>

#include "automation.hpp"

namespace automation {

const uint Automation2040W::ADC_LED_PINS[] = {6, 7, 8};
const uint Automation2040W::RELAY_PINS[] = {9, 10, 11};
const uint Automation2040W::USER_SW_PINS[] = {12, 13};
const uint Automation2040W::USER_LED_PINS[] = {14, 15};
const uint Automation2040W::OUTPUT_PINS[] = {16, 17, 18};
const uint Automation2040W::IN_BUFFERED_PINS[] = {19, 20, 21, 22};
const uint Automation2040W::ADC_PINS[] = {26, 27, 28};


Automation2040W::Automation2040W()
: i2c(I2C_SDA_PIN, I2C_SCL_PIN, 100000)
, analogs{Analog(ADC_PINS[0], VOLTAGE_GAIN, 0.0f, VOLTAGE_OFFSET),
          Analog(ADC_PINS[1], VOLTAGE_GAIN, 0.0f, VOLTAGE_OFFSET),
          Analog(ADC_PINS[2], VOLTAGE_GAIN, 0.0f, VOLTAGE_OFFSET)} {
}

bool Automation2040W::init() {
  // Set up the relay pins
  for(auto i = 0u; i < NUM_RELAYS; i++) {
    gpio_set_function(RELAY_PINS[i], GPIO_FUNC_SIO);
    gpio_set_dir(RELAY_PINS[i], GPIO_OUT);
    release_relay(i);
  }

  // Set up the output pins
  for(auto i = 0u; i < NUM_OUTPUTS; i++) {
    gpio_set_function(OUTPUT_PINS[i], GPIO_FUNC_SIO);
    gpio_set_dir(OUTPUT_PINS[i], GPIO_OUT);
    output(i, false);
  }

  // Set up the input pins
  for(auto i = 0u; i < NUM_INPUTS; i++) {
    gpio_set_function(IN_BUFFERED_PINS[i], GPIO_FUNC_SIO);
    gpio_set_dir(IN_BUFFERED_PINS[i], GPIO_IN);
  }

  // Set up the adc leds
  for(auto i = 0u; i < NUM_ADCS; i++) {
    gpio_put(ADC_LED_PINS[i], false);
    pwm_config cfg = pwm_get_default_config();
    pwm_set_wrap(pwm_gpio_to_slice_num(ADC_LED_PINS[i]), 65535);
    pwm_init(pwm_gpio_to_slice_num(ADC_LED_PINS[i]), &cfg, true);
    gpio_set_function(ADC_LED_PINS[i], GPIO_FUNC_PWM);
    pwm_set_gpio_level(ADC_LED_PINS[i], 0);
  }

  // Set up the user switches and LEDs
  for(auto i = 0u; i < NUM_SWITCHES; i++) {
    gpio_set_function(USER_SW_PINS[i], GPIO_FUNC_SIO);
    gpio_set_dir(USER_SW_PINS[i], GPIO_IN);
    gpio_pull_up(USER_SW_PINS[i]);

    gpio_put(USER_LED_PINS[i], false);
    pwm_config cfg = pwm_get_default_config();
    pwm_set_wrap(pwm_gpio_to_slice_num(USER_LED_PINS[i]), 65535);
    pwm_init(pwm_gpio_to_slice_num(USER_LED_PINS[i]), &cfg, true);
    gpio_set_function(USER_LED_PINS[i], GPIO_FUNC_PWM);
    pwm_set_gpio_level(USER_LED_PINS[i], 0);
  }

  // Set up the connectivity LED
  gpio_put(CONN_LED_PIN, false);
  pwm_config cfg = pwm_get_default_config();
  pwm_set_wrap(pwm_gpio_to_slice_num(CONN_LED_PIN), 65535);
  pwm_init(pwm_gpio_to_slice_num(CONN_LED_PIN), &cfg, true);
  gpio_set_function(CONN_LED_PIN, GPIO_FUNC_PWM);
  pwm_set_gpio_level(CONN_LED_PIN, 0);

  return true;  // We just return true for now, but could expand in the future
}

void Automation2040W::conn_led(bool on) {
  pwm_set_gpio_level(CONN_LED_PIN, on ? 65535 : 0);
}

void Automation2040W::conn_led(float brightness) {
  brightness = CLAMP(brightness, 0.0f, 100.0f);
  const float gamma = 2.8f;
  uint16_t value = (uint16_t)(powf(brightness / 100.0f, gamma) * 65535.0f + 0.5f);
  pwm_set_gpio_level(CONN_LED_PIN, value);
}

bool Automation2040W::switch_pressed(uint sw) {
  assert(sw < NUM_SWITCHES);
  return !gpio_get(USER_SW_PINS[sw]);
}

void Automation2040W::switch_led(uint sw, bool on) {
  assert(sw < NUM_SWITCHES);
  pwm_set_gpio_level(USER_LED_PINS[sw], on ? 65535 : 0);
}

void Automation2040W::switch_led(uint sw, float brightness) {
  assert(sw < NUM_SWITCHES);

  brightness = CLAMP(brightness, 0.0f, 100.0f);
  const float gamma = 2.8f;

  uint16_t value = (uint16_t)(powf(brightness / 100.0f, gamma) * 65535.0f + 0.5f);
  pwm_set_gpio_level(USER_LED_PINS[sw], value);
}

bool Automation2040W::relay(uint relay) {
  assert(relay < NUM_RELAYS);
  return gpio_get(RELAY_PINS[relay]);
}

void Automation2040W::relay(uint relay, bool actuate) {
  assert(relay < NUM_RELAYS);
  gpio_put(RELAY_PINS[relay], actuate);
}

void Automation2040W::actuate_relay(uint relay) {
  assert(relay < NUM_RELAYS);
  gpio_put(RELAY_PINS[relay], true);
}

void Automation2040W::release_relay(uint relay) {
  assert(relay < NUM_RELAYS);
  gpio_put(RELAY_PINS[relay], false);
}

bool Automation2040W::output(uint output) {
  assert(output < NUM_OUTPUTS);
  return gpio_get(OUTPUT_PINS[output]);
}

void Automation2040W::output(uint output, bool value) {
  assert(output < NUM_OUTPUTS);
  gpio_put(OUTPUT_PINS[output], value);
}

bool Automation2040W::read_input(uint input) {
  assert(input < NUM_INPUTS);
  return gpio_get(IN_BUFFERED_PINS[input]);
}

float Automation2040W::read_adc(uint adc) {
  assert(adc < NUM_ADCS);
  float voltage = analogs[adc].read_voltage();
  const float gamma = 2.8f;
  uint16_t value = (uint16_t)(powf(voltage / MAX_ADC_LED_VOLTAGE, gamma) * 65535.0f + 0.5f);
  pwm_set_gpio_level(ADC_LED_PINS[adc], value);
  return voltage;
}

void Automation2040W::reset() {
  // Reset the relays
  for(auto i = 0u; i < NUM_RELAYS; i++) {
    release_relay(i);
  }

  // Reset the outputs
  for(auto i = 0u; i < NUM_OUTPUTS; i++) {
    output(i, false);
  }

  // Reset the adc leds
  for(auto i = 0u; i < NUM_ADCS; i++) {
    pwm_set_gpio_level(ADC_LED_PINS[i], 0);
  }

  // Reset the switch LEDs
  for(auto i = 0u; i < NUM_SWITCHES; i++) {
    pwm_set_gpio_level(USER_LED_PINS[i], 0);
  }

  // Reset the connectivity LED
  pwm_set_gpio_level(CONN_LED_PIN, 0);
}
}
