#include <TimerOne.h>
#include <digitalWriteFast.h>
#include <IRremote.h>

#define TRIAC_ON 0UL
#define TRIAC_OFF 10000UL

const uint8_t XZERO_PIN = 2;
const uint8_t TRIAC_PIN = 3;
const uint8_t IR_PIN = 4;

const uint16_t MAINS_FREQUENCY = 50;
const uint16_t DIMM_STEPS_PER_SECOND = 2 * MAINS_FREQUENCY;
const uint16_t MAX_BRIGHTNESS = 253;
const uint16_t MIN_BRIGHTNESS = 1;

//const uint32_t MAX_BRIGHT_PULSE_DELAY = (-1.5034e-10 * pow(MAX_BRIGHTNESS, 5) + 9.5843e-08 * pow(MAX_BRIGHTNESS, 4) - 2.2953e-05 * pow(MAX_BRIGHTNESS, 3) + 0.0025471 * pow(MAX_BRIGHTNESS, 2) - 0.14965 * MAX_BRIGHTNESS + 9.9846) * 1000UL;
//const uint32_t MIN_BRIGHT_PULSE_DELAY = (-1.5034e-10 * pow(MIN_BRIGHTNESS, 5) + 9.5843e-08 * pow(MIN_BRIGHTNESS, 4) - 2.2953e-05 * pow(MIN_BRIGHTNESS, 3) + 0.0025471 * pow(MIN_BRIGHTNESS, 2) - 0.14965 * MIN_BRIGHTNESS + 9.9846) * 1000UL;
const uint32_t MAX_BRIGHT_PULSE_DELAY = 9900 - (9900 - 200) / 255.0 * MAX_BRIGHTNESS;
const uint32_t MIN_BRIGHT_PULSE_DELAY = 9900 - (9900 - 200) / 255.0 * MIN_BRIGHTNESS;

const float STEP_WIDTH = (MAX_BRIGHTNESS - MIN_BRIGHTNESS + 1) / (float)DIMM_STEPS_PER_SECOND;

uint32_t brightPulseDelay[100];

uint16_t fadeinRampTimeSecs;
uint16_t fadeoutRampTimeSecs;
uint16_t intermissionSecs;

volatile bool zeroCross_flag = false;
volatile bool timerRunning = false;
volatile uint32_t dimmerPulseDelay = TRIAC_OFF;

enum fade_t {
  fadein,
  fadeout
};

enum remote_t {
  ON,
  OFF,
  PLAY,
  STOP
} remoteMode = OFF;

// ----------------------------------------------------------------------------

static void stop_timer() {
  noInterrupts();
  if (timerRunning) {
    Timer1.stop();
    timerRunning = false;
  }
  interrupts();
}

// ----------------------------------------------------------------------------

static void zeroCross_ISR() {
  stop_timer();  // detener timer si esta corriendo
  zeroCross_flag = true;
  if (dimmerPulseDelay <= MAX_BRIGHT_PULSE_DELAY) {   // brillo maximo?
    digitalWriteFast(TRIAC_PIN, HIGH);                // mantener cebado el triac
  } else {                                            // caso contrario
    digitalWriteFast(TRIAC_PIN, LOW);                 // desactivar triac en cruce por cero
    if (dimmerPulseDelay < MIN_BRIGHT_PULSE_DELAY) {  // si brillo mayor a umbral de apagado
      timerRunning = true;
      Timer1.initialize(dimmerPulseDelay);  // setear retrazo del pulso de encendido
    }
  }
}

// ----------------------------------------------------------------------------

static void timer_ISR() {
  digitalWriteFast(TRIAC_PIN, HIGH);
  stop_timer();
}

// ----------------------------------------------------------------------------

void blink_led(bool firstTime = false, uint32_t tOn = 500UL, uint32_t tOff = 500UL) {
  static uint32_t oldTime;
  static uint32_t interval;
  static uint32_t timeOn;
  static uint32_t timeOff;
  static bool ledState = true;
  if (firstTime) {
    oldTime = millis();
    ledState = true;
    timeOn = tOn;
    timeOff = tOff;
    interval = timeOn;
    digitalWriteFast(LED_BUILTIN, ledState);
  } else if (millis() - oldTime >= interval) {
    ledState = !ledState;
    digitalWriteFast(LED_BUILTIN, ledState ? HIGH : LOW);
    interval = ledState ? timeOn : timeOff;
    oldTime += ledState ? timeOff : timeOn;
  }
}

// ----------------------------------------------------------------------------

void delay_with_blink(uint32_t blink_time) {
  uint32_t t0 = millis();
  while (millis() - t0 <= blink_time) {
    blink_led();
//    delay(1);
check_remote();
  }
}

// ----------------------------------------------------------------------------

void long_delay_with_blink(uint16_t blink_time_secs) {
  blink_led(true, 100UL, 900UL);
  //  uint32_t t0 = millis();
  delay_with_blink(blink_time_secs * 1000UL);
  digitalWriteFast(LED_BUILTIN, LOW);
}

// ----------------------------------------------------------------------------

void set_triac_state(uint32_t value) {
  while (!zeroCross_flag) blink_led();
  noInterrupts();
  dimmerPulseDelay = value;  //MIN_DIM_VALUE;
  zeroCross_flag = false;
  interrupts();
}

// ----------------------------------------------------------------------------

void fade(uint32_t fadeRampTime, fade_t direction) {
  blink_led(true, 200UL, 300UL);
  for (int i = 0; i < DIMM_STEPS_PER_SECOND; i++) {
    for (int j = 0; j < fadeRampTime; j++) {
      while (!zeroCross_flag) blink_led();
      noInterrupts();
      if (direction == fadein) {
        dimmerPulseDelay = brightPulseDelay[i];
      } else {
        dimmerPulseDelay = brightPulseDelay[DIMM_STEPS_PER_SECOND - i];
      }
      zeroCross_flag = false;
      interrupts();
    }
  }
  if (direction == fadein) {
    set_triac_state(TRIAC_ON);
  } else {
    set_triac_state(TRIAC_OFF);
  }
}

// ----------------------------------------------------------------------------

bool check_remote(){
  bool temp = false;  
  if (IrReceiver.decode()) {
    Serial.println(IrReceiver.decodedIRData.command, HEX);
    IrReceiver.printIRResultShort(&Serial);
    temp = true;
    switch (IrReceiver.decodedIRData.command) {
      case 0xA2:  if (remoteMode == STOP || remoteMode == ON) {
                    remoteMode = OFF;
                  } else if (remoteMode == OFF) {
                    remoteMode = ON;
                  }
                  Serial.println(remoteMode);
                  break;
      case 0xA8:  if (remoteMode == STOP || remoteMode == ON) {
                    remoteMode = PLAY;
                  } else if (remoteMode == PLAY) {
                    remoteMode = STOP;
                  }
                  break;
//      case 0xE0: remoteMode = STOP; break;
//      case 0x22: remoteMode = OFF; break;
    }
    IrReceiver.resume(); // Enable receiving of the next value
  }
  return temp;
}

// ----------------------------------------------------------------------------

void setup() {
  Serial.begin(115200);
  Serial.println(STEP_WIDTH);
  Serial.println(9900 - (9900 - 200) / 255.0 * 253);
  Serial.println( 9900-(9900 - 200) / 255.0 * 1);
//while(1);
  float bright;
  for (int i = 0; i < DIMM_STEPS_PER_SECOND; i++) {
    bright = i * STEP_WIDTH + MIN_BRIGHTNESS; // * STEP_WIDTH;
//    brightPulseDelay[i] =  (-1.5034e-10 * pow(bright, 5) + 9.5843e-08 * pow(bright, 4) - 2.2953e-05 * pow(bright, 3) + 0.0025471 * pow(bright, 2) - 0.14965 * bright + 9.9846) * 1000UL;
       brightPulseDelay[i] =  9900 - (9900 - 200) / 255.0 * bright;
    Serial.print(i); Serial.print(": \t");
    Serial.println(brightPulseDelay[i]);

  }
//while(1);
  pinModeFast(TRIAC_PIN, OUTPUT);
  digitalWriteFast(TRIAC_PIN, LOW);

  pinModeFast(LED_BUILTIN, OUTPUT);
  digitalWriteFast(LED_BUILTIN, LOW);

  IrReceiver.begin(IR_PIN);

  delay(1000);

  // Cargar valores por serial
  fadeinRampTimeSecs = 5;   // secs
  fadeoutRampTimeSecs = 5;  // secs
  intermissionSecs = 20;    // secs
  intermissionSecs -= (fadeinRampTimeSecs + fadeoutRampTimeSecs);

  while(remoteMode != ON) {
    check_remote();
  }

  Timer1.initialize(30000000);
  Timer1.attachInterrupt(timer_ISR);
  
  attachInterrupt(digitalPinToInterrupt(XZERO_PIN), zeroCross_ISR, RISING);
  stop_timer();

  set_triac_state(TRIAC_OFF);
//  Serial.println("Iniciando...");
  fade(1, fadein);
  long_delay_with_blink(10);
  while(!check_remote());
}

// ----------------------------------------------------------------------------

void loop() {
//  Serial.println("Apagando luces...");
  fade(fadeoutRampTimeSecs, fadeout);
//  Serial.println("Proyectando...");
  long_delay_with_blink(5);  // tiempo proyeccion
//  Serial.println("Encendiendo luces...");
  fade(fadeinRampTimeSecs, fadein);
//  Serial.println("Intervalo...");
  long_delay_with_blink(intermissionSecs);
}

// ----------------------------------------------------------------------------
Loading chip...chip-pwm
Loading chip...chip-scope