#pragma once

// IWYU pragma: private

#include "platforms/esp/is_esp.h"

#include "fl/stl/stdint.h"
#include "fl/stl/static_assert.h"

#include "fl/stl/compiler_control.h"
#include "platforms/esp/esp_version.h"
#include "fl/system/fastpin.h"
#include "fl/stl/compiler_control.h"
#include "fl/stl/noexcept.h"

FL_DISABLE_WARNING_PUSH
FL_DISABLE_WARNING_DEPRECATED_REGISTER
namespace fl {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpragmas"
#pragma GCC diagnostic ignored "-Wvolatile"

template<u8 PIN, u32 MASK, bool VALIDPIN> class _ESPPIN {
public:
  typedef volatile u32 * port_ptr_t;
  typedef u32 port_t;

  static constexpr bool validpin() { return VALIDPIN; }

#ifndef GPIO_OUT1_REG
  static constexpr u32 GPIO_REG = GPIO_OUT_REG;
  static constexpr u32 GPIO_BIT_SET_REG = GPIO_OUT_W1TS_REG;
  static constexpr u32 GPIO_BIT_CLEAR_REG = GPIO_OUT_W1TC_REG;
  #else
  static constexpr u32 GPIO_REG = PIN < 32 ? GPIO_OUT_REG : GPIO_OUT1_REG;
  static constexpr u32 GPIO_BIT_SET_REG = PIN < 32 ? GPIO_OUT_W1TS_REG : GPIO_OUT1_W1TS_REG;
  static constexpr u32 GPIO_BIT_CLEAR_REG = PIN < 32 ? GPIO_OUT_W1TC_REG : GPIO_OUT1_W1TC_REG;
  #endif

  inline static void setOutput() FL_NOEXCEPT {
      FL_STATIC_ASSERT(validpin(), "This pin has been marked as an invalid pin, common reasons includes it being a ground pin, read only, or too noisy (e.g. hooked up to the uart).");
      pinMode(PIN, PinMode::Output);
  }
  inline static void setInput() { pinMode(PIN, PinMode::Input); }

  inline static void hi() FL_NOEXCEPT __attribute__ ((always_inline)) {
      *sport() = MASK;
  }

  inline static void lo() FL_NOEXCEPT __attribute__ ((always_inline)) {
      *cport() = MASK;
  }

  inline static void set(FASTLED_REGISTER port_t val) FL_NOEXCEPT __attribute__ ((always_inline)) {
      *port() = val;
  }

  inline static void strobe() __attribute__ ((always_inline)) { toggle(); toggle(); }

  inline static void toggle() FL_NOEXCEPT __attribute__ ((always_inline)) {
      *port() ^= MASK;
  }

  inline static void hi(FASTLED_REGISTER port_ptr_t port) __attribute__ ((always_inline)) { hi(); }
  inline static void lo(FASTLED_REGISTER port_ptr_t port) __attribute__ ((always_inline)) { lo(); }
  inline static void fastset(FASTLED_REGISTER port_ptr_t port, FASTLED_REGISTER port_t val) __attribute__ ((always_inline)) { *port = val; }

  inline static port_t hival() FL_NOEXCEPT __attribute__ ((always_inline)) {
      return (*port()) | MASK;
  }

  inline static port_t loval() FL_NOEXCEPT __attribute__ ((always_inline)) {
      return (*port()) & ~MASK;
  }

  inline static port_ptr_t port() FL_NOEXCEPT __attribute__ ((always_inline)) {
      return (port_ptr_t)GPIO_REG;
  }

  inline static port_ptr_t sport() FL_NOEXCEPT __attribute__ ((always_inline)) {
      return (port_ptr_t)GPIO_BIT_SET_REG;
  }

  inline static port_ptr_t cport() FL_NOEXCEPT __attribute__ ((always_inline)) {
      return (port_ptr_t)GPIO_BIT_CLEAR_REG;
  }

  inline static port_t mask() __attribute__ ((always_inline)) { return MASK; }

  inline static bool isset() FL_NOEXCEPT __attribute__ ((always_inline)) {
      return (*port()) & MASK;
  }
};

#define _FL_BIT(B) (1ULL << B)

#ifndef FASTLED_UNUSABLE_PIN_MASK

// CONFIG_IDF_TARGET_ESP32 was introduced in ESP-IDF v4.0
// For v3.x (where the macro doesn't exist), assume original ESP32
// For v4.0+, only use these settings when CONFIG_IDF_TARGET_ESP32 is defined
#if !ESP_IDF_VERSION_4_OR_HIGHER || defined(FL_IS_ESP_32DEV)
// 40 GPIO pins. ESPIDF defined 24, 28-31 as invalid and 34-39 as readonly
// GPIO 6-11 used by default for SPI flash.  GPIO 20 is invalid.
// NOTE: GPIO 1 & 3 commonly used for UART and may cause flashes when uploading.
#define FASTLED_UNUSABLE_PIN_MASK (0ULL | _FL_BIT(6) | _FL_BIT(7) | _FL_BIT(8) | _FL_BIT(9) | _FL_BIT(10) | _FL_BIT(11) | _FL_BIT(20))

#elif defined(FL_IS_ESP_32C3)
// 22 GPIO pins. ESPIDF defines all pins as valid
// GPIO 11-17 used by default for SPI flash
// NOTE: GPIO 20-21 commonly used for UART and may cause flashes when uploading.
#define FASTLED_UNUSABLE_PIN_MASK (0ULL | _FL_BIT(11) | _FL_BIT(12) | _FL_BIT(13) | _FL_BIT(14) | _FL_BIT(15) | _FL_BIT(16) | _FL_BIT(17))

#elif defined(FL_IS_ESP_32S2)
// 48 GPIO pins.  ESPIDF defines 22-25, 47 as invalid and 46-47 as readonly.s
// GPIO 27-32 used by default for SPI flash.
// NOTE: GPIO 37 & 38 commonly used for UART and may cause flashes when uploading.
#define FASTLED_UNUSABLE_PIN_MASK (0ULL | _FL_BIT(27) | _FL_BIT(28) | _FL_BIT(29) | _FL_BIT(30) | _FL_BIT(31) | _FL_BIT(32))

#elif defined(FL_IS_ESP_32S3)
// 49 GPIO pins. ESPIDF defineds 22-25 as invalid.
// GPIO 19-20 used for USB-JTAG and will break flashing if used.
// GPIO 27-32 used by default for SPI flash.
// NOTE: GPIO 43 & 44 commonly used for UART and may cause flashes when uploading.
#define FASTLED_UNUSABLE_PIN_MASK (0ULL | _FL_BIT(19) | _FL_BIT(20) | _FL_BIT(27) | _FL_BIT(28) | _FL_BIT(29) | _FL_BIT(30) | _FL_BIT(31) | _FL_BIT(32))

#elif defined(FL_IS_ESP_32C5)

// GPIO 13,14 (USB_D*), 15-18,20-22 used by default for SPI flash.
#define FASTLED_UNUSABLE_PIN_MASK (0ULL | _FL_BIT(13) | _FL_BIT(14) | _FL_BIT(15) | _FL_BIT(16) | _FL_BIT(17) | _FL_BIT(18) | _FL_BIT(20) | _FL_BIT(21) | _FL_BIT(22))

#elif defined(FL_IS_ESP_32C6)

// GPIO 12/13 are native USB D-/D+ on ESP32-C6 USB-Serial/JTAG boards; using
// them as FastLED outputs can sever the active serial/upload connection.
// GPIO 24-26 and 28-30 are reserved/unusable on ESP32-C6 packages.
#define FASTLED_UNUSABLE_PIN_MASK (0ULL | _FL_BIT(12) | _FL_BIT(13) | _FL_BIT(24) | _FL_BIT(25) | _FL_BIT(26) | _FL_BIT(28) | _FL_BIT(29) | _FL_BIT(30))

#elif defined(FL_IS_ESP_32P4)
// 55 GPIO pins. ESPIDF defines all pins as valid.
// NOTE: GPIO 24 & 25 commonly used for USB and may cause flashes when uploading.
#define FASTLED_UNUSABLE_PIN_MASK (0ULL | _FL_BIT(24) | _FL_BIT(25))

#elif defined(FL_IS_ESP_32H2)
// 22 GPIO pins.  ESPIDF defines all pins as valid.
// GPIO 15-21 are typically reserved for SPI0/1 flash communication.
#define FASTLED_UNUSABLE_PIN_MASK (0ULL | _FL_BIT(15) | _FL_BIT(16) | _FL_BIT(17) | _FL_BIT(18) | _FL_BIT(19) | _FL_BIT(20) | _FL_BIT(21))
#elif defined(FL_IS_ESP_32C2)
// 21 GPIO pins (GPIO 0-20). ESPIDF defines all pins as valid.
// GPIO 11-17 are typically reserved for SPI flash and not recommended for other uses.
// NOTE: ESP32-C2 is a low-memory device - complex examples may not compile
#define FASTLED_UNUSABLE_PIN_MASK (0ULL | _FL_BIT(11) | _FL_BIT(12) | _FL_BIT(13) | _FL_BIT(14) | _FL_BIT(15) | _FL_BIT(16) | _FL_BIT(17))
#else
#warning Unknown ESP32 chip variant.  Only pins defined by ESP-IDF will be masked.
#define FASTLED_UNUSABLE_PIN_MASK (0ULL)
#endif


#endif



// SOC GPIO mask was not added until version IDF version 4.3.  Prior to this only ESP32 chip was supported, so only
// the value for ESP32
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 3, 0) && !defined(SOC_GPIO_VALID_OUTPUT_GPIO_MASK)
// 0~39 except from 24, 28~31 are valid
#define SOC_GPIO_VALID_GPIO_MASK (0xFFFFFFFFFFULL & ~(0ULL | _FL_BIT(24) | _FL_BIT(28) | _FL_BIT(29) | _FL_BIT(30) | _FL_BIT(31)))
// GPIO >= 34 are input only
#define SOC_GPIO_VALID_OUTPUT_GPIO_MASK (SOC_GPIO_VALID_GPIO_MASK & ~(0ULL | _FL_BIT(34) | _FL_BIT(35) | _FL_BIT(36) | _FL_BIT(37) | _FL_BIT(38) | _FL_BIT(39)))

#endif


// Define mask of valid pins.  Start with the list of valid output pins from ESPIDF and remove unusable pins
#define _FL_VALID_PIN_MASK (u64(SOC_GPIO_VALID_OUTPUT_GPIO_MASK) & ~FASTLED_UNUSABLE_PIN_MASK)

#define _FL_PIN_VALID(PIN) ((_FL_VALID_PIN_MASK & (1ULL << PIN)) != 0)

#if defined(FL_IS_ESP_32C6)
FL_STATIC_ASSERT(!_FL_PIN_VALID(12), "ESP32-C6 GPIO12 is USB D- and must be invalid for FastLED output");
FL_STATIC_ASSERT(!_FL_PIN_VALID(13), "ESP32-C6 GPIO13 is USB D+ and must be invalid for FastLED output");
#endif

#define _FL_DEFPIN(PIN) template <> class FastPin<PIN> : public _ESPPIN<PIN, ((u32)1 << (PIN % 32)), _FL_PIN_VALID(PIN)> {};

// Define all possible pins.  If the pin is not valid for a particular ESP32 variant, the pin number
// will be shifted into the 192-255 range, in effect rendering it unusable.
_FL_DEFPIN( 0); _FL_DEFPIN( 1); _FL_DEFPIN( 2); _FL_DEFPIN( 3);
_FL_DEFPIN( 4); _FL_DEFPIN( 5); _FL_DEFPIN( 6); _FL_DEFPIN( 7);
_FL_DEFPIN( 8); _FL_DEFPIN( 9); _FL_DEFPIN(10); _FL_DEFPIN(11);
_FL_DEFPIN(12); _FL_DEFPIN(13); _FL_DEFPIN(14); _FL_DEFPIN(15);
_FL_DEFPIN(16); _FL_DEFPIN(17); _FL_DEFPIN(18); _FL_DEFPIN(19);
_FL_DEFPIN(20); _FL_DEFPIN(21); _FL_DEFPIN(22); _FL_DEFPIN(23);
_FL_DEFPIN(24); _FL_DEFPIN(25); _FL_DEFPIN(26); _FL_DEFPIN(27);
_FL_DEFPIN(28); _FL_DEFPIN(29); _FL_DEFPIN(30); _FL_DEFPIN(31);
_FL_DEFPIN(32); _FL_DEFPIN(33); _FL_DEFPIN(34); _FL_DEFPIN(35);
_FL_DEFPIN(36); _FL_DEFPIN(37); _FL_DEFPIN(38); _FL_DEFPIN(39);
_FL_DEFPIN(40); _FL_DEFPIN(41); _FL_DEFPIN(42); _FL_DEFPIN(43);
_FL_DEFPIN(44); _FL_DEFPIN(45); _FL_DEFPIN(46); _FL_DEFPIN(47);
_FL_DEFPIN(48); _FL_DEFPIN(49); _FL_DEFPIN(50); _FL_DEFPIN(51);
_FL_DEFPIN(52); _FL_DEFPIN(53); _FL_DEFPIN(54); _FL_DEFPIN(55);
_FL_DEFPIN(56); _FL_DEFPIN(57); _FL_DEFPIN(58); _FL_DEFPIN(59);
_FL_DEFPIN(60); _FL_DEFPIN(61); _FL_DEFPIN(62); _FL_DEFPIN(63);

#define HAS_HARDWARE_PIN_SUPPORT

#pragma GCC diagnostic pop
}  // namespace fl

FL_DISABLE_WARNING_POP
