/*
  Trains

  by Chris Ritchie - 28.03.2018
*/

#include "horn.h"
#include <stdint.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <EEPROM.h>

#define SAMPLE_RATE 8000

// Mode to turn off train lights when horn button is pressed
#define SWITCH_MODE true

bool turnOnLights = 1;

// Define Arduino Nano pins
const byte redPin = 10;
const byte greenPin = 9;
const byte bluePin = 6;
const byte locomotivePin = 13;
const byte boxcarPin = 8;
const byte caboosePin = 12;
const byte speakerPin = 3;
const byte speakerBttn = 2;

// Delay for rbg led color cycle
const byte cycleDelay = 30;

// Variables for horn sound
volatile uint16_t sample;
byte lastSample;
unsigned long lastHorn;

// Cycle RGB LED colours
class RgbLeds {
    unsigned int red;
    unsigned int green;
    unsigned int blue;

  public:
    void Cycle()
    {
      unsigned int rgbColour[3];
      // Start off with red.
      rgbColour[0] = 255;
      rgbColour[1] = 0;
      rgbColour[2] = 0;

      // Choose the colours to increment and decrement.
      for (int decColour = 0; decColour < 3; decColour += 1) {
        int incColour = decColour == 2 ? 0 : decColour + 1;
        // cross-fade the two colours.
        for (int i = 0; i < 255; i += 1) {
          rgbColour[decColour] -= 1;
          rgbColour[incColour] += 1;
          SetColourRgb(rgbColour[0], rgbColour[1], rgbColour[2]);
          delay(cycleDelay);
        }
      }
    }

    void SetColourRgb(unsigned int red, unsigned int green, unsigned int blue)
    {
      analogWrite(redPin, red);
      analogWrite(greenPin, green);
      analogWrite(bluePin, blue);
    }
};

class Horn {

  public:
    void stopPlayback()
    {
      // Disable playback per-sample interrupt.
      TIMSK1 &= ~_BV(OCIE1A);

      // Disable the per-sample timer completely.
      TCCR1B &= ~_BV(CS10);

      // Disable the PWM timer.
      TCCR2B &= ~_BV(CS10);

      digitalWrite(speakerPin, LOW);
    }


    void startPlayback()
    {
      pinMode(speakerPin, OUTPUT);

      // Set up Timer 2 to do pulse width modulation on the speaker
      // pin.

      // Use internal clock (datasheet p.160)
      ASSR &= ~(_BV(EXCLK) | _BV(AS2));

      // Set fast PWM mode  (p.157)
      TCCR2A |= _BV(WGM21) | _BV(WGM20);
      TCCR2B &= ~_BV(WGM22);

      if (speakerPin == 11) {
        // Do non-inverting PWM on pin OC2A (p.155)
        // On the Arduino this is pin 11.
        TCCR2A = (TCCR2A | _BV(COM2A1)) & ~_BV(COM2A0);
        TCCR2A &= ~(_BV(COM2B1) | _BV(COM2B0));
        // No prescaler (p.158)
        TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);

        // Set initial pulse width to the first sample.
        OCR2A = pgm_read_byte(&sounddata_data[0]);
      } else {
        // Do non-inverting PWM on pin OC2B (p.155)
        // On the Arduino this is pin 3.
        TCCR2A = (TCCR2A | _BV(COM2B1)) & ~_BV(COM2B0);
        TCCR2A &= ~(_BV(COM2A1) | _BV(COM2A0));
        // No prescaler (p.158)
        TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);

        // Set initial pulse width to the first sample.
        OCR2B = pgm_read_byte(&sounddata_data[0]);
      }

      // Set up Timer 1 to send a sample every interrupt.

      cli();

      // Set CTC mode (Clear Timer on Compare Match) (p.133)
      // Have to set OCR1A *after*, otherwise it gets reset to 0!
      TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12);
      TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10));

      // No prescaler (p.134)
      TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);

      // Set the compare register (OCR1A).
      // OCR1A is a 16-bit register, so we have to do this with
      // interrupts disabled to be safe.
      OCR1A = F_CPU / SAMPLE_RATE;    // 16e6 / 8000 = 2000

      // Enable interrupt when TCNT1 == OCR1A (p.136)
      TIMSK1 |= _BV(OCIE1A);

      lastSample = pgm_read_byte(&sounddata_data[sounddata_length - 1]);
      sample = 0;
      sei();
    }

};

RgbLeds rgb;
Horn horn;

void setup() {
  //Serial.begin(115200);

  // Prevent speaker from making faint humming noise after playing horn
  digitalWrite(speakerPin, LOW);
  pinMode(speakerPin, INPUT);

  // If SWITCH_MODE is true read setting from memory
#if SWITCH_MODE == true
  turnOnLights = EEPROM.read(0);
#endif
  // Serial.println(value);
  if (turnOnLights == 0)
  {
    digitalWrite(locomotivePin, LOW);
    analogWrite(boxcarPin, 0);
    analogWrite(caboosePin, 0);
  }
  else
    trainLights();

  // Start off with the LED off.
  rgb.SetColourRgb(0, 0, 0);

  pinMode(speakerBttn, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(speakerBttn), PlayHorn, FALLING);
}

void PlayHorn()
{
  // If SWITCH_MODE is true save setting to memory
#if SWITCH_MODE == true
  if (turnOnLights == 1)
    EEPROM.write(0, 0);
  else
    EEPROM.write(0, 1);
#endif
  unsigned long currMillis = millis();
  if (currMillis - lastHorn > 900) {
    rgb.SetColourRgb(255, 0, 0); // Set LED colour to prevent them from turning off when playing horn
    lastHorn = currMillis;
    horn.startPlayback();
    delay(400);
    horn.startPlayback();
    delay(1000);
    asm volatile ("  jmp 0"); // Reset Arduino
  }
}

void trainLights()
{
  // Turn on LED's on trains
  digitalWrite(locomotivePin, HIGH);
  delay(500);
  analogWrite(boxcarPin, 255);
  delay(500);
  analogWrite(caboosePin, 255);
  delay(500);
}

void loop() {
  rgb.Cycle();
}


// This is called at 8000 Hz to load the next sample.
ISR(TIMER1_COMPA_vect) {
  if (sample >= sounddata_length) {
    if (sample == sounddata_length + lastSample) {
      horn.stopPlayback();
    }
    else {
      if (speakerPin == 11) {
        // Ramp down to zero to reduce the click at the end of playback.
        OCR2A = sounddata_length + lastSample - sample;
      } else {
        OCR2B = sounddata_length + lastSample - sample;
      }
    }
  }
  else {
    if (speakerPin == 11) {
      OCR2A = pgm_read_byte(&sounddata_data[sample]);
    } else {
      OCR2B = pgm_read_byte(&sounddata_data[sample]);
    }
  }

  ++sample;
}