/*
 *  IRDispatcherDemo.cpp
 *
 *  Receives NEC IR commands and maps them to different actions by means of a mapping array.
 *
 *  Copyright (C) 2020-2021  Armin Joachimsmeyer
 *  armin.joachimsmeyer@gmail.com
 *
 *  This file is part of IRMP https://github.com/IRMP-org/IRMP.
 *  This file is part of Arduino-IRremote https://github.com/Arduino-IRremote/Arduino-IRremote.
 *
 *  IRMP 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 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *  See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program. If not, see <http://www.gnu.org/licenses/gpl.html>.
 *
 */

#include <Arduino.h>

/*
 * Choose the library to be used for IR receiving
 */
#define USE_TINY_IR_RECEIVER // Recommended, but only for NEC protocol!!! If disabled and IRMP_INPUT_PIN is defined, the IRMP library is used for decoding
//#define TINY_RECEIVER_USE_ARDUINO_ATTACH_INTERRUPT // Requires additional 112 bytes program memory + 4 bytes RAM

#include "PinDefinitionsAndMore.h" // Define macros for input and output pin etc.
// Some kind of auto detect library if USE_TINY_IR_RECEIVER is deactivated
#if !defined(USE_TINY_IR_RECEIVER)
#  if defined(IR_RECEIVE_PIN)
#define USE_TINY_IR_RECEIVER
#  elif !defined(USE_IRMP_LIBRARY) && defined(IRMP_INPUT_PIN)
#define USE_IRMP_LIBRARY
#  else
#error No IR library selected
#  endif
#endif

//#define NO_LED_FEEDBACK_CODE // You can set it here, before the include of IRCommandDispatcher below

#if defined(USE_TINY_IR_RECEIVER)
//#define NO_LED_FEEDBACK_CODE   // Activate this if you want to suppress LED feedback or if you do not have a LED. This saves 14 bytes code and 2 clock cycles per interrupt.

#elif defined(USE_IRMP_LIBRARY)
/*
 * IRMP version
 */
#define IR_RECEIVE_PIN    2
#define IRMP_USE_COMPLETE_CALLBACK       1 // Enable callback functionality. It is required if IRMP library is used.
#if defined(ALTERNATIVE_IR_FEEDBACK_LED_PIN)
#define FEEDBACK_LED_PIN    ALTERNATIVE_IR_FEEDBACK_LED_PIN
#endif

//#define IRMP_ENABLE_PIN_CHANGE_INTERRUPT  // Enable interrupt functionality (not for all protocols) - requires around 376 additional bytes of program memory

#define IRMP_PROTOCOL_NAMES 1               // Enable protocol number mapping to protocol strings - requires some program memory. Must before #include <irmp*>

#define IRMP_SUPPORT_NEC_PROTOCOL         1 // this enables only one protocol
//#define IRMP_SUPPORT_KASEIKYO_PROTOCOL    1

#  if defined(ALTERNATIVE_IR_FEEDBACK_LED_PIN)
#define IRMP_FEEDBACK_LED_PIN   ALTERNATIVE_IR_FEEDBACK_LED_PIN
#  endif
/*
 * After setting the definitions we can include the code and compile it.
 */
#include <irmp.hpp>
void handleReceivedIRData();
void irmp_tone(uint8_t _pin, unsigned int frequency, unsigned long duration);
#endif // #if defined(USE_IRMP_LIBRARY)

bool doBlink = false;
uint16_t sBlinkDelay = 200;

void doPrintMenu();
void doLedOn();
void doLedOff();
void doIncreaseBlinkFrequency();
void doDecreaseBlinkFrequency();
void doStop();
void doResetBlinkFrequency();
void doLedBlinkStart();
void doLedBlink20times();
void doTone1800();
void doTone2200();

/*
 * Set definitions and include IRCommandDispatcher library after the declaration of all commands to map
 */
#define INFO // to see some informative output
#include "IRCommandDispatcher.h" // Only for required declarations, the library itself is included below after the definitions of the commands
#include "DemoIRCommandMapping.h" // must be included before IRCommandDispatcher.hpp to define IR_ADDRESS and IRMapping and string "unknown".
#include "IRCommandDispatcher.hpp"

/*
 * Helper macro for getting a macro definition as string
 */
#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)

void setup() {
    pinMode(LED_BUILTIN, OUTPUT);
    Serial.begin(115200);
    while (!Serial)
        ; // Wait for Serial to become available. Is optimized away for some cores.

#if defined(__AVR_ATmega32U4__) || defined(SERIAL_PORT_USBVIRTUAL) || defined(SERIAL_USB) /*stm32duino*/|| defined(USBCON) /*STM32_stm32*/ \
    || defined(SERIALUSB_PID)  || defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_attiny3217)
    delay(4000); // To be able to connect Serial monitor after reset or power up and before first print out. Do not wait for an attached Serial Monitor!
#endif
#if defined(ESP8266)
    Serial.println(); // to separate it from the internal boot output
#endif

    // Just to know which program is running on my Arduino
#if defined(USE_TINY_IR_RECEIVER)
    Serial.println(F("START " __FILE__ " from " __DATE__ "\r\nUsing TinyIRReceiver"));
#elif defined(USE_IRREMOTE_LIBRARY)
    Serial.println(F("START " __FILE__ " from " __DATE__ "\r\nUsing IRremote library version " VERSION_IRREMOTE));
#elif defined(USE_IRMP_LIBRARY)
    Serial.println(F("START " __FILE__ " from " __DATE__ "\r\nUsing IRMP library version " VERSION_IRMP));
#endif

#if !defined(ESP8266) && !defined(NRF5)
    // play feedback tone before setup, since it kills the IR timer settings
    tone(TONE_PIN, 1000, 50);
    delay(50);
#endif

    IRDispatcher.init(); // This just calls irmp_init()
#if defined(USE_TINY_IR_RECEIVER)
    Serial.println(F("Ready to receive NEC IR signals at pin " STR(IR_RECEIVE_PIN)));
#else
    irmp_register_complete_callback_function(&handleReceivedIRData); // fixed function in IRCommandDispatcher.hpp

    Serial.print(F("Ready to receive IR signals of protocols: "));
    irmp_print_active_protocols(&Serial);
    Serial.println(F("at pin " STR(IRMP_INPUT_PIN)));

#  if defined(ALTERNATIVE_IR_FEEDBACK_LED_PIN)
    irmp_irsnd_LEDFeedback(true); // Enable receive signal feedback at ALTERNATIVE_IR_FEEDBACK_LED_PIN
    Serial.println(F("IR feedback pin is " STR(ALTERNATIVE_IR_FEEDBACK_LED_PIN)));
#  endif
#endif

    Serial.print(F("Listening to commands of IR remote of type "));
    Serial.println(IR_REMOTE_NAME);
    doPrintMenu();
}

void loop() {

    IRDispatcher.checkAndRunSuspendedBlockingCommands();

    if (doBlink) {
        digitalWrite(LED_BUILTIN, HIGH);
        DELAY_AND_RETURN_IF_STOP(sBlinkDelay);
        digitalWrite(LED_BUILTIN, LOW);
        DELAY_AND_RETURN_IF_STOP(sBlinkDelay);
    }

    if (millis() - IRDispatcher.IRReceivedData.MillisOfLastCode > 120000) {
        //Short beep as remainder, if we did not receive any command in the last 2 minutes
        IRDispatcher.IRReceivedData.MillisOfLastCode += 120000;
        doTone1800();
    }

//    delay(10);
}

void doPrintMenu() {
    Serial.println();
    Serial.println(F("Press 1 for tone 1800 Hz"));
    Serial.println(F("Press 2 for tone 2200 Hz"));
    Serial.println(F("Press 3 for this Menu"));
    Serial.println(F("Press 0 for LED blink 20 times"));
    Serial.println(F("Press UP for LED on"));
    Serial.println(F("Press DOWN for LED off"));
    Serial.println(F("Press OK for LED blink start"));
    Serial.println(F("Press RIGHT for LED increase blink frequency"));
    Serial.println(F("Press LEFT for LED decrease blink frequency"));
    Serial.println(F("Press STAR for reset blink frequency"));
    Serial.println(F("Press HASH for stop"));
    Serial.println();
}
/*
 * Here the actions that are matched to IR keys
 */
void doLedOn() {
    digitalWrite(LED_BUILTIN, HIGH);
    doBlink = false;
}
void doLedOff() {
    digitalWrite(LED_BUILTIN, LOW);
    doBlink = false;
}
void doIncreaseBlinkFrequency() {
    doBlink = true;
    if (sBlinkDelay > 5) {
        sBlinkDelay -= sBlinkDelay / 4;
    }
}
void doDecreaseBlinkFrequency() {
    doBlink = true;
    sBlinkDelay += sBlinkDelay / 4;
}
void doStop() {
    doBlink = false;
}
void doResetBlinkFrequency() {
    sBlinkDelay = 200;
    digitalWrite(LED_BUILTIN, LOW);
}
void doLedBlinkStart() {
    doBlink = true;
}
/*
 * This is a blocking function and checks periodically for stop
 */
void doLedBlink20times() {
    for (int i = 0; i < 20; ++i) {
        digitalWrite(LED_BUILTIN, HIGH);
        DELAY_AND_RETURN_IF_STOP(200);
        digitalWrite(LED_BUILTIN, LOW);
        DELAY_AND_RETURN_IF_STOP(200);
    }
}

void doTone1800() {
#if defined(USE_IRMP_LIBRARY) && !defined(IRMP_ENABLE_PIN_CHANGE_INTERRUPT)
    irmp_tone(TONE_PIN, 1800, 200);
#else
#  if !defined(ESP8266) && !defined(NRF5) // tone() stops timer 1 for ESP8266
    tone(TONE_PIN, 1800, 200);
#  endif
#endif
}

void doTone2200() {
#if defined(USE_IRMP_LIBRARY) && !defined(IRMP_ENABLE_PIN_CHANGE_INTERRUPT)
    // use IRMP compatible function for tone()
    irmp_tone(TONE_PIN, 2200, 50);
#else
#  if !defined(ESP8266) && !defined(NRF5) // tone() stops timer 1 for ESP8266
    tone(TONE_PIN, 2200, 50);
#  endif
#endif
}

#if defined(USE_IRMP_LIBRARY)
/*
 * Convenience IRMP compatible wrapper function for Arduino tone() if IRMP_ENABLE_PIN_CHANGE_INTERRUPT is NOT activated
 * It currently disables the receiving of repeats
 */
void irmp_tone(uint8_t _pin, unsigned int frequency, unsigned long duration) {
#  if defined(__AVR__) && !defined(IRMP_ENABLE_PIN_CHANGE_INTERRUPT)
    storeIRTimer();
    tone(_pin, frequency, 0);
    if (duration == 0) {
        duration = 100;
    }
    delay(duration);
    noTone(_pin);
    restoreIRTimer();
#elif defined(ESP8266)
    // tone() stops timer 1
    (void)  _pin;
    (void)  frequency;
    (void)  duration;
#else
    tone(_pin, frequency, duration);
#endif
}
#endif // #if defined(USE_IRMP_LIBRARY)
