/*
 * IRReceive.hpp
 * This file is exclusively included by IRremote.h to enable easy configuration of library switches
 *
 *  Contains all IRrecv class functions as well as other receiver related functions.
 *
 *  This file is part of Arduino-IRremote https://github.com/Arduino-IRremote/Arduino-IRremote.
 *
 ************************************************************************************
 * MIT License
 *
 * Copyright (c) 2009-2023 Ken Shirriff, Rafi Khan, Armin Joachimsmeyer
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is furnished
 * to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 ************************************************************************************
 */
#ifndef _IR_RECEIVE_HPP
#define _IR_RECEIVE_HPP

#if defined(DEBUG) && !defined(LOCAL_DEBUG)
//#define LOCAL_DEBUG //
#else
//#define LOCAL_DEBUG // This enables debug output only for this file
#endif

#if defined(TRACE) && !defined(LOCAL_TRACE)
#define LOCAL_TRACE
#else
//#define LOCAL_TRACE // This enables debug output only for this file
#endif
/*
 * Low level hardware timing measurement
 */
//#define _IR_MEASURE_TIMING // for ISR
//#define _IR_TIMING_TEST_PIN 7 // "pinModeFast(_IR_TIMING_TEST_PIN, OUTPUT);" is executed at start()
//
/*
 * Check for additional required characteristics of timing like length of mark for a constant mark protocol,
 * where space length determines the bit value. Requires up to 194 additional bytes of program memory.
 */
//#define DECODE_STRICT_CHECKS
/** \addtogroup Receiving Receiving IR data for multiple protocols
 * @{
 */
/**
 * The receiver instance
 */
IRrecv IrReceiver;

/*
 * The control structure instance
 */
struct irparams_struct irparams; // the irparams instance

/**
 * Instantiate the IRrecv class. Multiple instantiation is not supported.
 * @param IRReceivePin Arduino pin to use. No sanity check is made.
 */
IRrecv::IRrecv() {
    decodedIRData.rawDataPtr = &irparams; // for decodePulseDistanceData() etc.
    setReceivePin(0);
#if !defined(NO_LED_FEEDBACK_CODE)
    setLEDFeedback(0, DO_NOT_ENABLE_LED_FEEDBACK);
#endif
}

IRrecv::IRrecv(uint_fast8_t aReceivePin) {
    decodedIRData.rawDataPtr = &irparams; // for decodePulseDistanceData() etc.
    setReceivePin(aReceivePin);
#if !defined(NO_LED_FEEDBACK_CODE)
    setLEDFeedback(0, DO_NOT_ENABLE_LED_FEEDBACK);
#endif
}

/**
 * Instantiate the IRrecv class. Multiple instantiation is not supported.
 * @param aReceivePin Arduino pin to use, where a demodulating IR receiver is connected.
 * @param aFeedbackLEDPin if 0, then take board specific FEEDBACK_LED_ON() and FEEDBACK_LED_OFF() functions
 */
IRrecv::IRrecv(uint_fast8_t aReceivePin, uint_fast8_t aFeedbackLEDPin) {
    decodedIRData.rawDataPtr = &irparams; // for decodePulseDistanceData() etc.
    setReceivePin(aReceivePin);
#if !defined(NO_LED_FEEDBACK_CODE)
    setLEDFeedback(aFeedbackLEDPin, DO_NOT_ENABLE_LED_FEEDBACK);
#else
    (void) aFeedbackLEDPin;
#endif
}

/**********************************************************************************************************************
 * Interrupt Service Routine - Called every 50 us
 *
 * Duration in ticks of 50 us of alternating SPACE, MARK are recorded in irparams.rawbuf array.
 * 'rawlen' counts the number of entries recorded so far.
 * First entry is the SPACE between transmissions.
 *
 * As soon as one SPACE entry gets longer than RECORD_GAP_TICKS, state switches to STOP (frame received). Timing of SPACE continues.
 * A call of resume() switches from STOP to IDLE.
 * As soon as first MARK arrives in IDLE, gap width is recorded and new logging starts.
 *
 * With digitalRead and Feedback LED
 * 15 pushs, 1 in, 1 eor before start of code = 2 us @16MHz + * 7.2 us computation time (6us idle time) + * pop + reti = 2.25 us @16MHz => 10.3 to 11.5 us @16MHz
 * With portInputRegister and mask and Feedback LED code commented
 * 9 pushs, 1 in, 1 eor before start of code = 1.25 us @16MHz + * 2.25 us computation time + * pop + reti = 1.5 us @16MHz => 5 us @16MHz
 * => Minimal CPU frequency is 4 MHz
 *
 **********************************************************************************************************************/
#if defined(ESP8266) || defined(ESP32)
IRAM_ATTR
#endif
void IRReceiveTimerInterruptHandler() {
#if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN)
    digitalWriteFast(_IR_TIMING_TEST_PIN, HIGH); // 2 clock cycles
#endif
// 7 - 8.5 us for ISR body (without pushes and pops) for ATmega328 @16MHz

#if defined(TIMER_REQUIRES_RESET_INTR_PENDING)
    timerResetInterruptPending(); // reset TickCounterForISR interrupt flag if required (currently only for Teensy and ATmega4809)
#endif

// Read if IR Receiver -> SPACE [xmt LED off] or a MARK [xmt LED on]
#if defined(__AVR__)
    uint8_t tIRInputLevel = *irparams.IRReceivePinPortInputRegister & irparams.IRReceivePinMask;
#else
    uint_fast8_t tIRInputLevel = (uint_fast8_t) digitalReadFast(irparams.IRReceivePin);
#endif

    /*
     * Increase TickCounter and clip it at maximum 0xFFFF / 3.2 seconds at 50 us ticks
     */
    if (irparams.TickCounterForISR < UINT16_MAX) {
        irparams.TickCounterForISR++;  // One more 50uS tick
    }

    /*
     * Due to a ESP32 compiler bug https://github.com/espressif/esp-idf/issues/1552 no switch statements are possible for ESP32
     * So we change the code to if / else if
     */
//    switch (irparams.StateForISR) {
//
    if (irparams.StateForISR == IR_REC_STATE_IDLE) {
        /*
         * Here we are just resumed and maybe in the middle of a transmission
         */
        if (tIRInputLevel == INPUT_MARK) {
            // check if we did not start in the middle of a transmission by checking the minimum length of leading space
            if (irparams.TickCounterForISR > RECORD_GAP_TICKS) {
#if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN)
//                digitalWriteFast(_IR_TIMING_TEST_PIN, HIGH); // 2 clock cycles
#endif
                /*
                 * Gap between two transmissions just ended; Record gap duration + start recording transmission
                 * Initialize all state machine variables
                 */
                irparams.OverflowFlag = false;
                irparams.rawbuf[0] = irparams.TickCounterForISR;
                irparams.rawlen = 1;
                irparams.StateForISR = IR_REC_STATE_MARK;
            } // otherwise stay in idle state
            irparams.TickCounterForISR = 0; // reset counter in both cases
        }

    } else if (irparams.StateForISR == IR_REC_STATE_MARK) {  // Timing mark
        if (tIRInputLevel != INPUT_MARK) {
            /*
             * Mark ended here. Record mark time in rawbuf array
             */
#if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN)
//            digitalWriteFast(_IR_TIMING_TEST_PIN, HIGH); // 2 clock cycles
#endif
            irparams.rawbuf[irparams.rawlen++] = irparams.TickCounterForISR; // record mark
            irparams.StateForISR = IR_REC_STATE_SPACE;
            irparams.TickCounterForISR = 0; // This resets the tick counter also at end of frame :-)
        }

    } else if (irparams.StateForISR == IR_REC_STATE_SPACE) {  // Timing space
        if (tIRInputLevel == INPUT_MARK) {
            /*
             * Space ended here. Check for overflow and record space time in rawbuf array
             */
            if (irparams.rawlen >= RAW_BUFFER_LENGTH) {
                // Flag up a read OverflowFlag; Stop the state machine
                irparams.OverflowFlag = true;
                irparams.StateForISR = IR_REC_STATE_STOP;
#if !defined(IR_REMOTE_DISABLE_RECEIVE_COMPLETE_CALLBACK)
                /*
                 * Call callback if registered (not NULL)
                 */
                if (irparams.ReceiveCompleteCallbackFunction != NULL) {
                    irparams.ReceiveCompleteCallbackFunction();
                }
#endif
            } else {
#if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN)
//                digitalWriteFast(_IR_TIMING_TEST_PIN, HIGH); // 2 clock cycles
#endif
                irparams.rawbuf[irparams.rawlen++] = irparams.TickCounterForISR; // record space
                irparams.StateForISR = IR_REC_STATE_MARK;
            }
            irparams.TickCounterForISR = 0;

        } else if (irparams.TickCounterForISR > RECORD_GAP_TICKS) {
            /*
             * Maximum space duration reached here.
             * Current code is ready for processing!
             * We received a long space, which indicates gap between codes.
             * Switch to IR_REC_STATE_STOP
             * Don't reset TickCounterForISR; keep counting width of next leading space
             */
            irparams.StateForISR = IR_REC_STATE_STOP;
#if !defined(IR_REMOTE_DISABLE_RECEIVE_COMPLETE_CALLBACK)
            /*
             * Call callback if registered (not NULL)
             */
            if (irparams.ReceiveCompleteCallbackFunction != NULL) {
                irparams.ReceiveCompleteCallbackFunction();
            }
#endif
        }
    } else if (irparams.StateForISR == IR_REC_STATE_STOP) {
        /*
         * Complete command received
         * stay here until resume() is called, which switches state to IR_REC_STATE_IDLE
         */
#if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN)
//        digitalWriteFast(_IR_TIMING_TEST_PIN, HIGH); // 2 clock cycles
#endif
        if (tIRInputLevel == INPUT_MARK) {
            // Reset gap TickCounterForISR, to prepare for detection if we are in the middle of a transmission after call of resume()
            irparams.TickCounterForISR = 0;
        }
    }

#if !defined(NO_LED_FEEDBACK_CODE)
    if (FeedbackLEDControl.LedFeedbackEnabled == LED_FEEDBACK_ENABLED_FOR_RECEIVE) {
        setFeedbackLED(tIRInputLevel == INPUT_MARK);
    }
#endif

#ifdef _IR_MEASURE_TIMING
    digitalWriteFast(_IR_TIMING_TEST_PIN, LOW); // 2 clock cycles
#endif

}

/*
 * The ISR, which calls the interrupt handler
 */
#if defined(TIMER_INTR_NAME) || defined(ISR)
#  if defined(TIMER_INTR_NAME)
ISR (TIMER_INTR_NAME) // for ISR definitions
#  elif defined(ISR)
ISR()
// for functions definitions which are called by separate (board specific) ISR
#  endif
{
    IRReceiveTimerInterruptHandler();
}
#endif

/**********************************************************************************************************************
 * Stream like API
 **********************************************************************************************************************/
/**
 * Initializes the receive and feedback pin
 * @param aReceivePin The Arduino pin number, where a demodulating IR receiver is connected.
 * @param aEnableLEDFeedback if true / ENABLE_LED_FEEDBACK, then let the feedback led blink on receiving IR signal
 * @param aFeedbackLEDPin if 0 / USE_DEFAULT_FEEDBACK_LED_PIN, then take board specific FEEDBACK_LED_ON() and FEEDBACK_LED_OFF() functions
 */
void IRrecv::begin(uint_fast8_t aReceivePin, bool aEnableLEDFeedback, uint_fast8_t aFeedbackLEDPin) {

    setReceivePin(aReceivePin);
#if !defined(NO_LED_FEEDBACK_CODE)
    uint_fast8_t tEnableLEDFeedback = DO_NOT_ENABLE_LED_FEEDBACK;
    if (aEnableLEDFeedback) {
        tEnableLEDFeedback = LED_FEEDBACK_ENABLED_FOR_RECEIVE;
    }
    setLEDFeedback(aFeedbackLEDPin, tEnableLEDFeedback);
#else
    (void) aEnableLEDFeedback;
    (void) aFeedbackLEDPin;
#endif

#if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN)
    pinModeFast(_IR_TIMING_TEST_PIN, OUTPUT);
#endif
    start();
}

/**
 * Sets / changes the receiver pin number
 */
void IRrecv::setReceivePin(uint_fast8_t aReceivePinNumber) {
    irparams.IRReceivePin = aReceivePinNumber;
#if defined(__AVR__)
#  if defined(__digitalPinToBit)
    if (__builtin_constant_p(aReceivePinNumber)) {
        irparams.IRReceivePinMask = 1UL << (__digitalPinToBit(aReceivePinNumber));
    } else {
        irparams.IRReceivePinMask = digitalPinToBitMask(aReceivePinNumber); // requires 10 bytes PGM, even if not referenced (?because it is assembler code?)
    }
#  else
    irparams.IRReceivePinMask = digitalPinToBitMask(aReceivePinNumber); // requires 10 bytes PGM, even if not referenced (?because it is assembler code?)
#  endif
#  if defined(__digitalPinToPINReg)
    /*
     * This code is 54 bytes smaller, if aReceivePinNumber is a constant :-), but 38 byte longer if it is not constant (,which is not likely).
     */
    if (__builtin_constant_p(aReceivePinNumber)) {
        irparams.IRReceivePinPortInputRegister = __digitalPinToPINReg(aReceivePinNumber);
    } else {
        irparams.IRReceivePinPortInputRegister = portInputRegister(digitalPinToPort(aReceivePinNumber)); // requires 44 bytes PGM, even if not referenced
    }
#  else
    irparams.IRReceivePinPortInputRegister = portInputRegister(digitalPinToPort(aReceivePinNumber)); // requires 44 bytes PGM, even if not referenced
#  endif
#endif
    // Set pin mode once. pinModeFast makes no difference if used, but saves 224 if not referenced :-(
    pinModeFast(aReceivePinNumber, INPUT); // Seems to be at least required by ESP32
}

#if !defined(IR_REMOTE_DISABLE_RECEIVE_COMPLETE_CALLBACK)
/**
 * Sets the function to call if a protocol message has arrived
 */
void IRrecv::registerReceiveCompleteCallback(void (*aReceiveCompleteCallbackFunction)(void)) {
    irparams.ReceiveCompleteCallbackFunction = aReceiveCompleteCallbackFunction;
}
#endif

/**
 * Start the receiving process.
 * This configures the timer and the state machine for IR reception
 * and enables the receive sample timer interrupt which consumes a small amount of CPU every 50 us.
 */
void IRrecv::start() {

    // Setup for cyclic 50 us interrupt
    timerConfigForReceive(); // no interrupts enabled here!

    // Initialize state machine state
    resume();

    // Timer interrupt is enabled after state machine reset
    timerEnableReceiveInterrupt(); // Enables the receive sample timer interrupt which consumes a small amount of CPU every 50 us.
#ifdef _IR_MEASURE_TIMING
    pinModeFast(_IR_TIMING_TEST_PIN, OUTPUT);
#endif
}

/*
 * Do not resume() reading of IR data
 */
void IRrecv::restartTimer() {
    // Setup for cyclic 50 us interrupt
    timerConfigForReceive(); // no interrupts enabled here!
    // Timer interrupt is enabled after state machine reset
    timerEnableReceiveInterrupt(); // Enables the receive sample timer interrupt which consumes a small amount of CPU every 50 us.
#ifdef _IR_MEASURE_TIMING
    pinModeFast(_IR_TIMING_TEST_PIN, OUTPUT);
#endif
}
/**
 * Alias for start().
 */
void IRrecv::enableIRIn() {
    start();
}

/**
 * Configures the timer and the state machine for IR reception.
 * The tick counter value is already at 100 when decode() gets true, because of the 5000 us minimal gap defined in RECORD_GAP_MICROS.
 * @param aMicrosecondsToAddToGapCounter To compensate for the amount of microseconds the timer was stopped / disabled.
 */
void IRrecv::start(uint32_t aMicrosecondsToAddToGapCounter) {
    irparams.TickCounterForISR += aMicrosecondsToAddToGapCounter / MICROS_PER_TICK;
    start();
}
void IRrecv::restartTimer(uint32_t aMicrosecondsToAddToGapCounter) {
    irparams.TickCounterForISR += aMicrosecondsToAddToGapCounter / MICROS_PER_TICK;
    restartTimer();
}
void IRrecv::startWithTicksToAdd(uint16_t aTicksToAddToGapCounter) {
    irparams.TickCounterForISR += aTicksToAddToGapCounter;
    start();
}
void IRrecv::restartTimerWithTicksToAdd(uint16_t aTicksToAddToGapCounter) {
    irparams.TickCounterForISR += aTicksToAddToGapCounter;
    restartTimer();
}

void IRrecv::addTicksToInternalTickCounter(uint16_t aTicksToAddToInternalTickCounter) {
    irparams.TickCounterForISR += aTicksToAddToInternalTickCounter;
}

void IRrecv::addMicrosToInternalTickCounter(uint16_t aMicrosecondsToAddToInternalTickCounter) {
    irparams.TickCounterForISR += aMicrosecondsToAddToInternalTickCounter / MICROS_PER_TICK;
}
/**
 * Restarts receiver after send. Is a NOP if sending does not require a timer.
 */
void IRrecv::restartAfterSend() {
#if defined(SEND_PWM_BY_TIMER) && !defined(SEND_PWM_DOES_NOT_USE_RECEIVE_TIMER)
    start();
#endif
}

/**
 * Disables the timer for IR reception.
 */
void IRrecv::stop() {
    timerDisableReceiveInterrupt();
}

void IRrecv::stopTimer() {
    timerDisableReceiveInterrupt();
}
/**
 * Alias for stop().
 */
void IRrecv::disableIRIn() {
    stop();
}
/**
 * Alias for stop().
 */
void IRrecv::end() {
    stop();
}

/**
 * Returns status of reception
 * @return true if no reception is on-going.
 */
bool IRrecv::isIdle() {
    return (irparams.StateForISR == IR_REC_STATE_IDLE || irparams.StateForISR == IR_REC_STATE_STOP) ? true : false;
}

/**
 * Restart the ISR (Interrupt Service Routine) state machine, to enable receiving of the next IR frame.
 * Internal counting of gap timing is independent of StateForISR and therefore independent of call time of resume().
 */
void IRrecv::resume() {
    // This check allows to call resume at arbitrary places or more than once
    if (irparams.StateForISR == IR_REC_STATE_STOP) {
        irparams.StateForISR = IR_REC_STATE_IDLE;
    }
}

/**
 * Is internally called by decode before calling decoders.
 * Must be used to setup data, if you call decoders manually.
 */
void IRrecv::initDecodedIRData() {

    if (irparams.OverflowFlag) {
        decodedIRData.flags = IRDATA_FLAGS_WAS_OVERFLOW;
#if defined(LOCAL_DEBUG)
        Serial.print(F("Overflow happened, try to increase the \"RAW_BUFFER_LENGTH\" value of "));
        Serial.print(RAW_BUFFER_LENGTH);
        Serial.println(F(" with #define RAW_BUFFER_LENGTH=<biggerValue>"));
#endif

    } else {
        decodedIRData.flags = IRDATA_FLAGS_EMPTY;
        // save last protocol, command and address for repeat handling (where they are compared or copied back :-))
        lastDecodedProtocol = decodedIRData.protocol; // repeat patterns can be equal between protocols (e.g. NEC, Samsung and LG), so we must keep the original one
        lastDecodedCommand = decodedIRData.command;
        lastDecodedAddress = decodedIRData.address;

    }

    //These 2 variables allow to call resume() directly after decode, if no dump is required. since 4.3.0.
    decodedIRData.initialGap = decodedIRData.rawDataPtr->rawbuf[0];
    decodedIRData.rawlen = decodedIRData.rawDataPtr->rawlen;

    decodedIRData.protocol = UNKNOWN;
    decodedIRData.command = 0;
    decodedIRData.address = 0;
    decodedIRData.decodedRawData = 0;
    decodedIRData.numberOfBits = 0;
}

/**
 * Returns true if IR receiver data is available.
 */
bool IRrecv::available() {
    return (irparams.StateForISR == IR_REC_STATE_STOP);
}

/**
 * If IR receiver data is available, returns pointer to IrReceiver.decodedIRData, else NULL.
 */
IRData* IRrecv::read() {
    if (irparams.StateForISR != IR_REC_STATE_STOP) {
        return NULL;
    }
    if (decode()) {
        return &decodedIRData;
    } else {
        return NULL;
    }
}

/**
 * The main decode function, attempts to decode the recently receive IR signal.
 * The set of decoders used is determined by active definitions of the DECODE_<PROTOCOL> macros.
 * Results of decoding are stored in IrReceiver.decodedIRData.* like e.g. IrReceiver.decodedIRData.command.
 * @return false if no IR receiver data available, true if data available.
 */
bool IRrecv::decode() {
    if (irparams.StateForISR != IR_REC_STATE_STOP) {
        return false;
    }

    initDecodedIRData(); // sets IRDATA_FLAGS_WAS_OVERFLOW

    if (decodedIRData.flags & IRDATA_FLAGS_WAS_OVERFLOW) {
        /*
         * Set OverflowFlag flag and return true here, to let the loop call resume or print raw data.
         */
        decodedIRData.protocol = UNKNOWN;
        return true;
    }

#if defined(DECODE_NEC) || defined(DECODE_ONKYO)
    IR_TRACE_PRINTLN(F("Attempting NEC/Onkyo decode"));
    if (decodeNEC()) {
        return true;
    }
#endif

#if defined(DECODE_PANASONIC) || defined(DECODE_KASEIKYO)
    IR_TRACE_PRINTLN(F("Attempting Panasonic/Kaseikyo decode"));
    if (decodeKaseikyo()) {
        return true;
    }
#endif

#if defined(DECODE_DENON)
    IR_TRACE_PRINTLN(F("Attempting Denon/Sharp decode"));
    if (decodeDenon()) {
        return true;
    }
#endif

#if defined(DECODE_SONY)
    IR_TRACE_PRINTLN(F("Attempting Sony decode"));
    if (decodeSony()) {
        return true;
    }
#endif

#if defined(DECODE_RC5)
    IR_TRACE_PRINTLN(F("Attempting RC5 decode"));
    if (decodeRC5()) {
        return true;
    }
#endif

#if defined(DECODE_RC6)
    IR_TRACE_PRINTLN(F("Attempting RC6 decode"));
    if (decodeRC6()) {
        return true;
    }
#endif

#if defined(DECODE_LG)
    IR_TRACE_PRINTLN(F("Attempting LG decode"));
    if (decodeLG()) {
        return true;
    }
#endif

#if defined(DECODE_JVC)
    IR_TRACE_PRINTLN(F("Attempting JVC decode"));
    if (decodeJVC()) {
        return true;
    }
#endif

#if defined(DECODE_SAMSUNG)
    IR_TRACE_PRINTLN(F("Attempting Samsung decode"));
    if (decodeSamsung()) {
        return true;
    }
#endif
    /*
     * Start of the exotic protocols
     */

#if defined(DECODE_BEO)
    IR_TRACE_PRINTLN(F("Attempting Bang & Olufsen decode"));
    if (decodeBangOlufsen()) {
        return true;
    }
#endif

#if defined(DECODE_FAST)
    IR_TRACE_PRINTLN(F("Attempting FAST decode"));
    if (decodeFAST()) {
        return true;
    }
#endif

#if defined(DECODE_WHYNTER)
    IR_TRACE_PRINTLN(F("Attempting Whynter decode"));
    if (decodeWhynter()) {
        return true;
    }
#endif

#if defined(DECODE_LEGO_PF)
    IR_TRACE_PRINTLN(F("Attempting Lego Power Functions"));
    if (decodeLegoPowerFunctions()) {
        return true;
    }
#endif

#if defined(DECODE_BOSEWAVE)
    IR_TRACE_PRINTLN(F("Attempting Bosewave  decode"));
    if (decodeBoseWave()) {
        return true;
    }
#endif

#if defined(DECODE_MAGIQUEST)
    IR_TRACE_PRINTLN(F("Attempting MagiQuest decode"));
    if (decodeMagiQuest()) {
        return true;
    }
#endif

    /*
     * Try the universal decoder for pulse distance protocols
     */
#if defined(DECODE_DISTANCE_WIDTH)
    IR_TRACE_PRINTLN(F("Attempting universal Distance Width decode"));
    if (decodeDistanceWidth()) {
        return true;
    }
#endif

    /*
     * Last resort is the universal hash decode which always return true
     */
#if defined(DECODE_HASH)
    IR_TRACE_PRINTLN(F("Hash decode"));
    // decodeHash returns a hash on any input.
    // Thus, it needs to be last in the list.
    // If you add any decodes, add them before this.
    if (decodeHash()) {
        return true;
    }
#endif

    /*
     * Return true here, to let the loop decide to call resume or to print raw data.
     */
    return true;
}

/**********************************************************************************************************************
 * Common decode functions
 **********************************************************************************************************************/
/**
 * Decode pulse distance width protocols.
 *
 * We can have the following protocol timings
 * Pulse distance:          Pulses/marks are constant, pause/spaces have different length, like NEC.
 * Pulse width:             Pulses/marks have different length, pause/spaces are constant, like Sony.
 * Pulse distance width:    Pulses/marks and pause/spaces have different length, often the bit length is constant, like MagiQuest.
 * Pulse distance width can be decoded like pulse width decoder, if this decoder does not check the length of pause/spaces.
 *
 * Input is     IrReceiver.decodedIRData.rawDataPtr->rawbuf[]
 * Output is    IrReceiver.decodedIRData.decodedRawData
 *
 * Assume pulse distance if aOneMarkMicros == aZeroMarkMicros
 *
 * @param   aNumberOfBits       Number of bits to decode from decodedIRData.rawDataPtr->rawbuf[] array.
 * @param   aStartOffset        Offset in decodedIRData.rawDataPtr->rawbuf[] to start decoding. Must point to a mark.
 * @param   aOneMarkMicros      Taken as constant BitMarkMicros for pulse distance.
 * @param   aZeroMarkMicros     Not required if DECODE_STRICT_CHECKS is not defined.
 * @param   aOneSpaceMicros     Taken as (constant) BitSpaceMicros for pulse width.
 * @param   aZeroSpaceMicros    Not required if DECODE_STRICT_CHECKS is not defined.
 * @param   aMSBfirst           If true send Most Significant Bit first, else send Least Significant Bit (lowest bit) first.
 * @return  true                If decoding was successful
 */
bool IRrecv::decodePulseDistanceWidthData(uint_fast8_t aNumberOfBits, IRRawlenType aStartOffset, uint16_t aOneMarkMicros,
        uint16_t aZeroMarkMicros, uint16_t aOneSpaceMicros, uint16_t aZeroSpaceMicros, bool aMSBfirst) {

    auto *tRawBufPointer = &decodedIRData.rawDataPtr->rawbuf[aStartOffset];

    bool isPulseDistanceProtocol = (aOneMarkMicros == aZeroMarkMicros); // If true, we have a constant mark -> pulse distance protocol

    IRRawDataType tDecodedData = 0; // For MSB first tDecodedData is shifted left each loop
    IRRawDataType tMask = 1UL; // Mask is only used for LSB first

    for (uint_fast8_t i = aNumberOfBits; i > 0; i--) {
        // get one mark and space pair
        unsigned int tMarkTicks;
        unsigned int tSpaceTicks;
        bool tBitValue;

        if (isPulseDistanceProtocol) {
            /*
             * Pulse distance here, it is not required to check constant mark duration (aOneMarkMicros) and zero space duration.
             */
#if defined DECODE_STRICT_CHECKS
            tMarkTicks = *tRawBufPointer++;
#else
            (void) aZeroSpaceMicros;
            tRawBufPointer++;
#endif
            tSpaceTicks = *tRawBufPointer++; // maybe buffer overflow for last bit, but we do not evaluate this value :-)
            tBitValue = matchSpace(tSpaceTicks, aOneSpaceMicros); // Check for variable length space indicating a 1 or 0

#if defined DECODE_STRICT_CHECKS
            // Check for constant length mark
            if (!matchMark(tMarkTicks, aOneMarkMicros)) {
#  if defined(LOCAL_DEBUG)
                Serial.print(F("Mark="));
                Serial.print(tMarkTicks * MICROS_PER_TICK);
                Serial.print(F(" is not "));
                Serial.print(aOneMarkMicros);
                Serial.print(F(". Index="));
                Serial.print(aNumberOfBits - i);
                Serial.print(' ');
#  endif
                return false;
            }
#endif

        } else {
            /*
             * Pulse width here, it is not required to check (constant) space duration and zero mark duration.
             */
            tMarkTicks = *tRawBufPointer++;
            tBitValue = matchMark(tMarkTicks, aOneMarkMicros); // Check for variable length mark indicating a 1 or 0

#if defined DECODE_STRICT_CHECKS
            tSpaceTicks = *tRawBufPointer++; // maybe buffer overflow for last bit, but we do not evaluate this value :-)
#else
            (void) aZeroMarkMicros;
            (void) aZeroSpaceMicros;
            tRawBufPointer++;
#endif
        }

        if (aMSBfirst) {
            tDecodedData <<= 1;
        }

        if (tBitValue) {
            // It's a 1 -> set the bit
            if (aMSBfirst) {
                tDecodedData |= 1;
            } else {
                tDecodedData |= tMask;
            }
            IR_TRACE_PRINTLN(F("=> 1"));
        } else {
#if defined DECODE_STRICT_CHECKS
            /*
             * Additionally check length of length parameter which determine a zero
             */
            if (isPulseDistanceProtocol) {
                if (!matchSpace(tSpaceTicks, aZeroSpaceMicros)) {
#  if defined(LOCAL_DEBUG)
                    Serial.print(F("Space="));
                    Serial.print(tSpaceTicks * MICROS_PER_TICK);
                    Serial.print(F(" is not "));
                    Serial.print(aOneSpaceMicros);
                    Serial.print(F(" or "));
                    Serial.print(aZeroSpaceMicros);
                    Serial.print(F(". Index="));
                    Serial.print(aNumberOfBits - i);
                    Serial.print(' ');
#  endif
                    return false;
                }
            } else {
                if (!matchMark(tMarkTicks, aZeroMarkMicros)) {
#  if defined(LOCAL_DEBUG)
                    Serial.print(F("Mark="));
                    Serial.print(tMarkTicks * MICROS_PER_TICK);
                    Serial.print(F(" is not "));
                    Serial.print(aOneMarkMicros);
                    Serial.print(F(" or "));
                    Serial.print(aZeroMarkMicros);
                    Serial.print(F(". Index="));
                    Serial.print(aNumberOfBits - i);
                    Serial.print(' ');
#  endif
                    return false;
                }
            }
#endif
            // do not set the bit
            IR_TRACE_PRINTLN(F("=> 0"));
        }
#if defined DECODE_STRICT_CHECKS
        // If we have no stop bit, assume that last space, which is not recorded, is correct, since we can not check it
        if (aZeroSpaceMicros == aOneSpaceMicros
                && tRawBufPointer < &decodedIRData.rawDataPtr->rawbuf[decodedIRData.rawDataPtr->rawlen]) {
            // Check for constant length space (of pulse width protocol) here
            if (!matchSpace(tSpaceTicks, aOneSpaceMicros)) {
#  if defined(LOCAL_DEBUG)
                Serial.print(F("Space="));
                Serial.print(tSpaceTicks * MICROS_PER_TICK);
                Serial.print(F(" is not "));
                Serial.print(aOneSpaceMicros);
                Serial.print(F(". Index="));
                Serial.print(aNumberOfBits - i);
                Serial.print(' ');
#  endif
                return false;
            }
        }
#endif
        tMask <<= 1;
    }
    decodedIRData.decodedRawData = tDecodedData;
    return true;
}

/**
 * Decode pulse distance protocols for PulseDistanceWidthProtocolConstants.
 * @return  true if decoding was successful
 */
bool IRrecv::decodePulseDistanceWidthData(PulseDistanceWidthProtocolConstants *aProtocolConstants, uint_fast8_t aNumberOfBits,
        IRRawlenType aStartOffset) {

    return decodePulseDistanceWidthData(aNumberOfBits, aStartOffset, aProtocolConstants->DistanceWidthTimingInfo.OneMarkMicros,
            aProtocolConstants->DistanceWidthTimingInfo.ZeroMarkMicros, aProtocolConstants->DistanceWidthTimingInfo.OneSpaceMicros,
            aProtocolConstants->DistanceWidthTimingInfo.ZeroSpaceMicros, aProtocolConstants->Flags);
}

/*
 * Static variables for the getBiphaselevel function
 */
uint_fast8_t sBiphaseDecodeRawbuffOffset;   // Index into raw timing array
uint16_t sBiphaseCurrentTimingIntervals;    // 1, 2 or 3. Number of aBiphaseTimeUnit intervals of the current rawbuf[sBiphaseDecodeRawbuffOffset] timing.
uint_fast8_t sBiphaseUsedTimingIntervals;   // Number of already used intervals of sCurrentTimingIntervals.
uint16_t sBiphaseTimeUnit;

void IRrecv::initBiphaselevel(uint_fast8_t aRCDecodeRawbuffOffset, uint16_t aBiphaseTimeUnit) {
    sBiphaseDecodeRawbuffOffset = aRCDecodeRawbuffOffset;
    sBiphaseTimeUnit = aBiphaseTimeUnit;
    sBiphaseUsedTimingIntervals = 0;
}

/**
 * Gets the level of one time interval (aBiphaseTimeUnit) at a time from the raw buffer.
 * The RC5/6 decoding is easier if the data is broken into time intervals.
 * E.g. if the buffer has mark for 2 time intervals and space for 1,
 * successive calls to getBiphaselevel will return 1, 1, 0.
 *
 *               _   _   _   _   _   _   _   _   _   _   _   _   _
 *         _____| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |
 *                ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^    Significant clock edge
 *               _     _   _   ___   _     ___     ___   _   - Mark
 * Data    _____| |___| |_| |_|   |_| |___|   |___|   |_| |  - Data starts with a mark->space bit
 *                1   0   0   0   1   1   0   1   0   1   1  - Space
 * A mark to space at a significant clock edge results in a 1
 * A space to mark at a significant clock edge results in a 0 (for RC6)
 * Returns current level [MARK or SPACE] or -1 for error (measured time interval is not a multiple of sBiphaseTimeUnit).
 */
uint_fast8_t IRrecv::getBiphaselevel() {
    uint_fast8_t tLevelOfCurrentInterval; // 0 (SPACE) or 1 (MARK)

    if (sBiphaseDecodeRawbuffOffset >= decodedIRData.rawlen) {
        return SPACE;  // After end of recorded buffer, assume space.
    }

    tLevelOfCurrentInterval = (sBiphaseDecodeRawbuffOffset) & 1; // on odd rawbuf offsets we have mark timings

    /*
     * Setup data if sUsedTimingIntervals is 0
     */
    if (sBiphaseUsedTimingIntervals == 0) {
        uint16_t tCurrentTimingWith = decodedIRData.rawDataPtr->rawbuf[sBiphaseDecodeRawbuffOffset];
        uint16_t tMarkExcessCorrection = (tLevelOfCurrentInterval == MARK) ? MARK_EXCESS_MICROS : -MARK_EXCESS_MICROS;

        if (matchTicks(tCurrentTimingWith, sBiphaseTimeUnit + tMarkExcessCorrection)) {
            sBiphaseCurrentTimingIntervals = 1;
        } else if (matchTicks(tCurrentTimingWith, (2 * sBiphaseTimeUnit) + tMarkExcessCorrection)) {
            sBiphaseCurrentTimingIntervals = 2;
        } else if (matchTicks(tCurrentTimingWith, (3 * sBiphaseTimeUnit) + tMarkExcessCorrection)) {
            sBiphaseCurrentTimingIntervals = 3;
        } else {
            return -1;
        }
    }

// We use another interval from tCurrentTimingIntervals
    sBiphaseUsedTimingIntervals++;

// keep track of current timing offset
    if (sBiphaseUsedTimingIntervals >= sBiphaseCurrentTimingIntervals) {
        // we have used all intervals of current timing, switch to next timing value
        sBiphaseUsedTimingIntervals = 0;
        sBiphaseDecodeRawbuffOffset++;
    }

    IR_TRACE_PRINTLN(tLevelOfCurrentInterval);

    return tLevelOfCurrentInterval;
}

/**********************************************************************************************************************
 * Internal Hash decode function
 **********************************************************************************************************************/
#define FNV_PRIME_32 16777619   ///< used for decodeHash()
#define FNV_BASIS_32 2166136261 ///< used for decodeHash()

/**
 * Compare two (tick) values for Hash decoder
 * Use a tolerance of 20% to enable e.g. 500 and 600 (NEC timing) to be equal
 * @return  0 if newval is shorter, 1 if newval is equal, and 2 if newval is longer
 */
uint_fast8_t IRrecv::compare(uint16_t oldval, uint16_t newval) {
    if (newval * 10 < oldval * 8) {
        return 0;
    }
    if (oldval * 10 < newval * 8) {
        return 2;
    }
    return 1;
}

/**
 * decodeHash - decode an arbitrary IR code.
 * Instead of decoding using a standard encoding scheme
 * (e.g. Sony, NEC, RC5), the code is hashed to a 32-bit value.
 *
 * The algorithm: look at the sequence of MARK and SPACE signals, and see if each one
 * is shorter (0), the same length (1), or longer (2) than the previous MARK or SPACE.
 * Hash the resulting sequence of 0's, 1's, and 2's to a 32-bit value.
 * This will give a unique value for each different code (probably), for most code systems.
 *
 * Use FNV hash algorithm: http://isthe.com/chongo/tech/comp/fnv/#FNV-param
 * Converts the raw code values into a 32-bit hash code.
 * Hopefully this code is unique for each button.
 * This isn't a "real" decoding, just an arbitrary value.
 *
 * see: http://www.righto.com/2010/01/using-arbitrary-remotes-with-arduino.html
 */
bool IRrecv::decodeHash() {
    unsigned long hash = FNV_BASIS_32; // the result is the same no matter if we use a long or unsigned long variable

// Require at least 6 samples to prevent triggering on noise
    if (decodedIRData.rawlen < 6) {
        return false;
    }
    for (IRRawlenType i = 1; (i + 2) < decodedIRData.rawlen; i++) {
        // Compare mark with mark and space with space
        uint_fast8_t value = compare(decodedIRData.rawDataPtr->rawbuf[i], decodedIRData.rawDataPtr->rawbuf[i + 2]);
        // Add value into the hash
        hash = (hash * FNV_PRIME_32) ^ value;
    }

    decodedIRData.decodedRawData = hash;
    decodedIRData.numberOfBits = 32;
    decodedIRData.protocol = UNKNOWN;

    return true;
}

bool IRrecv::decodeHashOld(decode_results *aResults) {
    unsigned long hash = FNV_BASIS_32;

// Require at least 6 samples to prevent triggering on noise
    if (aResults->rawlen < 6) {
        return false;
    }

    for (uint8_t i = 3; i < aResults->rawlen; i++) {
        uint_fast8_t value = compare(aResults->rawbuf[i - 2], aResults->rawbuf[i]);
        // Add value into the hash
        hash = (hash * FNV_PRIME_32) ^ value;
    }

    aResults->value = hash;
    aResults->bits = 32;
    aResults->decode_type = UNKNOWN;
    decodedIRData.protocol = UNKNOWN;

    return true;
}

/**********************************************************************************************************************
 * Match functions
 **********************************************************************************************************************/

/*
 * returns true if values do match
 */
bool IRrecv::checkHeader(PulseDistanceWidthProtocolConstants *aProtocolConstants) {
// Check header "mark" and "space"
    if (!matchMark(decodedIRData.rawDataPtr->rawbuf[1], aProtocolConstants->DistanceWidthTimingInfo.HeaderMarkMicros)) {
#if defined(LOCAL_TRACE)
        Serial.print(::getProtocolString(aProtocolConstants->ProtocolIndex));
        Serial.println(F(": Header mark length is wrong"));
#endif
        return false;
    }
    if (!matchSpace(decodedIRData.rawDataPtr->rawbuf[2], aProtocolConstants->DistanceWidthTimingInfo.HeaderSpaceMicros)) {
#if defined(LOCAL_TRACE)
        Serial.print(::getProtocolString(aProtocolConstants->ProtocolIndex));
        Serial.println(F(": Header space length is wrong"));
#endif
        return false;
    }
    return true;
}

/*
 * Do not check for same address and command, because it is almost not possible to press 2 different buttons on the remote within around 100 ms.
 * And if really required, it can be enabled here, or done manually in user program.
 * And we have still no RC6 toggle bit check for detecting a second press on the same button.
 */
void IRrecv::checkForRepeatSpaceTicksAndSetFlag(uint16_t aMaximumRepeatSpaceTicks) {
    if (decodedIRData.rawDataPtr->rawbuf[0] < aMaximumRepeatSpaceTicks
#if defined(ENABLE_FULL_REPEAT_CHECK)
            && decodedIRData.address == lastDecodedAddress && decodedIRData.command == lastDecodedCommand /* requires around 85 bytes program space */
#endif
            ) {
        decodedIRData.flags |= IRDATA_FLAGS_IS_REPEAT;
    }
}

/**
 * Match function without compensating for marks exceeded or spaces shortened by demodulator hardware
 * Currently not used
 */
bool matchTicks(uint16_t aMeasuredTicks, uint16_t aMatchValueMicros) {
#if defined(LOCAL_TRACE)
    Serial.print(F("Testing: "));
    Serial.print(TICKS_LOW(aMatchValueMicros), DEC);
    Serial.print(F(" <= "));
    Serial.print(aMeasuredTicks, DEC);
    Serial.print(F(" <= "));
    Serial.print(TICKS_HIGH(aMatchValueMicros), DEC);
#endif
    bool passed = ((aMeasuredTicks >= TICKS_LOW(aMatchValueMicros)) && (aMeasuredTicks <= TICKS_HIGH(aMatchValueMicros)));
#if defined(LOCAL_TRACE)
    if (passed) {
        Serial.println(F(" => passed"));
    } else {
        Serial.println(F(" => FAILED"));
    }
#endif
    return passed;
}

bool MATCH(uint16_t measured_ticks, uint16_t desired_us) {
    return matchTicks(measured_ticks, desired_us);
}

/**
 * Compensate for marks exceeded by demodulator hardware
 */
bool matchMark(uint16_t aMeasuredTicks, uint16_t aMatchValueMicros) {
#if defined(LOCAL_TRACE)
    Serial.print(F("Testing mark (actual vs desired): "));
    Serial.print(aMeasuredTicks * MICROS_PER_TICK, DEC);
    Serial.print(F("us vs "));
    Serial.print(aMatchValueMicros, DEC);
    Serial.print(F("us: "));
    Serial.print(TICKS_LOW(aMatchValueMicros + MARK_EXCESS_MICROS) * MICROS_PER_TICK, DEC);
    Serial.print(F(" <= "));
    Serial.print(aMeasuredTicks * MICROS_PER_TICK, DEC);
    Serial.print(F(" <= "));
    Serial.print(TICKS_HIGH(aMatchValueMicros + MARK_EXCESS_MICROS) * MICROS_PER_TICK, DEC);
#endif
    // compensate for marks exceeded by demodulator hardware
    bool passed = ((aMeasuredTicks >= TICKS_LOW(aMatchValueMicros + MARK_EXCESS_MICROS))
            && (aMeasuredTicks <= TICKS_HIGH(aMatchValueMicros + MARK_EXCESS_MICROS)));
#if defined(LOCAL_TRACE)
    if (passed) {
        Serial.println(F(" => passed"));
    } else {
        Serial.println(F(" => FAILED"));
    }
#endif
    return passed;
}

bool MATCH_MARK(uint16_t measured_ticks, uint16_t desired_us) {
    return matchMark(measured_ticks, desired_us);
}

/**
 * Compensate for spaces shortened by demodulator hardware
 */
bool matchSpace(uint16_t aMeasuredTicks, uint16_t aMatchValueMicros) {
#if defined(LOCAL_TRACE)
    Serial.print(F("Testing space (actual vs desired): "));
    Serial.print(aMeasuredTicks * MICROS_PER_TICK, DEC);
    Serial.print(F("us vs "));
    Serial.print(aMatchValueMicros, DEC);
    Serial.print(F("us: "));
    Serial.print(TICKS_LOW(aMatchValueMicros - MARK_EXCESS_MICROS) * MICROS_PER_TICK, DEC);
    Serial.print(F(" <= "));
    Serial.print(aMeasuredTicks * MICROS_PER_TICK, DEC);
    Serial.print(F(" <= "));
    Serial.print(TICKS_HIGH(aMatchValueMicros - MARK_EXCESS_MICROS) * MICROS_PER_TICK, DEC);
#endif
    // compensate for spaces shortened by demodulator hardware
    bool passed = ((aMeasuredTicks >= TICKS_LOW(aMatchValueMicros - MARK_EXCESS_MICROS))
            && (aMeasuredTicks <= TICKS_HIGH(aMatchValueMicros - MARK_EXCESS_MICROS)));
#if defined(LOCAL_TRACE)
    if (passed) {
        Serial.println(F(" => passed"));
    } else {
        Serial.println(F(" => FAILED"));
    }
#endif
    return passed;
}

bool MATCH_SPACE(uint16_t measured_ticks, uint16_t desired_us) {
    return matchSpace(measured_ticks, desired_us);
}

/**
 * Getter function for MARK_EXCESS_MICROS
 */
int getMarkExcessMicros() {
    return MARK_EXCESS_MICROS;
}

/*
 * Check if protocol is not detected and detected space between two transmissions
 * is smaller than known value for protocols (Sony with around 24 ms)
 * @return true, if CheckForRecordGapsMicros() has printed a message, i.e. gap < 15ms (RECORD_GAP_MICROS_WARNING_THRESHOLD)
 */
bool IRrecv::checkForRecordGapsMicros(Print *aSerial) {
    /*
     * Check if protocol is not detected and detected space between two transmissions
     * is smaller than known value for protocols (Sony with around 24 ms)
     */
    if (decodedIRData.protocol <= PULSE_DISTANCE
            && decodedIRData.initialGap < (RECORD_GAP_MICROS_WARNING_THRESHOLD / MICROS_PER_TICK)) {
        aSerial->println();
        aSerial->print(F("Space of "));
        aSerial->print(decodedIRData.initialGap * MICROS_PER_TICK);
        aSerial->print(F(" us between two detected transmission is smaller than the minimal gap of "));
        aSerial->print(RECORD_GAP_MICROS_WARNING_THRESHOLD);
        aSerial->println(F(" us known for implemented protocols like NEC, Sony, RC% etc.."));
        aSerial->println(F("But it can be OK for some yet unsupported protocols, and especially for repeats."));
        aSerial->println(F("If you get unexpected results, try to increase the RECORD_GAP_MICROS in IRremote.h."));
        aSerial->println();
        return true;
    }
    return false;
}

/**********************************************************************************************************************
 * Print functions
 * Since a library should not allocate the "Serial" object, all functions require a pointer to a Print object.
 **********************************************************************************************************************/
void IRrecv::printActiveIRProtocols(Print *aSerial) {
// call no class function with same name
    ::printActiveIRProtocols(aSerial);
}
void printActiveIRProtocols(Print *aSerial) {
#if defined(DECODE_ONKYO)
    aSerial->print(F("Onkyo, "));
#elif defined(DECODE_NEC)
    aSerial->print(F("NEC/NEC2/Onkyo/Apple, "));
#endif
#if defined(DECODE_PANASONIC) || defined(DECODE_KASEIKYO)
    aSerial->print(F("Panasonic/Kaseikyo, "));
#endif
#if defined(DECODE_DENON)
    aSerial->print(F("Denon/Sharp, "));
#endif
#if defined(DECODE_SONY)
    aSerial->print(F("Sony, "));
#endif
#if defined(DECODE_RC5)
    aSerial->print(F("RC5, "));
#endif
#if defined(DECODE_RC6)
    aSerial->print(F("RC6, "));
#endif
#if defined(DECODE_LG)
    aSerial->print(F("LG, "));
#endif
#if defined(DECODE_JVC)
    aSerial->print(F("JVC, "));
#endif
#if defined(DECODE_SAMSUNG)
    aSerial->print(F("Samsung, "));
#endif
    /*
     * Start of the exotic protocols
     */
#if defined(DECODE_BEO)
    aSerial->print(F("Bang & Olufsen, "));
#endif
#if defined(DECODE_FAST)
    aSerial->print(F("FAST, "));
#endif
#if defined(DECODE_WHYNTER)
    aSerial->print(F("Whynter, "));
#endif
#if defined(DECODE_LEGO_PF)
    aSerial->print(F("Lego Power Functions, "));
#endif
#if defined(DECODE_BOSEWAVE)
    aSerial->print(F("Bosewave, "));
#endif
#if defined(DECODE_MAGIQUEST)
    aSerial->print(F("MagiQuest, "));
#endif
#if defined(DECODE_DISTANCE_WIDTH)
    aSerial->print(F("Universal Pulse Distance Width, "));
#endif
#if defined(DECODE_HASH)
    aSerial->print(F("Hash "));
#endif
#if defined(NO_DECODER) // for sending raw only
    (void)aSerial; // to avoid compiler warnings
#endif
}

/**
 * Function to print values and flags of IrReceiver.decodedIRData in one line.
 * Ends with println().
 *
 * @param aSerial   The Print object on which to write, for Arduino you can use &Serial.
 * @param aPrintRepeatGap     If true also print the gap before repeats.
 * @param aCheckForRecordGapsMicros   If true, call CheckForRecordGapsMicros() which may do a long printout,
 *                                    which in turn may block the proper detection of repeats.*
 * @return true, if CheckForRecordGapsMicros() has printed a message, i.e. gap < 15ms (RECORD_GAP_MICROS_WARNING_THRESHOLD).
 */
bool IRrecv::printIRResultShort(Print *aSerial, bool aPrintRepeatGap, bool aCheckForRecordGapsMicros) {
// call no class function with same name
    ::printIRResultShort(aSerial, &decodedIRData, aPrintRepeatGap);
    if (aCheckForRecordGapsMicros && decodedIRData.protocol != UNKNOWN) {
        return checkForRecordGapsMicros(aSerial);
    }
    return false;
}

void IRrecv::printDistanceWidthTimingInfo(Print *aSerial, DistanceWidthTimingInfoStruct *aDistanceWidthTimingInfo) {
    aSerial->print(aDistanceWidthTimingInfo->HeaderMarkMicros);
    aSerial->print(F(", "));
    aSerial->print(aDistanceWidthTimingInfo->HeaderSpaceMicros);
    aSerial->print(F(", "));
    aSerial->print(aDistanceWidthTimingInfo->OneMarkMicros);
    aSerial->print(F(", "));
    aSerial->print(aDistanceWidthTimingInfo->OneSpaceMicros);
    aSerial->print(F(", "));
    aSerial->print(aDistanceWidthTimingInfo->ZeroMarkMicros);
    aSerial->print(F(", "));
    aSerial->print(aDistanceWidthTimingInfo->ZeroSpaceMicros);
}

uint32_t IRrecv::getTotalDurationOfRawData() {
    uint16_t tSumOfDurationTicks = 0;

    for (IRRawlenType i = 1; i < decodedIRData.rawlen; i++) {
        tSumOfDurationTicks += decodedIRData.rawDataPtr->rawbuf[i];
    }
    return tSumOfDurationTicks * (uint32_t) MICROS_PER_TICK;
}

/**
 * Function to print values and flags of IrReceiver.decodedIRData in one line.
 * Ends with println().
 * !!!Attention: The result differs on a 8 bit or 32 bit platform!!!
 *
 * @param aSerial The Print object on which to write, for Arduino you can use &Serial.
 */
void IRrecv::printIRSendUsage(Print *aSerial) {
    if (decodedIRData.flags & IRDATA_FLAGS_IS_PROTOCOL_WITH_DIFFERENT_REPEAT) {
        /*
         * Here we have a repeat of type NEC2 or SamsungLG. -> Inform the user to use this and not the initial protocol for sending.
         */
        Serial.print(F("!Use the "));
        Serial.print(getProtocolString());
        Serial.println(F(" protocol for sending!"));
    } else {
        if (decodedIRData.protocol != UNKNOWN
                && (decodedIRData.flags & (IRDATA_FLAGS_IS_AUTO_REPEAT | IRDATA_FLAGS_IS_REPEAT)) == 0x00) {
#if defined(DECODE_DISTANCE_WIDTH)
        uint_fast8_t tNumberOfArrayData = 0;
        if (decodedIRData.protocol == PULSE_DISTANCE || decodedIRData.protocol == PULSE_WIDTH) {
#  if __INT_WIDTH__ < 32
            aSerial->print(F("Send on a 8 bit platform with: "));
            tNumberOfArrayData = ((decodedIRData.numberOfBits - 1) / 32) + 1;
            if(tNumberOfArrayData > 1) {
                aSerial->println();
                aSerial->print(F("    uint32_t tRawData[]={0x"));
#  else
                aSerial->print(F("Send on a 32 bit platform with: "));
            tNumberOfArrayData = ((decodedIRData.numberOfBits - 1) / 64) + 1;
            if(tNumberOfArrayData > 1) {
                aSerial->println();
                aSerial->print(F("    uint64_t tRawData[]={0x"));
#  endif
                for (uint_fast8_t i = 0; i < tNumberOfArrayData; ++i) {
#  if (__INT_WIDTH__ < 32)
                    aSerial->print(decodedIRData.decodedRawDataArray[i], HEX);
#  else
                    PrintULL::print(aSerial, decodedIRData.decodedRawDataArray[i], HEX);
#  endif
                    if (i != tNumberOfArrayData - 1) {
                        aSerial->print(F(", 0x"));
                    }
                }
                aSerial->println(F("};"));
                aSerial->print(F("    "));
            }
        } else {
            aSerial->print(F("Send with: "));
        }
        aSerial->print(F("IrSender.send"));

#else
            aSerial->print(F("Send with: IrSender.send"));
#endif

#if defined(DECODE_DISTANCE_WIDTH)
        if (decodedIRData.protocol != PULSE_DISTANCE && decodedIRData.protocol != PULSE_WIDTH) {
#endif
            aSerial->print(getProtocolString());
            aSerial->print(F("(0x"));
#if defined(DECODE_MAGIQUEST)
            if (decodedIRData.protocol == MAGIQUEST) {
#  if (__INT_WIDTH__ < 32)
                aSerial->print(decodedIRData.decodedRawData, HEX);
#  else
                PrintULL::print(aSerial, decodedIRData.decodedRawData, HEX);
#  endif
            } else {
                aSerial->print(decodedIRData.address, HEX);
            }
#else
            /*
             * New decoders have address and command
             */
            aSerial->print(decodedIRData.address, HEX);
#endif

            aSerial->print(F(", 0x"));
            aSerial->print(decodedIRData.command, HEX);
            if (decodedIRData.protocol == SONY) {
                aSerial->print(F(", 2, "));
                aSerial->print(decodedIRData.numberOfBits);
            } else {
                aSerial->print(F(", <numberOfRepeats>"));
            }

#if defined(DECODE_DISTANCE_WIDTH)
        } else {
            /*
             * Pulse distance or pulse width here
             */
            aSerial->print("PulseDistanceWidth");
            if(tNumberOfArrayData > 1) {
                aSerial->print("FromArray(38, ");
            } else {
                aSerial->print("(38, ");
            }
            printDistanceWidthTimingInfo(aSerial, &decodedIRData.DistanceWidthTimingInfo);

            if(tNumberOfArrayData > 1) {
                aSerial->print(F(", &tRawData[0], "));
            } else {
                aSerial->print(F(", 0x"));
#  if (__INT_WIDTH__ < 32)
                aSerial->print(decodedIRData.decodedRawData, HEX);
#  else
                PrintULL::print(aSerial, decodedIRData.decodedRawData, HEX);
#  endif
                aSerial->print(F(", "));
            }
            aSerial->print(decodedIRData.numberOfBits);// aNumberOfBits
            aSerial->print(F(", PROTOCOL_IS_"));

            if (decodedIRData.flags & IRDATA_FLAGS_IS_MSB_FIRST) {
                aSerial->print('M');
            } else {
                aSerial->print('L');
            }
            aSerial->print(F("SB_FIRST, <RepeatPeriodMillis>, <numberOfRepeats>"));
        }
#endif
#if defined(DECODE_PANASONIC) || defined(DECODE_KASEIKYO)
        if ((decodedIRData.flags & IRDATA_FLAGS_EXTRA_INFO) && decodedIRData.protocol == KASEIKYO) {
            aSerial->print(F(", 0x"));
            aSerial->print(decodedIRData.extra, HEX);
        }
#endif
            aSerial->print(F(");"));
            aSerial->println();
        }
    }
}

/**
 * Function to print protocol number, address, command, raw data and repeat flag of IrReceiver.decodedIRData in one short line.
 * Does not print a Newline / does not end with println().
 *
 * @param aSerial The Print object on which to write, for Arduino you can use &Serial.
 */
void IRrecv::printIRResultMinimal(Print *aSerial) {
    aSerial->print(F("P="));
    aSerial->print(decodedIRData.protocol);
    if (decodedIRData.protocol == UNKNOWN) {
#if defined(DECODE_HASH)
        aSerial->print(F(" #=0x"));
#  if (__INT_WIDTH__ < 32)
        aSerial->print(decodedIRData.decodedRawData, HEX);
#  else
        PrintULL::print(aSerial, decodedIRData.decodedRawData, HEX);
#  endif
#endif
        aSerial->print(' ');
        aSerial->print((decodedIRData.rawlen + 1) / 2, DEC);
        aSerial->println(F(" bits received"));
    } else {
        /*
         * New decoders have address and command
         */
        aSerial->print(F(" A=0x"));
        aSerial->print(decodedIRData.address, HEX);

        aSerial->print(F(" C=0x"));
        aSerial->print(decodedIRData.command, HEX);

        aSerial->print(F(" Raw=0x"));
#if (__INT_WIDTH__ < 32)
        aSerial->print(decodedIRData.decodedRawData, HEX);
#else
        PrintULL::print(aSerial, decodedIRData.decodedRawData, HEX);
#endif

        if (decodedIRData.flags & (IRDATA_FLAGS_IS_AUTO_REPEAT | IRDATA_FLAGS_IS_REPEAT)) {
            aSerial->print(F(" R"));
        }
    }
}

/**
 * Dump out the timings in IrReceiver.decodedIRData.rawDataPtr->rawbuf[] array 8 values per line.
 *
 * @param aSerial The Print object on which to write, for Arduino you can use &Serial.
 * @param aOutputMicrosecondsInsteadOfTicks Output the (rawbuf_values * MICROS_PER_TICK) for better readability.
 */
void IRrecv::printIRResultRawFormatted(Print *aSerial, bool aOutputMicrosecondsInsteadOfTicks) {

// Print Raw data
    aSerial->print(F("rawData["));
    aSerial->print(decodedIRData.rawlen, DEC);
    aSerial->println(F("]: "));

    /*
     * Print initial gap
     */
    aSerial->print(F(" -"));
    if (aOutputMicrosecondsInsteadOfTicks) {
        aSerial->println((uint32_t) decodedIRData.initialGap * MICROS_PER_TICK, DEC);
    } else {
        aSerial->println(decodedIRData.initialGap, DEC);
    }

// Newline is printed every 8. value, if tCounterForNewline % 8 == 0
    uint_fast8_t tCounterForNewline = 6; // first newline is after the 2 values of the start bit

// check if we have a protocol with no or 8 start bits
#if defined(DECODE_DENON) || defined(DECODE_MAGIQUEST)
    if (
#  if defined(DECODE_DENON)
            decodedIRData.protocol == DENON || decodedIRData.protocol == SHARP ||
#  endif
#  if defined(DECODE_MAGIQUEST)
            decodedIRData.protocol == MAGIQUEST ||
#  endif
            false) {
        tCounterForNewline = 0; // no or 8 start bits
    }
#endif

    uint32_t tDuration;
    uint16_t tSumOfDurationTicks = 0;
    for (IRRawlenType i = 1; i < decodedIRData.rawlen; i++) {
        auto tCurrentTicks = decodedIRData.rawDataPtr->rawbuf[i];
        if (aOutputMicrosecondsInsteadOfTicks) {
            tDuration = tCurrentTicks * MICROS_PER_TICK;
        } else {
            tDuration = tCurrentTicks;
        }
        tSumOfDurationTicks += tCurrentTicks; // compute length of protocol frame

        if (!(i & 1)) {  // even
            aSerial->print('-');
        } else {  // odd
            aSerial->print(F(" +"));
        }

        // padding only for big values
        if (aOutputMicrosecondsInsteadOfTicks && tDuration < 1000) {
            aSerial->print(' ');
        }
        if (aOutputMicrosecondsInsteadOfTicks && tDuration < 100) {
            aSerial->print(' ');
        }
        if (tDuration < 10) {
            aSerial->print(' ');
        }
        aSerial->print(tDuration, DEC);

        if ((i & 1) && (i + 1) < decodedIRData.rawlen) {
            aSerial->print(','); //',' not required for last one
        }

        tCounterForNewline++;
        if ((tCounterForNewline % 8) == 0) {
            aSerial->println();
        }
    }

    aSerial->println();
    aSerial->print("Sum: ");
    if (aOutputMicrosecondsInsteadOfTicks) {
        aSerial->println((uint32_t) tSumOfDurationTicks * MICROS_PER_TICK, DEC);
    } else {
        aSerial->println(tSumOfDurationTicks, DEC);
    }
}

/**
 * Dump out the IrReceiver.decodedIRData.rawDataPtr->rawbuf[] to be used as C definition for sendRaw().
 *
 * Compensate received values by MARK_EXCESS_MICROS, like it is done for decoding!
 * Print ticks in 8 bit format to save space.
 * Maximum is 255*50 microseconds = 12750 microseconds = 12.75 ms, which hardly ever occurs inside an IR sequence.
 * Recording of IRremote anyway stops at a gap of RECORD_GAP_MICROS (5 ms).
 *
 * @param aSerial The Print object on which to write, for Arduino you can use &Serial.
 * @param aOutputMicrosecondsInsteadOfTicks Output the (rawbuf_values * MICROS_PER_TICK) for better readability.
 */
void IRrecv::compensateAndPrintIRResultAsCArray(Print *aSerial, bool aOutputMicrosecondsInsteadOfTicks) {
// Start declaration
    if (aOutputMicrosecondsInsteadOfTicks) {
        aSerial->print(F("uint16_t rawData["));         // variable type, array name
    } else {
        aSerial->print(F("uint8_t rawTicks["));          // variable type, array name
    }

    aSerial->print(decodedIRData.rawlen - 1, DEC);    // array size
    aSerial->print(F("] = {"));    // Start declaration

// Dump data
    for (IRRawlenType i = 1; i < decodedIRData.rawlen; i++) {
        uint32_t tDuration = decodedIRData.rawDataPtr->rawbuf[i] * MICROS_PER_TICK;

        if (i & 1) {
            // Mark
            tDuration -= MARK_EXCESS_MICROS;
        } else {
            tDuration += MARK_EXCESS_MICROS;
        }

        if (aOutputMicrosecondsInsteadOfTicks) {
            aSerial->print(tDuration);
        } else {
            unsigned int tTicks = (tDuration + (MICROS_PER_TICK / 2)) / MICROS_PER_TICK;
            /*
             * Clip to 8 bit value
             */
            tTicks = (tTicks > UINT8_MAX) ? UINT8_MAX : tTicks;
            aSerial->print(tTicks);
        }
        if (i + 1 < decodedIRData.rawlen) aSerial->print(',');                // ',' not required on last one
        if (!(i & 1)) aSerial->print(' ');
    }

// End declaration
    aSerial->print(F("};"));                //

// Comment
    aSerial->print(F("  // "));
    printIRResultShort(aSerial);

// Newline
    aSerial->println("");
}

/**
 * Store the decodedIRData to be used for sendRaw().
 *
 * Compensate received values by MARK_EXCESS_MICROS, like it is done for decoding and store it in an array provided.
 *
 * Maximum for uint8_t is 255*50 microseconds = 12750 microseconds = 12.75 ms, which hardly ever occurs inside an IR sequence.
 * Recording of IRremote anyway stops at a gap of RECORD_GAP_MICROS (5 ms).
 * @param aArrayPtr Address of an array provided by the caller.
 */
void IRrecv::compensateAndStoreIRResultInArray(uint8_t *aArrayPtr) {

// Store data, skip leading space#
    IRRawlenType i;
    for (i = 1; i < decodedIRData.rawlen; i++) {
        uint32_t tDuration = decodedIRData.rawDataPtr->rawbuf[i] * MICROS_PER_TICK;
        if (i & 1) {
            // Mark
            tDuration -= MARK_EXCESS_MICROS;
        } else {
            tDuration += MARK_EXCESS_MICROS;
        }

        unsigned int tTicks = (tDuration + (MICROS_PER_TICK / 2)) / MICROS_PER_TICK;
        *aArrayPtr = (tTicks > UINT8_MAX) ? UINT8_MAX : tTicks; // we store it in an 8 bit array
        aArrayPtr++;
    }
}

/**
 * Print results as C variables to be used for sendXXX()
 * @param aSerial The Print object on which to write, for Arduino you can use &Serial.
 */
void IRrecv::printIRResultAsCVariables(Print *aSerial) {
// Now dump "known" codes
    if (decodedIRData.protocol != UNKNOWN) {

        /*
         * New decoders have address and command
         */
        aSerial->print(F("uint16_t"));
        aSerial->print(F(" address = 0x"));
        aSerial->print(decodedIRData.address, HEX);
        aSerial->println(';');

        aSerial->print(F("uint16_t"));
        aSerial->print(F(" command = 0x"));
        aSerial->print(decodedIRData.command, HEX);
        aSerial->println(';');

        // All protocols have raw data
#if __INT_WIDTH__ < 32
        aSerial->print(F("uint32_t rawData = 0x"));
#else
        aSerial->print(F("uint64_t rawData = 0x"));
#endif
#if (__INT_WIDTH__ < 32)
        aSerial->print(decodedIRData.decodedRawData, HEX);
#else
        PrintULL::print(aSerial, decodedIRData.decodedRawData, HEX);
#endif
        aSerial->println(';');
        aSerial->println();
    }
}

#if defined(__AVR__)
const __FlashStringHelper* IRrecv::getProtocolString() {
// call no class function with same name
    return ::getProtocolString(decodedIRData.protocol);
}
#else
const char* IRrecv::getProtocolString() {
    // call no class function with same name
    return ::getProtocolString(decodedIRData.protocol);
}
#endif

/**********************************************************************************************************************
 * The OLD and DEPRECATED decode function with parameter aResults, kept for backward compatibility to old 2.0 tutorials
 * This function calls the old MSB first decoders and fills only the 3 variables:
 * aResults->value
 * aResults->bits
 * aResults->decode_type
 **********************************************************************************************************************/
bool IRrecv::decode_old(decode_results *aResults) {

    if (irparams.StateForISR != IR_REC_STATE_STOP) {
        return false;
    }

// copy for usage by legacy programs
    aResults->rawbuf = irparams.rawbuf;
    aResults->rawlen = irparams.rawlen;
    if (irparams.OverflowFlag) {
        // Copy overflow flag to decodedIRData.flags
        irparams.OverflowFlag = false;
        irparams.rawlen = 0; // otherwise we have OverflowFlag again at next ISR call
        IR_DEBUG_PRINTLN(F("Overflow happened"));
    }
    aResults->overflow = irparams.OverflowFlag;
    aResults->value = 0;

    decodedIRData.flags = IRDATA_FLAGS_IS_MSB_FIRST; // for print

#if defined(DECODE_NEC)
    IR_DEBUG_PRINTLN(F("Attempting old NEC decode"));
    if (decodeNECMSB(aResults)) {
        return true;
    }
#endif

#if defined(DECODE_SONY)
    IR_DEBUG_PRINTLN(F("Attempting old Sony decode"));
    if (decodeSonyMSB(aResults)) {
        return true;
    }
#endif

#if defined(DECODE_RC5)
    IR_DEBUG_PRINTLN(F("Attempting RC5 decode"));
    if (decodeRC5()) {
        aResults->bits = decodedIRData.numberOfBits;
        aResults->value = decodedIRData.decodedRawData;
        aResults->decode_type = RC5;

        return true;
    }
#endif

#if defined(DECODE_RC6)
    IR_DEBUG_PRINTLN(F("Attempting RC6 decode"));
    if (decodeRC6()) {
        aResults->bits = decodedIRData.numberOfBits;
        aResults->value = decodedIRData.decodedRawData;
        aResults->decode_type = RC6;
        return true;
    }
#endif

//    Removed bool IRrecv::decodePanasonicMSB(decode_results *aResults) since  implementations was wrong (wrong length), and nobody recognized it

#if defined(DECODE_LG)
    IR_DEBUG_PRINTLN(F("Attempting old LG decode"));
    if (decodeLGMSB(aResults)) {return true;}
#endif

#if defined(DECODE_JVC)
    IR_DEBUG_PRINTLN(F("Attempting old JVC decode"));
    if (decodeJVCMSB(aResults)) {
        return true;
    }
#endif

#if defined(DECODE_SAMSUNG)
    IR_DEBUG_PRINTLN(F("Attempting old SAMSUNG decode"));
    if (decodeSAMSUNG(aResults)) {
        return true;
    }
#endif

#if defined(DECODE_DENON)
    IR_DEBUG_PRINTLN(F("Attempting old Denon decode"));
    if (decodeDenonOld(aResults)) {
        return true;
    }
#endif

// decodeHash returns a hash on any input.
// Thus, it needs to be last in the list.
// If you add any decodes, add them before this.
    if (decodeHashOld(aResults)) {
        return true;
    }
// Throw away and start over
    resume();
    return false;
}

/** @}*/
#if defined(_IR_MEASURE_TIMING)
#undef _IR_MEASURE_TIMING
#endif
#if defined(LOCAL_TRACE)
#undef LOCAL_TRACE
#endif
#if defined(LOCAL_DEBUG)
#undef LOCAL_DEBUG
#endif
#endif // _IR_RECEIVE_HPP
