#pragma once

#include "IR_Encoder.h"
#include "IrTxBsrrWave.h"

#if defined(ARDUINO_ARCH_STM32) && defined(STM32G4xx)

#if defined(_MSC_VER)
#define IRPROTO_DMA_PRAGMA_MESSAGE(text) __pragma(message(text))
#else
#define IRPROTO_DMA_PRAGMA_MESSAGE(text) _Pragma(#text)
#endif

IRPROTO_DMA_PRAGMA_MESSAGE(message("[IR-protocol] TX path available: built-in DMA"))

#include <Arduino.h>
#include <HardwareTimer.h>

#if defined(__GNUC__)
#define IR_DMA_TX_HOT __attribute__((always_inline)) inline
#else
#define IR_DMA_TX_HOT inline
#endif

#include "stm32g4xx_hal.h"

/**
 * STM32G4: ИК TX через DMA в GPIO BSRR, такт от TIM UPDATE (carrierFrec × IR_Encoder::carrierMultiply()).
 *
 * Число слотов потоков — параметр шаблона (без макросов), например IrDmaTxStm32<2>.
 * По умолчанию: IrDmaTxStm32<> ≡ irproto::kDefaultDmaTxMaxStreams (см. IR_config.h).
 *
 * Контракт: ref/IR_DMA_TX_backend.md
 */
template<size_t MaxStreams = irproto::kDefaultDmaTxMaxStreams>
class IrDmaTxStm32 {
    static_assert(MaxStreams >= 1U, "IrDmaTxStm32: MaxStreams >= 1");

public:
    struct StreamCfg {
        DMA_Channel_TypeDef* instance = nullptr;
        IRQn_Type irq = IRQn_Type(0);
        uint32_t dmamuxRequest = 0;
        IR_Encoder* enc = nullptr;

        uint32_t* dmaWords = nullptr;
        uint16_t dmaWordCount = 0;

        IrTxGateRun* gateRuns = nullptr;
        size_t maxGateRuns = 0;
    };

    struct Config {
        HardwareTimer* timer = nullptr;
        uint8_t streamCount = 0;
        StreamCfg streams[MaxStreams];
    };

    bool begin(const Config& cfg) {
        cfg_ = cfg;
        streamCount_ = cfg.streamCount;

        if (cfg_.timer == nullptr || streamCount_ == 0) return false;
        if (streamCount_ > MaxStreams) return false;

        htim_ = cfg_.timer->getHandle();
        if (htim_ == nullptr) return false;

        for (uint8_t i = 0; i < streamCount_; i++) {
            const StreamCfg& sc = cfg_.streams[i];
            if (sc.enc == nullptr || sc.instance == nullptr) return false;
            if (sc.dmaWords == nullptr || sc.dmaWordCount < 2U) return false;
            if ((sc.dmaWordCount & 1U) != 0U) return false;
            if (sc.gateRuns == nullptr || sc.maxGateRuns == 0U) return false;
        }

        __HAL_RCC_DMA1_CLK_ENABLE();
        __HAL_RCC_DMAMUX1_CLK_ENABLE();

        for (uint8_t i = 0; i < streamCount_; i++) {
            const StreamCfg& sc = cfg_.streams[i];
            if (!initStream(streams_[i], sc)) {
                return false;
            }
        }

        s_instance = this;
        activeCount_ = 0;

        for (uint8_t i = 0; i < streamCount_; i++) {
            HAL_NVIC_EnableIRQ(streams_[i].dmaIrq);
        }

        return true;
    }

    static IrDmaTxStm32<MaxStreams>* instance() {
        return s_instance;
    }

    bool busy() const {
        if (streamCount_ == 0) return false;
        for (uint8_t i = 0; i < streamCount_; i++) {
            if (!streams_[i].active) return false;
        }
        return true;
    }

    IR_SendStatus start(IR_Encoder* enc, const uint8_t* packet, uint8_t len) {
        if (enc == nullptr) return IR_SendStatus::ExternalNoStream;
        for (uint8_t i = 0; i < streamCount_; i++) {
            if (streams_[i].enc == enc) {
                return startStream(streams_[i], packet, len);
            }
        }
        return IR_SendStatus::ExternalNoStream;
    }

    void irqForStream(size_t streamIndex) {
        if (streamIndex >= streamCount_) return;
        HAL_DMA_IRQHandler(&streams_[streamIndex].hdma);
    }

    DMA_HandleTypeDef* dmaHandle(size_t streamIndex) {
        if (streamIndex >= streamCount_) return nullptr;
        return &streams_[streamIndex].hdma;
    }

private:
    struct TxStream {
        DMA_HandleTypeDef hdma{};
        DMA_Channel_TypeDef* dmaInstance = nullptr;
        IRQn_Type dmaIrq = IRQn_Type(0);
        uint32_t dmamuxRequest = 0;

        IR_Encoder* enc = nullptr;
        GPIO_TypeDef* port = nullptr;
        uint16_t mask = 0;
        uint32_t setWord = 0;
        uint32_t resetWord = 0;

        uint32_t* dmaBuf = nullptr;
        uint16_t bufLen = 0;
        uint16_t halfLen = 0;

        IrTxGateRun* runs = nullptr;
        size_t maxRuns = 0;

        size_t runCount = 0;
        IrTxBsrrWave wave{};

        uint32_t totalTicks = 0;
        volatile uint32_t ticksOutput = 0;

        bool active = false;

        void resetWave() {
            wave.configure(setWord, resetWord, nullptr, 0, 2, 1);
            ticksOutput = 0;
            totalTicks = 0;
            runCount = 0;
        }

        IR_DMA_TX_HOT void fill(uint32_t* dst, uint16_t count) {
            wave.fill(dst, count);
        }

        void onHalf() {
            ticksOutput += halfLen;
            fill(&dmaBuf[0], halfLen);
        }

        void onComplete() {
            ticksOutput += halfLen;
            fill(&dmaBuf[halfLen], halfLen);
        }

        void onError() {}
    };

    static IrDmaTxStm32<MaxStreams>* s_instance;

    Config cfg_{};
    TIM_HandleTypeDef* htim_ = nullptr;

    TxStream streams_[MaxStreams]{};
    uint8_t streamCount_ = 0;

    volatile uint8_t activeCount_ = 0;

    static uint32_t u32ptr(const volatile void* p) {
        return (uint32_t)(uintptr_t)p;
    }

    void startTimerIfNeeded() {
        if (htim_ == nullptr) return;
        if (activeCount_ != 1) return;

        __HAL_TIM_DISABLE_DMA(htim_, TIM_DMA_UPDATE);
        __HAL_TIM_CLEAR_FLAG(htim_, TIM_FLAG_UPDATE);
        __HAL_TIM_SET_COUNTER(htim_, 0);
        __HAL_TIM_ENABLE_DMA(htim_, TIM_DMA_UPDATE);
        HAL_TIM_Base_Start(htim_);
    }

    void stopTimerIfIdle() {
        if (htim_ == nullptr) return;
        if (activeCount_ != 0) return;

        __HAL_TIM_DISABLE_DMA(htim_, TIM_DMA_UPDATE);
        HAL_TIM_Base_Stop(htim_);
    }

    static TxStream* streamFromDma(DMA_HandleTypeDef* hdma) {
        if (s_instance == nullptr || hdma == nullptr) return nullptr;
        for (uint8_t i = 0; i < s_instance->streamCount_; i++) {
            if (hdma == &s_instance->streams_[i].hdma) {
                return &s_instance->streams_[i];
            }
        }
        return nullptr;
    }

    static void dmaHalfCpltCb(DMA_HandleTypeDef* hdma) {
        auto* s = streamFromDma(hdma);
        if (s == nullptr || !s->active) return;
        s->onHalf();
        if (s_instance != nullptr && s->ticksOutput >= s->totalTicks) {
            s_instance->stopStream(*s);
        }
    }

    static void dmaCpltCb(DMA_HandleTypeDef* hdma) {
        auto* s = streamFromDma(hdma);
        if (s == nullptr || !s->active) return;
        s->onComplete();
        if (s_instance != nullptr && s->ticksOutput >= s->totalTicks) {
            s_instance->stopStream(*s);
        }
    }

    static void dmaErrorCb(DMA_HandleTypeDef* hdma) {
        auto* s = streamFromDma(hdma);
        if (s == nullptr) return;
        s->onError();
        if (s_instance != nullptr) {
            s_instance->stopStream(*s);
        }
    }

    bool initStream(TxStream& s, const StreamCfg& chCfg) {
        s.enc = chCfg.enc;
        s.dmaInstance = chCfg.instance;
        s.dmaIrq = chCfg.irq;
        s.dmamuxRequest = chCfg.dmamuxRequest;

        s.dmaBuf = chCfg.dmaWords;
        s.bufLen = chCfg.dmaWordCount;
        s.halfLen = (uint16_t)(chCfg.dmaWordCount / 2U);
        s.runs = chCfg.gateRuns;
        s.maxRuns = chCfg.maxGateRuns;

        s.port = (s.enc != nullptr) ? s.enc->getPort() : nullptr;
        s.mask = (s.enc != nullptr) ? s.enc->getPinMask() : 0;
        s.setWord = (uint32_t)s.mask;
        s.resetWord = ((uint32_t)s.mask) << 16;

        s.resetWave();

        s.hdma.Instance = s.dmaInstance;
        s.hdma.Init.Request = s.dmamuxRequest;
        s.hdma.Init.Direction = DMA_MEMORY_TO_PERIPH;
        s.hdma.Init.PeriphInc = DMA_PINC_DISABLE;
        s.hdma.Init.MemInc = DMA_MINC_ENABLE;
        s.hdma.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
        s.hdma.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
        s.hdma.Init.Mode = DMA_CIRCULAR;
        s.hdma.Init.Priority = DMA_PRIORITY_HIGH;

        HAL_DMA_DeInit(&s.hdma);
        if (HAL_DMA_Init(&s.hdma) != HAL_OK) {
            return false;
        }

        s.hdma.XferHalfCpltCallback = dmaHalfCpltCb;
        s.hdma.XferCpltCallback = dmaCpltCb;
        s.hdma.XferErrorCallback = dmaErrorCb;
        s.hdma.XferAbortCallback = nullptr;

        return true;
    }

    IR_SendStatus startStream(TxStream& s, const uint8_t* packet, uint8_t len) {
        if (s.enc == nullptr || s.port == nullptr || s.mask == 0) return IR_SendStatus::ExternalInvalidConfig;
        if (s.active) return IR_SendStatus::EncoderBusy;
        if (s.dmaBuf == nullptr || s.bufLen < 2 || s.halfLen == 0) return IR_SendStatus::ExternalInvalidConfig;
        if (s.runs == nullptr || s.maxRuns == 0) return IR_SendStatus::ExternalInvalidConfig;

        s.resetWave();

        const uint16_t mult = IR_Encoder::carrierMultiply();
        s.runCount = IR_Encoder::buildPhysicalGateRuns(packet, len, s.runs, s.maxRuns, mult);
        if (s.runCount == 0) return IR_SendStatus::BuildGateRunsFailed;

        uint32_t total = 0;
        for (size_t i = 0; i < s.runCount; i++) total += s.runs[i].lenTicks;
        s.totalTicks = total;

        uint16_t pwr = mult / 2U;
        if (s.enc != nullptr) {
            const uint16_t want = s.enc->powerNumerator();
            const uint16_t cap = IR_Encoder::maxPowerNumerator();
            pwr = (want > cap) ? cap : want;
        }

        s.wave.configure(s.setWord, s.resetWord, s.runs, s.runCount, mult, pwr);

        s.fill(&s.dmaBuf[0], s.bufLen);

        s.port->BSRR = s.resetWord;

        const uint32_t dst = u32ptr(&s.port->BSRR);
        if (HAL_DMA_Start_IT(&s.hdma, (uint32_t)(uintptr_t)s.dmaBuf, dst, s.bufLen) != HAL_OK) {
            return IR_SendStatus::DmaStartFailed;
        }

        s.active = true;
        activeCount_++;
        startTimerIfNeeded();
        return IR_SendStatus::Success;
    }

    void stopStream(TxStream& s) {
        if (!s.active) return;

        s.active = false;
        HAL_DMA_Abort_IT(&s.hdma);

        if (s.port != nullptr) {
            s.port->BSRR = s.resetWord;
        }

        if (s.enc != nullptr) {
            s.enc->externalFinishSend();
        }

        if (activeCount_ > 0) activeCount_--;
        stopTimerIfIdle();
    }
};

template<size_t MaxStreams>
IrDmaTxStm32<MaxStreams>* IrDmaTxStm32<MaxStreams>::s_instance = nullptr;

inline void IrDmaTxStm32_onDmaHandle(DMA_HandleTypeDef* hdma) {
    HAL_DMA_IRQHandler(hdma);
}

#elif defined(ARDUINO_ARCH_STM32)

#error "IrDmaTxStm32: добавьте ветку HAL для вашей серии STM32 (сейчас только STM32G4xx)."

#else

template<size_t MaxStreams = irproto::kDefaultDmaTxMaxStreams>
class IrDmaTxStm32 {};

#endif
