/*
 * 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-2021 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

/** \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, false);
#endif
}

IRrecv::IRrecv(uint8_t aReceivePin) {
    decodedIRData.rawDataPtr = &irparams; // for decodePulseDistanceData() etc.
    setReceivePin(aReceivePin);
#if !defined(NO_LED_FEEDBACK_CODE)
    setLEDFeedback(0, false);
#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(uint8_t aReceivePin, uint8_t aFeedbackLEDPin) {
    decodedIRData.rawDataPtr = &irparams; // for decodePulseDistanceData() etc.
    setReceivePin(aReceivePin);
#if !defined(NO_LED_FEEDBACK_CODE)
    setLEDFeedback(aFeedbackLEDPin, false);
#else
    (void) aFeedbackLEDPin;
#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(uint8_t aReceivePin, bool aEnableLEDFeedback, uint8_t aFeedbackLEDPin) {

    setReceivePin(aReceivePin);
#if !defined(NO_LED_FEEDBACK_CODE)
    setLEDFeedback(aFeedbackLEDPin, aEnableLEDFeedback);
#else
    (void) aEnableLEDFeedback;
    (void) aFeedbackLEDPin;
#endif

    enableIRIn();
}

/**
 * Sets / changes the receiver pin number
 */
void IRrecv::setReceivePin(uint8_t aReceivePinNumber) {
    irparams.IRReceivePin = aReceivePinNumber;
#if defined(__AVR__)
    irparams.IRReceivePinMask = digitalPinToBitMask(aReceivePinNumber);
    irparams.IRReceivePinPortInputRegister = portInputRegister(digitalPinToPort(aReceivePinNumber));
#endif
}

/**
 * Configures the timer and the state machine for IR reception.
 */
void IRrecv::start() {
    enableIRIn();
}

/**
 * Configures the timer and the state machine for IR reception.
 * @param aMicrosecondsToAddToGapCounter To compensate for microseconds the timer was stopped / disabled.
 */
void IRrecv::start(uint32_t aMicrosecondsToAddToGapCounter) {
    enableIRIn();
    noInterrupts();
    irparams.TickCounterForISR += aMicrosecondsToAddToGapCounter / MICROS_PER_TICK;
    interrupts();
}

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

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

/**
 * Configures the timer and the state machine for IR reception.
 */
void IRrecv::enableIRIn() {

    noInterrupts();

    // Setup pulse clock TickCounterForISR interrupt
    timerConfigForReceive();
    TIMER_ENABLE_RECEIVE_INTR;  // Timer interrupt enable
    TIMER_RESET_INTR_PENDING;   // NOP for most platforms

    // Initialize state machine state
    resume();
    interrupts(); // after resume to avoid running through STOP state 1 time before switching to IDLE

    // Set pin modes
    pinMode(irparams.IRReceivePin, INPUT);
}

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

/**
 * 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 state machine
 * Enable receiving of the next value
 */
void IRrecv::resume() {
    // 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) {
        // Copy overflow flag to decodedIRData.flags and reset it
        irparams.OverflowFlag = false;
        irparams.rawlen = 0; // otherwise we have OverflowFlag again at next ISR call
        decodedIRData.flags = IRDATA_FLAGS_WAS_OVERFLOW;
        IR_DEBUG_PRINTLN("Overflow happened");

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

    }
    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.
 * @return false if no IR receiver data available, true if data available. Results of decoding are stored in IrReceiver.decodedIRData.
 * The set of decoders used is determined by active definitions of the DECODE_<PROTOCOL> macros.
 */
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)
    IR_TRACE_PRINTLN("Attempting NEC decode");
    if (decodeNEC()) {
        return true;
    }
#endif

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

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

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

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

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

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

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

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

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

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

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

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

    /*
     * Try the universal decoder for pulse width or pulse distance protocols
     */
#if defined(DECODE_DISTANCE)
    IR_TRACE_PRINTLN("Attempting universal Distance decode");
    if (decodeDistance()) {
        return true;
    }
#endif

    /*
     * Last resort is the universal hash decode which always return true
     */
#if defined(DECODE_HASH)
    IR_TRACE_PRINTLN("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 width protocols. Currently only used for sony protocol.
 * The space (pause) has constant length, the length of the mark determines the bit value.
 *      Each bit looks like: MARK_1 + SPACE -> 1 or : MARK_0 + SPACE -> 0
 *
 * Input is     IrReceiver.decodedIRData.rawDataPtr->rawbuf[]
 * Output is    IrReceiver.decodedIRData.decodedRawData
 *
 * @param aStartOffset must point to a mark
 * @return true if decoding was successful
 */
bool IRrecv::decodePulseWidthData(uint8_t aNumberOfBits, uint8_t aStartOffset, uint16_t aOneMarkMicros, uint16_t aZeroMarkMicros,
        uint16_t aBitSpaceMicros, bool aMSBfirst) {

    uint16_t *tRawBufPointer = &decodedIRData.rawDataPtr->rawbuf[aStartOffset];
    uint32_t tDecodedData = 0;

    if (aMSBfirst) {
        for (uint_fast8_t i = 0; i < aNumberOfBits; i++) {
            // Check for variable length mark indicating a 0 or 1
            if (matchMark(*tRawBufPointer, aOneMarkMicros)) {
                tDecodedData = (tDecodedData << 1) | 1;
                IR_TRACE_PRINT('1');
            } else if (matchMark(*tRawBufPointer, aZeroMarkMicros)) {
                tDecodedData = (tDecodedData << 1) | 0;
                IR_TRACE_PRINT('0');
            } else {
                IR_DEBUG_PRINT(F("Mark="));
                IR_DEBUG_PRINT(*tRawBufPointer * MICROS_PER_TICK);
                IR_DEBUG_PRINT(F(" is not "));
                IR_DEBUG_PRINT(aOneMarkMicros);
                IR_DEBUG_PRINT(F(" or "));
                IR_DEBUG_PRINT(aZeroMarkMicros);
                IR_DEBUG_PRINT(' ');
                return false;
            }
            tRawBufPointer++;

            if (tRawBufPointer < &decodedIRData.rawDataPtr->rawbuf[decodedIRData.rawDataPtr->rawlen]) {
                // Assume that last space, which is not recorded, is correct, since we can not check it
                // Check for constant length space
                if (!matchSpace(*tRawBufPointer, aBitSpaceMicros)) {
                    IR_DEBUG_PRINT(F("Space="));
                    IR_DEBUG_PRINT(*tRawBufPointer * MICROS_PER_TICK);
                    IR_DEBUG_PRINT(F(" is not "));
                    IR_DEBUG_PRINT(aBitSpaceMicros);
                    IR_DEBUG_PRINT(' ');
                    return false;
                }
                tRawBufPointer++;
            }
        }
        IR_TRACE_PRINTLN("");
    } else {
        for (uint32_t tMask = 1UL; aNumberOfBits > 0; tMask <<= 1, aNumberOfBits--) {

            // Check for variable length mark indicating a 0 or 1
            if (matchMark(*tRawBufPointer, aOneMarkMicros)) {
                tDecodedData |= tMask; // set the bit
                IR_TRACE_PRINT('1');
            } else if (matchMark(*tRawBufPointer, aZeroMarkMicros)) {
                // do not set the bit
                IR_TRACE_PRINT('0');
            } else {
                IR_DEBUG_PRINT(F("Mark="));
                IR_DEBUG_PRINT(*tRawBufPointer * MICROS_PER_TICK);
                IR_DEBUG_PRINT(F(" is not "));
                IR_DEBUG_PRINT(aOneMarkMicros);
                IR_DEBUG_PRINT(F(" or "));
                IR_DEBUG_PRINT(aZeroMarkMicros);
                IR_DEBUG_PRINT(' ');
                return false;
            }
            tRawBufPointer++;

            if (tRawBufPointer < &decodedIRData.rawDataPtr->rawbuf[decodedIRData.rawDataPtr->rawlen]) {
                // Assume that last space, which is not recorded, is correct, since we can not check it
                // Check for constant length space
                if (!matchSpace(*tRawBufPointer, aBitSpaceMicros)) {
                    IR_DEBUG_PRINT(F("Space="));
                    IR_DEBUG_PRINT(*tRawBufPointer * MICROS_PER_TICK);
                    IR_DEBUG_PRINT(F(" is not "));
                    IR_DEBUG_PRINT(aBitSpaceMicros);
                    IR_DEBUG_PRINT(' ');
                    return false;
                }
                tRawBufPointer++;
            }
        }
        IR_TRACE_PRINTLN("");
    }
    decodedIRData.decodedRawData = tDecodedData;
    return true;
}

/**
 * Decode pulse distance protocols.
 * The mark (pulse) has constant length, the length of the space determines the bit value.
 * Each bit looks like: MARK + SPACE_1 -> 1
 *                 or : MARK + SPACE_0 -> 0
 *
 * Input is     IrReceiver.decodedIRData.rawDataPtr->rawbuf[]
 * Output is    IrReceiver.decodedIRData.decodedRawData
 *
 * @param aStartOffset must point to a mark
 * @return true if decoding was successful
 */
bool IRrecv::decodePulseDistanceData(uint8_t aNumberOfBits, uint8_t aStartOffset, uint16_t aBitMarkMicros, uint16_t aOneSpaceMicros,
        uint16_t aZeroSpaceMicros, bool aMSBfirst) {

    uint16_t *tRawBufPointer = &decodedIRData.rawDataPtr->rawbuf[aStartOffset];
    uint32_t tDecodedData = 0;

    if (aMSBfirst) {
        for (uint_fast8_t i = 0; i < aNumberOfBits; i++) {
            // Check for constant length mark
            if (!matchMark(*tRawBufPointer, aBitMarkMicros)) {
                IR_DEBUG_PRINT(F("Mark="));
                IR_DEBUG_PRINT(*tRawBufPointer * MICROS_PER_TICK);
                IR_DEBUG_PRINT(F(" is not "));
                IR_DEBUG_PRINT(aBitMarkMicros);
                IR_DEBUG_PRINT(' ');
                return false;
            }
            tRawBufPointer++;

            // Check for variable length space indicating a 0 or 1
            if (matchSpace(*tRawBufPointer, aOneSpaceMicros)) {
                tDecodedData = (tDecodedData << 1) | 1;
                IR_TRACE_PRINT('1');
            } else if (matchSpace(*tRawBufPointer, aZeroSpaceMicros)) {
                tDecodedData = (tDecodedData << 1) | 0;
                IR_TRACE_PRINT('0');
            } else {
                IR_DEBUG_PRINT(F("Space="));
                IR_DEBUG_PRINT(*tRawBufPointer * MICROS_PER_TICK);
                IR_DEBUG_PRINT(F(" is not "));
                IR_DEBUG_PRINT(aOneSpaceMicros);
                IR_DEBUG_PRINT(F(" or "));
                IR_DEBUG_PRINT(aZeroSpaceMicros);
                IR_DEBUG_PRINT(' ');
                return false;
            }
            tRawBufPointer++;
        }
        IR_TRACE_PRINTLN("");

    } else {
        for (uint32_t tMask = 1UL; aNumberOfBits > 0; tMask <<= 1, aNumberOfBits--) {
            // Check for constant length mark
            if (!matchMark(*tRawBufPointer, aBitMarkMicros)) {
                IR_DEBUG_PRINT(F("Mark="));
                IR_DEBUG_PRINT(*tRawBufPointer * MICROS_PER_TICK);
                IR_DEBUG_PRINT(F(" is not "));
                IR_DEBUG_PRINT(aBitMarkMicros);
                IR_DEBUG_PRINT(' ');
                return false;
            }
            tRawBufPointer++;

            // Check for variable length space indicating a 0 or 1
            if (matchSpace(*tRawBufPointer, aOneSpaceMicros)) {
                tDecodedData |= tMask; // set the bit
                IR_TRACE_PRINT('1');
            } else if (matchSpace(*tRawBufPointer, aZeroSpaceMicros)) {
                // do not set the bit
                IR_TRACE_PRINT('0');
            } else {
                IR_DEBUG_PRINT(F("Space="));
                IR_DEBUG_PRINT(*tRawBufPointer * MICROS_PER_TICK);
                IR_DEBUG_PRINT(F(" is not "));
                IR_DEBUG_PRINT(aOneSpaceMicros);
                IR_DEBUG_PRINT(F(" or "));
                IR_DEBUG_PRINT(aZeroSpaceMicros);
                IR_DEBUG_PRINT(' ');
                return false;
            }
            tRawBufPointer++;
        }
        IR_TRACE_PRINTLN("");
    }
    decodedIRData.decodedRawData = tDecodedData;
    return true;
}

/*
 * Static variables for the getBiphaselevel function
 */
uint8_t sBiphaseDecodeRawbuffOffset; // Index into raw timing array
uint16_t sCurrentTimingIntervals; // Number of aBiphaseTimeUnit intervals of the current rawbuf[sBiphaseDecodeRawbuffOffset] timing.
uint8_t sUsedTimingIntervals;       // Number of already used intervals of sCurrentTimingIntervals.
uint16_t sBiphaseTimeUnit;

void IRrecv::initBiphaselevel(uint8_t aRCDecodeRawbuffOffset, uint16_t aBiphaseTimeUnit) {
    sBiphaseDecodeRawbuffOffset = aRCDecodeRawbuffOffset;
    sBiphaseTimeUnit = aBiphaseTimeUnit;
    sUsedTimingIntervals = 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).
 */
uint8_t IRrecv::getBiphaselevel() {
    uint8_t tLevelOfCurrentInterval; // 0 (SPACE) or 1 (MARK)

    if (sBiphaseDecodeRawbuffOffset >= decodedIRData.rawDataPtr->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 (sUsedTimingIntervals == 0) {
        uint16_t tCurrentTimingWith = decodedIRData.rawDataPtr->rawbuf[sBiphaseDecodeRawbuffOffset];
        uint16_t tMarkExcessCorrection = (tLevelOfCurrentInterval == MARK) ? MARK_EXCESS_MICROS : -MARK_EXCESS_MICROS;

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

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

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

    IR_TRACE_PRINTLN(tLevelOfCurrentInterval);

    return tLevelOfCurrentInterval;
}

#if defined(DECODE_HASH)
/**********************************************************************************************************************
 * Internal Hash decode function
 **********************************************************************************************************************/
/**
 * Compare two (tick) values
 * 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
 */
uint8_t IRrecv::compare(unsigned int oldval, unsigned int newval) {
    if (newval * 10 < oldval * 8) {
        return 0;
    }
    if (oldval * 10 < newval * 8) {
        return 2;
    }
    return 1;
}

#define FNV_PRIME_32 16777619   ///< used for decodeHash()
#define FNV_BASIS_32 2166136261 ///< used for decodeHash()

/**
 * 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 signals, and see if each one
 * is shorter (0), the same length (1), or longer (2) than the previous.
 * Do the same with the SPACE signals.  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.rawDataPtr->rawlen < 6) {
        return false;
    }
#if RAW_BUFFER_LENGTH <= 254        // saves around 75 bytes program space and speeds up ISR
    uint8_t i;
#else
    uint16_t i;
#endif
    for (i = 1; (i + 2) < decodedIRData.rawDataPtr->rawlen; i++) {
        uint8_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 (unsigned int i = 1; (i + 2) < aResults->rawlen; i++) {
        uint8_t value = compare(aResults->rawbuf[i], aResults->rawbuf[i + 2]);
        // 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;
}
#endif // DECODE_HASH

/**********************************************************************************************************************
 * Match functions
 **********************************************************************************************************************/
/**
 * Match function without compensating for marks exceeded or spaces shortened by demodulator hardware
 * Currently not used
 */
bool matchTicks(uint16_t aMeasuredTicks, uint16_t aMatchValueMicros) {
#ifdef 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)));
#ifdef 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) {
#ifdef 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)));
#ifdef 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) {
#ifdef 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)));
#ifdef 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)
 */
void CheckForRecordGapsMicros(Print *aSerial, IRData *aIRDataPtr) {
    /*
     * 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 (aIRDataPtr->protocol <= PULSE_WIDTH && aIRDataPtr->rawDataPtr->rawbuf[0] < RECORD_GAP_MICROS_WARNING_THRESHOLD) {
        aSerial->println();
        aSerial->print(F("Space between two detected transmission is greater than "));
        aSerial->print(RECORD_GAP_MICROS);
        aSerial->print(F(" but smaller than the minimal gap of "));
        aSerial->print(RECORD_GAP_MICROS_WARNING_THRESHOLD);
        aSerial->println(F(" known for a protocol."));
        aSerial->println(F("If you get unexpected results, try to increase the RECORD_GAP_MICROS in IRremote.h."));
        aSerial->println();
    }
}

/**********************************************************************************************************************
 * Print functions
 * Since a library should not allocate the "Serial" object, all functions require a pointer to a Print object.
 **********************************************************************************************************************/
void printActiveIRProtocols(Print *aSerial) {
#if defined(DECODE_NEC)
    aSerial->print(F("NEC, "));
#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_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)
    aSerial->print(F("Universal Distance, "));
#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

    }
/**
 * Internal function to print decoded result and flags in one line.
 * Ends with println().
 *
 * @param aSerial The Print object on which to write, for Arduino you can use &Serial.
 */
void printIRResultShort(Print *aSerial, IRData *aIRDataPtr, uint16_t aLeadingSpaceTicks) {
    aSerial->print(F("Protocol="));
    aSerial->print(getProtocolString(aIRDataPtr->protocol));
    if (aIRDataPtr->protocol == UNKNOWN) {
#if defined(DECODE_HASH)
        aSerial->print(F(" Hash=0x"));
        aSerial->print(aIRDataPtr->decodedRawData, HEX);
#endif
        aSerial->print(' ');
        aSerial->print((aIRDataPtr->rawDataPtr->rawlen + 1) / 2, DEC);
        aSerial->println(F(" bits received"));
    } else {
        /*
         * New decoders have address and command
         */
        aSerial->print(F(" Address=0x"));
        aSerial->print(aIRDataPtr->address, HEX);

        aSerial->print(F(" Command=0x"));
        aSerial->print(aIRDataPtr->command, HEX);

        if (aIRDataPtr->flags & IRDATA_FLAGS_EXTRA_INFO) {
            aSerial->print(F(" Extra=0x"));
            aSerial->print(aIRDataPtr->extra, HEX);
        }

        if (aIRDataPtr->flags & IRDATA_FLAGS_PARITY_FAILED) {
            aSerial->print(F(" Parity fail"));
        }

        if (aIRDataPtr->flags & IRDATA_TOGGLE_BIT_MASK) {
            aSerial->print(F(" Toggle=1"));
        }

        if (aIRDataPtr->flags & (IRDATA_FLAGS_IS_AUTO_REPEAT | IRDATA_FLAGS_IS_REPEAT)) {
            aSerial->print(' ');
            if (aIRDataPtr->flags & IRDATA_FLAGS_IS_AUTO_REPEAT) {
                aSerial->print(F("Auto-"));
            }
            aSerial->print(F("Repeat"));
            if (aLeadingSpaceTicks != 0) {
                aSerial->print(F(" gap="));
                aSerial->print((uint32_t) aLeadingSpaceTicks * MICROS_PER_TICK);
                aSerial->print(F("us"));
            }
        }

        /*
         * Print raw data
         */
        if (!(aIRDataPtr->flags & IRDATA_FLAGS_IS_REPEAT) || aIRDataPtr->decodedRawData != 0) {
            aSerial->print(F(" Raw-Data=0x"));
            aSerial->print(aIRDataPtr->decodedRawData, HEX);

            /*
             * Print number of bits processed
             */
            aSerial->print(' ');
            aSerial->print(aIRDataPtr->numberOfBits, DEC);
            aSerial->print(F(" bits"));

            if (aIRDataPtr->flags & IRDATA_FLAGS_IS_MSB_FIRST) {
                aSerial->println(F(" MSB first"));
            } else {
                aSerial->println(F(" LSB first"));
            }

        } else {
            aSerial->println();
        }

        CheckForRecordGapsMicros(aSerial, aIRDataPtr);
    }
}

/**
 * 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.
 */
void IRrecv::printIRResultShort(Print *aSerial) {
    ::printIRResultShort(aSerial, &decodedIRData, decodedIRData.rawDataPtr->rawbuf[0]);
}

/**
 * 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"));
        aSerial->print(decodedIRData.decodedRawData, HEX);
#endif
        aSerial->print(' ');
        aSerial->print((decodedIRData.rawDataPtr->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"));
        aSerial->print(decodedIRData.decodedRawData, HEX);

        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.
 */
void IRrecv::printIRResultRawFormatted(Print *aSerial, bool aOutputMicrosecondsInsteadOfTicks) {
    // Print Raw data
    aSerial->print(F("rawData["));
    aSerial->print(decodedIRData.rawDataPtr->rawlen, DEC);
    aSerial->println(F("]: "));

    uint32_t tDurationMicros;

    /*
     * Print initial gap
     */
    if (aOutputMicrosecondsInsteadOfTicks) {
        tDurationMicros = (uint32_t) decodedIRData.rawDataPtr->rawbuf[0] * MICROS_PER_TICK;
    } else {
        tDurationMicros = decodedIRData.rawDataPtr->rawbuf[0];
    }
    aSerial->print(F("     -"));
    aSerial->println(tDurationMicros, DEC);
#if RAW_BUFFER_LENGTH <= 254        // saves around 75 bytes program space and speeds up ISR
    uint8_t i;
#else
    uint16_t i;
#endif
    for (i = 1; i < decodedIRData.rawDataPtr->rawlen; i++) {
        if (aOutputMicrosecondsInsteadOfTicks) {
            tDurationMicros = decodedIRData.rawDataPtr->rawbuf[i] * MICROS_PER_TICK;
        } else {
            tDurationMicros = decodedIRData.rawDataPtr->rawbuf[i];
        }
        if (!(i & 1)) {  // even
            aSerial->print('-');
        } else {  // odd
            aSerial->print(F("     +"));
        }
        if (tDurationMicros < 1000) {
            aSerial->print(' ');
        }
        if (tDurationMicros < 100) {
            aSerial->print(' ');
        }
        if (tDurationMicros < 10) {
            aSerial->print(' ');
        }
        aSerial->print(tDurationMicros, DEC);

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

        if (!(i % 8)) {
            aSerial->println("");
        }
    }
    aSerial->println("");                    // Newline
}

/**
 * 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 "));         // variable type
        aSerial->print(F("rawData["));          // array name
    } else {
        aSerial->print(F("uint8_t "));          // variable type
        aSerial->print(F("rawTicks["));         // array name
    }

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

// Dump data
#if RAW_BUFFER_LENGTH <= 254        // saves around 75 bytes program space and speeds up ISR
    uint8_t i;
#else
    uint16_t i;
#endif
    for (i = 1; i < decodedIRData.rawDataPtr->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 {
            uint16_t tTicks = (tDuration + (MICROS_PER_TICK / 2)) / MICROS_PER_TICK;
            tTicks = (tTicks > 0xFF) ? 0xFF : tTicks; // safety net
            aSerial->print(tTicks);
        }
        if (i + 1 < decodedIRData.rawDataPtr->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#
#if RAW_BUFFER_LENGTH <= 254        // saves around 75 bytes program space and speeds up ISR
    uint8_t i;
#else
    uint16_t i;
#endif
    for (i = 1; i < decodedIRData.rawDataPtr->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;
        }

        uint16_t tTicks = (tDuration + (MICROS_PER_TICK / 2)) / MICROS_PER_TICK;
        *aArrayPtr = (tTicks > 0xFF) ? 0xFF : tTicks; // safety net
        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 data
        aSerial->print(F("uint32_t data = 0x"));
        aSerial->print(decodedIRData.decodedRawData, HEX);
        aSerial->println(';');
        aSerial->println();
    }
}

const __FlashStringHelper* getProtocolString(decode_type_t aProtocol) {
    switch (aProtocol) {
    default:
    case UNKNOWN:
        return (F("UNKNOWN"));
        break;
    case DENON:
        return (F("DENON"));
        break;
    case SHARP:
        return (F("SHARP"));
        break;
    case JVC:
        return (F("JVC"));
        break;
    case LG:
        return (F("LG"));
        break;
    case LG2:
        return (F("LG2"));
        break;
    case NEC:
        return (F("NEC"));
        break;
    case PANASONIC:
        return (F("PANASONIC"));
        break;
    case KASEIKYO:
        return (F("KASEIKYO"));
        break;
    case KASEIKYO_DENON:
        return (F("KASEIKYO_DENON"));
        break;
    case KASEIKYO_SHARP:
        return (F("KASEIKYO_SHARP"));
        break;
    case KASEIKYO_JVC:
        return (F("KASEIKYO_JVC"));
        break;
    case KASEIKYO_MITSUBISHI:
        return (F("KASEIKYO_MITSUBISHI"));
        break;
    case RC5:
        return (F("RC5"));
        break;
    case RC6:
        return (F("RC6"));
        break;
    case SAMSUNG:
        return (F("SAMSUNG"));
        break;
    case SONY:
        return (F("SONY"));
        break;
    case ONKYO:
        return (F("ONKYO"));
        break;
    case APPLE:
        return (F("APPLE"));
        break;
    case PULSE_DISTANCE:
        return (F("PULSE_DISTANCE"));
        break;
    case PULSE_WIDTH:
        return (F("PULSE_WIDTH"));
        break;
#if !defined(EXCLUDE_EXOTIC_PROTOCOLS)
    case BOSEWAVE:
        return (F("BOSEWAVE"));
        break;
    case LEGO_PF:
        return (F("LEGO_PF"));
        break;
    case MAGIQUEST:
        return (F("MAGIQUEST"));
        break;
    case WHYNTER:
        return (F("WHYNTER"));
        break;
#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
 *
 **********************************************************************************************************************/
//#define _IR_MEASURE_TIMING
//#define _IR_TIMING_TEST_PIN 7 // do not forget to execute: "pinMode(_IR_TIMING_TEST_PIN, OUTPUT);" if activated by line above
#if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN)
#include "digitalWriteFast.h"
#endif
#if defined(TIMER_INTR_NAME)
ISR (TIMER_INTR_NAME) // for ISR definitions
#else
ISR () // for functions definitions which are called by separate (board specific) ISR
#endif
{
#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

    TIMER_RESET_INTR_PENDING;// reset TickCounterForISR interrupt flag if required (currently only for Teensy and ATmega4809)

// 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
    uint8_t tIRInputLevel = (uint8_t) digitalRead(irparams.IRReceivePin);
#endif

    /*
     * Increase TickCounter and clip it at maximum 0xFFFF / 3.2 seconds at 50 us ticks
     */
    if (irparams.TickCounterForISR < 0xFFFF) {
        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) { // In the middle of a gap or 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) {
                // Gap just ended; Record gap duration + start recording transmission
                // Initialize all state machine variables
#if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN)
//                digitalWriteFast(_IR_TIMING_TEST_PIN, HIGH); // 2 clock cycles
#endif
                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; Record time
#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;
            irparams.StateForISR = IR_REC_STATE_SPACE;
            irparams.TickCounterForISR = 0;
        }

    } else if (irparams.StateForISR == IR_REC_STATE_SPACE) {  // Timing space
        if (tIRInputLevel == INPUT_MARK) {  // Space just ended; Record time
            if (irparams.rawlen >= RAW_BUFFER_LENGTH) {
                // Flag up a read OverflowFlag; Stop the state machine
                irparams.OverflowFlag = true;
                irparams.StateForISR = IR_REC_STATE_STOP;
            } 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;
                irparams.StateForISR = IR_REC_STATE_MARK;
            }
            irparams.TickCounterForISR = 0;

        } else if (irparams.TickCounterForISR > RECORD_GAP_TICKS) {
            /*
             * 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;
        }
    } 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) {
        setFeedbackLED(tIRInputLevel == INPUT_MARK);
    }
#endif

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

/**********************************************************************************************************************
 * The DEPRECATED decode function with parameter aResults ONLY for backwards compatibility!
 * This function calls the old MSB first decoders and fills only the 3 variables:
 * aResults->value
 * aResults->bits
 * aResults->decode_type
 **********************************************************************************************************************/
bool IRrecv::decode(decode_results *aResults) {
    static bool sDeprecationMessageSent = false;

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

    if (!sDeprecationMessageSent) {
#if !(defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__))
        Serial.println(
                "The function decode(&results)) is deprecated and may not work as expected! Just use decode() without a parameter and IrReceiver.decodedIRData.<fieldname> .");
#endif
        sDeprecationMessageSent = true;
    }

    // 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("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("Attempting old NEC decode");
    if (decodeNECMSB(aResults)) {
        return true ;
    }
#endif

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

//#if defined(DECODE_MITSUBISHI)
//    IR_DEBUG_PRINTLN("Attempting Mitsubishi decode");
//    if (decodeMitsubishi(results))  return true ;
//#endif

#if defined(DECODE_RC5)
    IR_DEBUG_PRINTLN("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("Attempting RC6 decode");
    if (decodeRC6())  {
        aResults->bits = decodedIRData.numberOfBits;
        aResults->value = decodedIRData.decodedRawData;
        aResults->decode_type = RC6;
        return true ;
    }
#endif

#if defined( DECODE_PANASONIC)
    IR_DEBUG_PRINTLN("Attempting old Panasonic decode");
    if (decodePanasonicMSB(aResults)) {
        return true ;
    }
#endif

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

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

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

//#if defined(DECODE_WHYNTER)
//    IR_DEBUG_PRINTLN("Attempting Whynter decode");
//    if (decodeWhynter(results))  return true ;
//#endif

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

//#if defined(DECODE_LEGO_PF)
//    IR_DEBUG_PRINTLN("Attempting Lego Power Functions");
//    if (decodeLegoPowerFunctions(results))  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;
}

/** @}*/
#endif // #ifndef IR_RECEIVE_HPP
#pragma once
