#include "asf/common2/services/delay/delay.h"
#include "asf/sam0/drivers/port/port.h"

#include "mphalport.h"

#include "common-hal/microcontroller/Pin.h"
#include "shared-bindings/bitbangio/SPI.h"
#include "shared-bindings/busio/SPI.h"
#include "shared-bindings/digitalio/DigitalInOut.h"
#include "shared-bindings/neopixel_write/__init__.h"
#include "shared-module/bitbangio/types.h"
#include "rgb_led_status.h"
#include "samd21_pins.h"

uint8_t rgb_status_brightness = 255;
#ifdef MICROPY_HW_NEOPIXEL
static uint8_t status_neopixel_color[3];
static digitalio_digitalinout_obj_t status_neopixel;
#endif


#if defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK)
static uint8_t status_apa102_color[12] = {0, 0, 0, 0, 0xff, 0, 0, 0};
#ifdef CIRCUITPY_BITBANG_APA102
static bitbangio_spi_obj_t status_apa102;
#else
busio_spi_obj_t status_apa102;
#endif
#endif

#if defined(MICROPY_HW_NEOPIXEL) || (defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK))
static uint32_t current_status_color = 0;
#endif


void rgb_led_status_init() {
    #ifdef MICROPY_HW_NEOPIXEL
        common_hal_digitalio_digitalinout_construct(&status_neopixel, MICROPY_HW_NEOPIXEL);
        // Pretend we aren't using the pins. digitalio.DigitalInOut
        // will mark them as used.
        neopixel_in_use = false;
        common_hal_digitalio_digitalinout_switch_to_output(&status_neopixel, false, DRIVE_MODE_PUSH_PULL);
    #endif
    #if defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK)
        #ifdef CIRCUITPY_BITBANG_APA102
        shared_module_bitbangio_spi_construct(&status_apa102,
                                              MICROPY_HW_APA102_SCK,
                                              MICROPY_HW_APA102_MOSI,
                                              mp_const_none);
        #else
        if (status_apa102.current_baudrate > 0) {
            // Don't use spi_deinit because that leads to infinite
            // recursion because reset_pin may call
            // rgb_led_status_init.
            spi_disable(&status_apa102.spi_master_instance);
        }
        common_hal_busio_spi_construct(&status_apa102,
                                      MICROPY_HW_APA102_SCK,
                                      MICROPY_HW_APA102_MOSI,
                                      mp_const_none);
        #endif
        // Pretend we aren't using the pins. bitbangio.SPI will
        // mark them as used.
        apa102_mosi_in_use = false;
        apa102_sck_in_use = false;
        #ifdef CIRCUITPY_BITBANG_APA102
        shared_module_bitbangio_spi_try_lock(&status_apa102);
        shared_module_bitbangio_spi_configure(&status_apa102, 100000, 0, 1, 8);
        #else
        common_hal_busio_spi_try_lock(&status_apa102);
        common_hal_busio_spi_configure(&status_apa102, 100000, 0, 1, 8);
        #endif
    #endif

    #if defined(MICROPY_HW_NEOPIXEL) || (defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK))
    // Force a write of the current status color.
    uint32_t rgb = current_status_color;
    current_status_color = 0;
    new_status_color(rgb);
    #endif
}

void reset_status_led() {
    #ifdef MICROPY_HW_NEOPIXEL
        reset_pin(MICROPY_HW_NEOPIXEL->pin);
    #endif
    #if defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK)
        reset_pin(MICROPY_HW_APA102_MOSI->pin);
        reset_pin(MICROPY_HW_APA102_SCK->pin);
    #endif
}

void new_status_color(uint32_t rgb) {
    #if defined(MICROPY_HW_NEOPIXEL) || (defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK))
    if (current_status_color == rgb) {
        return;
    }
    uint32_t rgb_adjusted = color_brightness(rgb, rgb_status_brightness);
    current_status_color = rgb_adjusted;
    #endif

    #ifdef MICROPY_HW_NEOPIXEL
        if (neopixel_in_use) {
            return;
        }
        status_neopixel_color[0] = (rgb_adjusted >> 8) & 0xff;
        status_neopixel_color[1] = (rgb_adjusted >> 16) & 0xff;
        status_neopixel_color[2] = rgb_adjusted & 0xff;
        common_hal_neopixel_write(&status_neopixel, status_neopixel_color, 3);
    #endif
    #if defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK)
        if (apa102_mosi_in_use || apa102_sck_in_use) {
            return;
        }
        status_apa102_color[5] = rgb_adjusted & 0xff;
        status_apa102_color[6] = (rgb_adjusted >> 8) & 0xff;
        status_apa102_color[7] = (rgb_adjusted >> 16) & 0xff;

        #ifdef CIRCUITPY_BITBANG_APA102
        shared_module_bitbangio_spi_write(&status_apa102, status_apa102_color, 8);
        #else
        common_hal_busio_spi_write(&status_apa102, status_apa102_color, 8);
        #endif
    #endif
}

void temp_status_color(uint32_t rgb) {
    #if defined(MICROPY_HW_NEOPIXEL) || (defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK))
      uint32_t rgb_adjusted = rgb;
      rgb_adjusted = color_brightness(rgb, rgb_status_brightness);
    #endif
    #ifdef MICROPY_HW_NEOPIXEL
        if (neopixel_in_use) {
            return;
        }
        uint8_t colors[3] = {(rgb_adjusted >> 8) & 0xff, (rgb_adjusted >> 16) & 0xff, rgb_adjusted & 0xff};
        common_hal_neopixel_write(&status_neopixel, colors, 3);
    #endif
    #if defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK)
        if (apa102_mosi_in_use || apa102_sck_in_use) {
            return;
        }
        uint8_t colors[12] = {0, 0, 0, 0, 0xff, rgb_adjusted & 0xff, (rgb_adjusted >> 8) & 0xff, (rgb_adjusted >> 16) & 0xff, 0x0, 0x0, 0x0, 0x0};
        #ifdef CIRCUITPY_BITBANG_APA102
        shared_module_bitbangio_spi_write(&status_apa102, colors, 12);
        #else
        common_hal_busio_spi_write(&status_apa102, colors, 12);
        #endif
    #endif
}

void clear_temp_status() {
    #ifdef MICROPY_HW_NEOPIXEL
        common_hal_neopixel_write(&status_neopixel, status_neopixel_color, 3);
    #endif
    #if defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK)
        #ifdef CIRCUITPY_BITBANG_APA102
        shared_module_bitbangio_spi_write(&status_apa102, status_apa102_color, 8);
        #else
        common_hal_busio_spi_write(&status_apa102, status_apa102_color, 8);
        #endif
    #endif
}

uint32_t color_brightness(uint32_t color, uint8_t brightness) {
    #if defined(MICROPY_HW_NEOPIXEL) || (defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK))
    uint32_t result = ((color & 0xff0000) * brightness / 255) & 0xff0000;
    result += ((color & 0xff00) * brightness / 255) & 0xff00;
    result += ((color & 0xff) * brightness / 255) & 0xff;
    return result;
    #else
    return color;
    #endif
}

void set_rgb_status_brightness(uint8_t level){
      rgb_status_brightness = level;
}
