/*
 *  TinyIRSender.hpp
 *
 *  Sends IR protocol data of NEC and FAST protocol using bit banging.
 *  NEC is the protocol of most cheap remote controls for Arduino.
 *
 * The FAST protocol is a proprietary modified JVC protocol without address, with parity and with a shorter header.
 *  FAST Protocol characteristics:
 * - Bit timing is like NEC or JVC
 * - The header is shorter, 3156 vs. 12500
 * - No address and 16 bit data, interpreted as 8 bit command and 8 bit inverted command,
 *     leading to a fixed protocol length of (6 + (16 * 3) + 1) * 526 = 55 * 526 = 28930 microseconds or 29 ms.
 * - Repeats are sent as complete frames but in a 50 ms period / with a 21 ms distance.
 *
 *
 *  This file is part of IRMP https://github.com/IRMP-org/IRMP.
 *  This file is part of Arduino-IRremote https://github.com/Arduino-IRremote/Arduino-IRremote.
 *
 ************************************************************************************
 * MIT License
 *
 * Copyright (c) 2022-2025 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 _TINY_IR_SENDER_HPP
#define _TINY_IR_SENDER_HPP

#include <Arduino.h>

//#define ENABLE_NEC2_REPEATS // Instead of sending / receiving the NEC special repeat code, send / receive the original frame for repeat.

#if defined(DEBUG)
#define LOCAL_DEBUG
#else
//#define LOCAL_DEBUG // This enables debug output only for this file
#endif
//#define NO_LED_SEND_FEEDBACK_CODE     // Disables the LED feedback code for receive.
//#define IR_FEEDBACK_LED_PIN     12    // Use this, to disable use of LED_BUILTIN definition for IR_FEEDBACK_LED_PIN
#include "TinyIR.h" // Defines protocol timings

#include "digitalWriteFast.h"
/** \addtogroup TinySender Minimal sender for NEC and FAST protocol
 * @{
 */

#if !defined(IR_SEND_PIN)
#warning "IR_SEND_PIN is not defined, so it is set to 3"
#define IR_SEND_PIN    3
#endif
#if !defined(NO_LED_SEND_FEEDBACK_CODE)
#define LED_SEND_FEEDBACK_CODE // Resolve the double negative
#endif

/*
 * Generate 38 kHz IR signal by bit banging
 */
void sendMark(uint8_t aSendPin, unsigned int aMarkMicros) {
    unsigned long tStartMicros = micros();
    unsigned long tNextPeriodEnding = tStartMicros;
    unsigned long tMicros;
    do {
        /*
         * Generate pulse
         */
        noInterrupts(); // do not let interrupts extend the short on period
        digitalWriteFast(aSendPin, HIGH);
        delayMicroseconds(8); // 8 us for a 30 % duty cycle for 38 kHz
        digitalWriteFast(aSendPin, LOW);
        interrupts(); // Enable interrupts - to keep micros correct- for the longer off period 3.4 us until receive ISR is active (for 7 us + pop's)

        /*
         * PWM pause timing and end check
         * Minimal pause duration is 4.3 us
         */
        tNextPeriodEnding += 26; // for 38 kHz
        do {
            tMicros = micros(); // we have only 4 us resolution for AVR @16MHz
            /*
             * Exit the forever loop if aMarkMicros has reached
             */
            unsigned int tDeltaMicros = tMicros - tStartMicros;
#if defined(__AVR__)
            // Just getting variables and check for end condition takes minimal 3.8 us
            if (tDeltaMicros >= aMarkMicros - (112 / (F_CPU / MICROS_IN_ONE_SECOND))) { // To compensate for call duration - 112 is an empirical value
#else
                if (tDeltaMicros >= aMarkMicros) {
    #endif
                return;
            }
        } while (tMicros < tNextPeriodEnding);
    } while (true);
}

/*
 * Send NEC with 16 bit address and command, even if aCommand < 0x100 (I.E. ONKYO)
 * @param aAddress  - The 16 bit address to send.
 * @param aCommand  - The 16 bit command to send.
 * @param aNumberOfRepeats  - Number of repeats send at a period of 110 ms.
 * @param aSendNEC2Repeats - Instead of sending the NEC special repeat code, send the original frame for repeat.
 */
void sendONKYO(uint8_t aSendPin, uint16_t aAddress, uint16_t aCommand, uint_fast8_t aNumberOfRepeats, bool aSendNEC2Repeats) {
    pinModeFast(aSendPin, OUTPUT);

#if !defined(NO_LED_SEND_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
    pinModeFast(IR_FEEDBACK_LED_PIN, OUTPUT);
#  if defined(FEEDBACK_LED_IS_ACTIVE_LOW)
    digitalWriteFast(IR_FEEDBACK_LED_PIN, LOW);
#  else
    digitalWriteFast(IR_FEEDBACK_LED_PIN, HIGH);
#  endif
#endif

    uint_fast8_t tNumberOfCommands = aNumberOfRepeats + 1;
    while (tNumberOfCommands > 0) {
        unsigned long tStartOfFrameMillis = millis();

        sendMark(aSendPin, NEC_HEADER_MARK);
        if ((!aSendNEC2Repeats) && (tNumberOfCommands < aNumberOfRepeats + 1)) {
            // send the NEC special repeat
            delayMicroseconds (NEC_REPEAT_HEADER_SPACE); // - 2250
        } else {
            // send header
            delayMicroseconds (NEC_HEADER_SPACE);
            LongUnion tData;
            tData.UWord.LowWord = aAddress;
            tData.UWord.HighWord = aCommand;
            // Send data
            for (uint_fast8_t i = 0; i < NEC_BITS; ++i) {
                sendMark(aSendPin, NEC_BIT_MARK); // constant mark length
                if (tData.ULong & 1) {
                    delayMicroseconds (NEC_ONE_SPACE);
                } else {
                    delayMicroseconds (NEC_ZERO_SPACE);
                }
                tData.ULong >>= 1; // shift command for next bit
            }
        }        // send stop bit
        sendMark(aSendPin, NEC_BIT_MARK);

        tNumberOfCommands--;
        // skip last delay!
        if (tNumberOfCommands > 0) {
            /*
             * Check and fallback for wrong RepeatPeriodMillis parameter. I.e the repeat period must be greater than each frame duration.
             */
            auto tFrameDurationMillis = millis() - tStartOfFrameMillis;
            if (NEC_REPEAT_PERIOD / 1000 > tFrameDurationMillis) {
                delay(NEC_REPEAT_PERIOD / 1000 - tFrameDurationMillis);
            }
        }
    }
#if defined(LED_SEND_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
    pinModeFast(IR_FEEDBACK_LED_PIN, OUTPUT);
#  if defined(FEEDBACK_LED_IS_ACTIVE_LOW)
    digitalWriteFast(IR_FEEDBACK_LED_PIN, HIGH);
#  else
    digitalWriteFast(IR_FEEDBACK_LED_PIN, LOW);
#  endif
#endif
}

/*
 * Send NEC with 8 or 16 bit address or command depending on the values of aAddress and aCommand.
 * @param aAddress  - If aAddress < 0x100 send 8 bit address and 8 bit inverted address, else send 16 bit address.
 * @param aCommand  - If aCommand < 0x100 send 8 bit command and 8 bit inverted command, else send 16 bit command.
 * @param aNumberOfRepeats  - Number of repeats send at a period of 110 ms.
 * @param aSendNEC2Repeats - Instead of sending the NEC special repeat code, send the original frame for repeat.
 */
void sendNECMinimal(uint8_t aSendPin, uint16_t aAddress, uint16_t aCommand, uint_fast8_t aNumberOfRepeats) {
    sendNEC(aSendPin, aAddress, aCommand, aNumberOfRepeats); // sendNECMinimal() is deprecated
}
void sendNEC(uint8_t aSendPin, uint16_t aAddress, uint16_t aCommand, uint_fast8_t aNumberOfRepeats, bool aSendNEC2Repeats) {
    pinModeFast(aSendPin, OUTPUT);

#if defined(LED_SEND_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
    pinModeFast(IR_FEEDBACK_LED_PIN, OUTPUT);
#  if defined(FEEDBACK_LED_IS_ACTIVE_LOW)
    digitalWriteFast(IR_FEEDBACK_LED_PIN, LOW);
#  else
    digitalWriteFast(IR_FEEDBACK_LED_PIN, HIGH);
#  endif
#endif

    uint_fast8_t tNumberOfCommands = aNumberOfRepeats + 1;
    while (tNumberOfCommands > 0) {
        unsigned long tStartOfFrameMillis = millis();

        sendMark(aSendPin, NEC_HEADER_MARK);
        if ((!aSendNEC2Repeats) && (tNumberOfCommands < aNumberOfRepeats + 1)) {
            // send the NEC special repeat
            delayMicroseconds (NEC_REPEAT_HEADER_SPACE); // - 2250
        } else {
            // send header
            delayMicroseconds (NEC_HEADER_SPACE);
            LongUnion tData;
            /*
             * The compiler is intelligent and removes the code for "(aAddress > 0xFF)" if we are called with an uint8_t address :-).
             * Using an uint16_t address requires additional 28 bytes program memory.
             */
            if (aAddress > 0xFF) {
                tData.UWord.LowWord = aAddress;
            } else {
                tData.UByte.LowByte = aAddress; // LSB first
                tData.UByte.MidLowByte = ~aAddress;
            }
            if (aCommand > 0xFF) {
                tData.UWord.HighWord = aCommand;
            } else {
                tData.UByte.MidHighByte = aCommand;
                tData.UByte.HighByte = ~aCommand; // LSB first
            }
            // Send data
            for (uint_fast8_t i = 0; i < NEC_BITS; ++i) {
                sendMark(aSendPin, NEC_BIT_MARK); // constant mark length

                if (tData.ULong & 1) {
                    delayMicroseconds (NEC_ONE_SPACE);
                } else {
                    delayMicroseconds (NEC_ZERO_SPACE);
                }
                tData.ULong >>= 1; // shift command for next bit
            }
        }        // send stop bit
        sendMark(aSendPin, NEC_BIT_MARK);

        tNumberOfCommands--;
        // skip last delay!
        if (tNumberOfCommands > 0) {
            /*
             * Check and fallback for wrong RepeatPeriodMillis parameter. I.e the repeat period must be greater than each frame duration.
             */
            auto tFrameDurationMillis = millis() - tStartOfFrameMillis;
            if (NEC_REPEAT_PERIOD / 1000 > tFrameDurationMillis) {
                delay(NEC_REPEAT_PERIOD / 1000 - tFrameDurationMillis);
            }
        }
    }
#if defined(LED_SEND_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
    pinModeFast(IR_FEEDBACK_LED_PIN, OUTPUT);
#  if defined(FEEDBACK_LED_IS_ACTIVE_LOW)
    digitalWriteFast(IR_FEEDBACK_LED_PIN, HIGH);
#  else
    digitalWriteFast(IR_FEEDBACK_LED_PIN, LOW);
#  endif
#endif
}

/*
 * Send Extended NEC with a forced 16 bit address and 8 or 16 bit command depending on the value of aCommand.
 * @param aAddress  - Send 16 bit address.
 * @param aCommand  - If aCommand < 0x100 send 8 bit command and 8 bit inverted command, else send 16 bit command.
 * @param aNumberOfRepeats  - Number of repeats send at a period of 110 ms.
 * @param aSendNEC2Repeats - Instead of sending the NEC special repeat code, send the original frame for repeat.
 */
void sendExtendedNEC(uint8_t aSendPin, uint16_t aAddress, uint16_t aCommand, uint_fast8_t aNumberOfRepeats, bool aSendNEC2Repeats) {
    pinModeFast(aSendPin, OUTPUT);

#if defined(LED_SEND_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
    pinModeFast(IR_FEEDBACK_LED_PIN, OUTPUT);
#  if defined(FEEDBACK_LED_IS_ACTIVE_LOW)
    digitalWriteFast(IR_FEEDBACK_LED_PIN, LOW);
#  else
    digitalWriteFast(IR_FEEDBACK_LED_PIN, HIGH);
#  endif
#endif

    uint_fast8_t tNumberOfCommands = aNumberOfRepeats + 1;
    while (tNumberOfCommands > 0) {
        unsigned long tStartOfFrameMillis = millis();

        sendMark(aSendPin, NEC_HEADER_MARK);
        if ((!aSendNEC2Repeats) && (tNumberOfCommands < aNumberOfRepeats + 1)) {
            // send the NEC special repeat
            delayMicroseconds (NEC_REPEAT_HEADER_SPACE); // - 2250
        } else {
            // send header
            delayMicroseconds (NEC_HEADER_SPACE);
            LongUnion tData;
            tData.UWord.LowWord = aAddress;
            if (aCommand > 0xFF) {
                tData.UWord.HighWord = aCommand;
            } else {
                tData.UByte.MidHighByte = aCommand;
                tData.UByte.HighByte = ~aCommand; // LSB first
            }
            // Send data
            for (uint_fast8_t i = 0; i < NEC_BITS; ++i) {
                sendMark(aSendPin, NEC_BIT_MARK); // constant mark length

                if (tData.ULong & 1) {
                    delayMicroseconds (NEC_ONE_SPACE);
                } else {
                    delayMicroseconds (NEC_ZERO_SPACE);
                }
                tData.ULong >>= 1; // shift command for next bit
            }
        }        // send stop bit
        sendMark(aSendPin, NEC_BIT_MARK);

        tNumberOfCommands--;
        // skip last delay!
        if (tNumberOfCommands > 0) {
            /*
             * Check and fallback for wrong RepeatPeriodMillis parameter. I.e the repeat period must be greater than each frame duration.
             */
            auto tFrameDurationMillis = millis() - tStartOfFrameMillis;
            if (NEC_REPEAT_PERIOD / 1000 > tFrameDurationMillis) {
                delay(NEC_REPEAT_PERIOD / 1000 - tFrameDurationMillis);
            }
        }
    }
#if defined(LED_SEND_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
    pinModeFast(IR_FEEDBACK_LED_PIN, OUTPUT);
#  if defined(FEEDBACK_LED_IS_ACTIVE_LOW)
    digitalWriteFast(IR_FEEDBACK_LED_PIN, HIGH);
#  else
    digitalWriteFast(IR_FEEDBACK_LED_PIN, LOW);
#  endif
#endif
}

/*
 * LSB first, send header, command, inverted command and stop bit
 */
void sendFast8BitAndParity(uint8_t aSendPin, uint8_t aCommand, uint_fast8_t aNumberOfRepeats) {
    sendFAST(aSendPin, aCommand, aNumberOfRepeats);
}

/*
 * LSB first, send header, 16 bit command or 8 bit command, inverted command and stop bit
 */
void sendFAST(uint8_t aSendPin, uint16_t aCommand, uint_fast8_t aNumberOfRepeats) {
    pinModeFast(aSendPin, OUTPUT);

#if defined(LED_SEND_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
    pinModeFast(IR_FEEDBACK_LED_PIN, OUTPUT);
#  if defined(FEEDBACK_LED_IS_ACTIVE_LOW)
    digitalWriteFast(IR_FEEDBACK_LED_PIN, LOW);
#  else
    digitalWriteFast(IR_FEEDBACK_LED_PIN, HIGH);
#  endif
#endif

    uint_fast8_t tNumberOfCommands = aNumberOfRepeats + 1;
    while (tNumberOfCommands > 0) {
        unsigned long tStartOfFrameMillis = millis();

        // send header
        sendMark(aSendPin, FAST_HEADER_MARK);
        delayMicroseconds(FAST_HEADER_SPACE);
        uint16_t tData;
        /*
         * The compiler is intelligent and removes the code for "(aCommand > 0xFF)" if we are called with an uint8_t command :-).
         * Using an uint16_t command requires additional 56 bytes program memory.
         */
        if (aCommand > 0xFF) {
            tData = aCommand;
        } else {
            tData = aCommand | (((uint8_t) (~aCommand)) << 8); // LSB first
        }
        // Send data
        for (uint_fast8_t i = 0; i < FAST_BITS; ++i) {
            sendMark(aSendPin, FAST_BIT_MARK); // constant mark length

            if (tData & 1) {
                delayMicroseconds(FAST_ONE_SPACE);
            } else {
                delayMicroseconds(FAST_ZERO_SPACE);
            }
            tData >>= 1; // shift command for next bit
        }
        // send stop bit
        sendMark(aSendPin, FAST_BIT_MARK);

        tNumberOfCommands--;
        // skip last delay!
        if (tNumberOfCommands > 0) {
            /*
             * Check and fallback for wrong RepeatPeriodMillis parameter. I.e the repeat period must be greater than each frame duration.
             */
            auto tFrameDurationMillis = millis() - tStartOfFrameMillis;
            if (FAST_REPEAT_PERIOD / 1000 > tFrameDurationMillis) {
                delay(FAST_REPEAT_PERIOD / 1000 - tFrameDurationMillis);
            }
        }
    }
#if defined(LED_SEND_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
    pinModeFast(IR_FEEDBACK_LED_PIN, OUTPUT);
#  if defined(FEEDBACK_LED_IS_ACTIVE_LOW)
    digitalWriteFast(IR_FEEDBACK_LED_PIN, HIGH);
#  else
    digitalWriteFast(IR_FEEDBACK_LED_PIN, LOW);
#  endif
#endif
}

/** @}*/

#if defined(LOCAL_DEBUG)
#undef LOCAL_DEBUG
#endif
#endif // _TINY_IR_SENDER_HPP
