/* Copyright (c) 2008 Michael Hanselmann
 *
 * This program 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 2
 * of the License, or (at your option) any later version.
 */

#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/wdt.h>
#include <avr/pgmspace.h>
#include <util/delay.h>

#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))

/* Table calculation in Python:
 * gamma = 2.2
 * values = [int(round(4096 * (i / 255.0) ** gamma)) for i in xrange(256)]
 * print ", ".join([str(i) for i in values])
 */

/* Gamma 2.2 */
const uint16_t gamma_2_2[256] PROGMEM = {
  0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 5, 6, 7, 8, 9, 11, 12, 14, 15, 17, 19,
  21, 23, 25, 27, 29, 32, 34, 37, 40, 43, 46, 49, 52, 55, 59, 62, 66, 70, 73,
  77, 82, 86, 90, 95, 99, 104, 109, 114, 119, 124, 129, 135, 140, 146, 152,
  158, 164, 170, 176, 182, 189, 196, 202, 209, 216, 224, 231, 238, 246, 254,
  261, 269, 277, 286, 294, 302, 311, 320, 329, 338, 347, 356, 365, 375, 385,
  394, 404, 414, 424, 435, 445, 456, 467, 477, 489, 500, 511, 522, 534, 546,
  557, 569, 582, 594, 606, 619, 631, 644, 657, 670, 684, 697, 710, 724, 738,
  752, 766, 780, 795, 809, 824, 838, 853, 869, 884, 899, 915, 930, 946, 962,
  978, 994, 1011, 1027, 1044, 1061, 1078, 1095, 1112, 1130, 1147, 1165, 1183,
  1201, 1219, 1238, 1256, 1275, 1293, 1312, 1331, 1351, 1370, 1389, 1409, 1429,
  1449, 1469, 1489, 1510, 1530, 1551, 1572, 1593, 1614, 1636, 1657, 1679, 1700,
  1722, 1745, 1767, 1789, 1812, 1834, 1857, 1880, 1904, 1927, 1950, 1974, 1998,
  2022, 2046, 2070, 2095, 2119, 2144, 2169, 2194, 2219, 2245, 2270, 2296, 2322,
  2348, 2374, 2400, 2427, 2453, 2480, 2507, 2534, 2561, 2589, 2616, 2644, 2672,
  2700, 2728, 2757, 2785, 2814, 2843, 2872, 2901, 2931, 2960, 2990, 3020, 3050,
  3080, 3110, 3141, 3171, 3202, 3233, 3264, 3295, 3327, 3359, 3390, 3422, 3454,
  3487, 3519, 3552, 3585, 3618, 3651, 3684, 3717, 3751, 3785, 3819, 3853, 3887,
  3921, 3956, 3991, 4026, 4061, 4096
};

/* Gamma 0.8 */
const uint16_t gamma_0_8[256] PROGMEM = {
  0, 49, 85, 117, 147, 176, 204, 231, 257, 282, 307, 331, 355, 379, 402, 425,
  447, 469, 491, 513, 535, 556, 577, 598, 618, 639, 659, 680, 700, 720, 739,
  759, 778, 798, 817, 836, 855, 874, 893, 912, 931, 949, 968, 986, 1004, 1023,
  1041, 1059, 1077, 1095, 1113, 1130, 1148, 1166, 1183, 1201, 1218, 1235, 1253,
  1270, 1287, 1304, 1321, 1338, 1355, 1372, 1389, 1406, 1423, 1439, 1456, 1473,
  1489, 1506, 1522, 1539, 1555, 1572, 1588, 1604, 1620, 1636, 1653, 1669, 1685,
  1701, 1717, 1733, 1749, 1765, 1780, 1796, 1812, 1828, 1843, 1859, 1875, 1890,
  1906, 1921, 1937, 1952, 1968, 1983, 1999, 2014, 2029, 2045, 2060, 2075, 2090,
  2106, 2121, 2136, 2151, 2166, 2181, 2196, 2211, 2226, 2241, 2256, 2271, 2286,
  2301, 2316, 2330, 2345, 2360, 2375, 2389, 2404, 2419, 2433, 2448, 2463, 2477,
  2492, 2506, 2521, 2535, 2550, 2564, 2579, 2593, 2607, 2622, 2636, 2651, 2665,
  2679, 2693, 2708, 2722, 2736, 2750, 2765, 2779, 2793, 2807, 2821, 2835, 2849,
  2863, 2877, 2891, 2905, 2919, 2933, 2947, 2961, 2975, 2989, 3003, 3017, 3031,
  3045, 3058, 3072, 3086, 3100, 3114, 3127, 3141, 3155, 3169, 3182, 3196, 3210,
  3223, 3237, 3251, 3264, 3278, 3291, 3305, 3318, 3332, 3345, 3359, 3372, 3386,
  3399, 3413, 3426, 3440, 3453, 3467, 3480, 3493, 3507, 3520, 3533, 3547, 3560,
  3573, 3587, 3600, 3613, 3626, 3640, 3653, 3666, 3679, 3693, 3706, 3719, 3732,
  3745, 3758, 3771, 3785, 3798, 3811, 3824, 3837, 3850, 3863, 3876, 3889, 3902,
  3915, 3928, 3941, 3954, 3967, 3980, 3993, 4006, 4019, 4032, 4045, 4057, 4070,
  4083, 4096
};

volatile uint16_t current_red;
volatile uint16_t current_green;
volatile uint16_t current_blue;

/*
 * Input range: 0-65535
 * Output range: 0-4096
 */
uint16_t approx(const uint16_t *tbl, uint16_t value) {
  uint8_t idx = value >> 8;
  uint16_t first = pgm_read_word(tbl + idx);

  if (idx == 255) {
    /* Last entry */
    return first;
  }

  uint16_t second = pgm_read_word(tbl + idx + 1);
  uint16_t diff = second - first;
  uint8_t value_offset = value & 0xff;

  return first + ((diff * value_offset) >> 8);
}

void hsv_to_rgb(uint16_t h, uint16_t s, uint16_t v)
{
  uint16_t r = 0, g = 0, b = 0;

  if (s == 0) {
    r = g = b = v;
  } else {
    uint32_t i, f;
    uint16_t p, q, t;

    i = (((uint32_t)h) * 6) & 0xFF0000;
    f = ((((uint32_t)h) * 6) - i)>>8;
    i >>= 16;
    p = 65535 - s * 256;
    q = 65535 - s * f;
    t = 65535 - s * (256 - f);

    switch (i) {
    case 0:
      r = v; g = t; b = p; break;
    case 1:
      r = q; g = v; b = p; break;
    case 2:
      r = p; g = v; b = t; break;
    case 3:
      r = p; g = q; b = v; break;
    case 4:
      r = t; g = p; b = v; break;
    case 5:
      r = v; g = p; b = q; break;
    }
  }

  r = approx(gamma_0_8, r);
  g = approx(gamma_2_2, g);
  b = approx(gamma_2_2, b);

  /* Hack: The red chip of my LEDs is very weak, hence correcting the
   * other colours a bit.
   */
  g = (g * 2) >> 3;
  b = (b * 2) >> 3;

  cli();
  current_red = r;
  current_blue = b;
  current_green = g;
  sei();
}

ISR(TIMER0_COMPA_vect)
{
  static uint16_t val = 0;
  static uint8_t cnt = 0;

  wdt_reset();

  if (current_red > 0 && val <= current_red) {
    PORTB &= ~_BV(PB0);
  } else {
    PORTB |= _BV(PB0);
  }

  if (current_green > 0 && val <= current_green) {
    PORTB &= ~_BV(PB1);
  } else {
    PORTB |= _BV(PB1);
  }

  if (current_blue > 0 && val <= current_blue) {
    PORTB &= ~_BV(PB2);
  } else {
    PORTB |= _BV(PB2);
  }

  /* Cheat a bit to get more speed because lower values are more sensible
   * to changes than larger ones.
   */
  if (cnt < 20) {
    ++cnt;
  }
  val += cnt;

  if (val > 4096) {
    cnt = 0;
    val = 0;
  }
}

int main()
{
  wdt_disable();

  current_red = 0;
  current_green = 0;
  current_blue = 0;

  DDRB |= _BV(PB2) | _BV(PB1) | _BV(PB0);
  PORTB |= _BV(PB0) | _BV(PB1) | _BV(PB0);

  DDRB &= ~_BV(PB3);
  PORTB |= _BV(PB3);

  ADMUX = _BV(MUX1);
  ADCSRA = _BV(ADEN);
  DIDR0 = _BV(ADC0D) | _BV(ADC1D) | _BV(ADC2D);
  _delay_ms(1);

  /* Setup timer */
  TCCR0A = _BV(WGM01);
  TCCR0B = _BV(CS01); /* Prescaler clk/8 */
  TCNT0 = 0;
  OCR0A = 4;
  TIMSK = _BV(OCIE0A);

  /* Setup 15ms watchdog */
  wdt_reset();
  wdt_enable(WDTO_15MS);

  sei();

  uint16_t step = 0;
  uint16_t adc_value = 0;
  uint8_t mode = 0;
  uint8_t loop = 0;
  for (;;) {
    if (loop++ == 10) {
      /* Read potentiometer */
      ADCSRA |= _BV(ADSC);
      while (ADCSRA & _BV(ADSC));

      adc_value = ADCL;
      adc_value += (ADCH & 0b11) << 8;

      /* Read switch */
      mode = PINB & _BV(PB3);

      loop = 0;
    }

    if (mode) {
      /* Auto mode with speed selection */
      step += adc_value >> 1;
    } else {
      /* Colour selection mode */
      uint16_t new_step = adc_value << 6;

      /* Change smoothly */
      if (new_step < step) {
        step -= (step - new_step) >> 4;
      } else {
        step += (new_step - step) >> 4;
      }
    }

    hsv_to_rgb(step, 255, 0xffff);
  }
}

/* vim: set sw=2 sts=2 et foldmethod=marker : */

