/* Copyright (c) 2025, Canaan Bright Sight Co., Ltd
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "./api/Common.h"
#include "drv_gpio.h"
#include "drv_adc.h"
#include "drv_pwm.h"
#include "drv_fpioa.h"
#include <limits.h>

static drv_gpio_inst_t *gpio[GPIO_MAX_NUM];

void pinMode(pin_size_t pinNumber, PinMode pinMode)
{
    gpio_drive_mode_t mode;

    if (GPIO_MAX_NUM <= pinNumber) {
        printf("%s: invalid gpio number\n", __func__);
        return;
    }

    if (0 != drv_fpioa_set_pin_func(pinNumber, (fpioa_func_t)(GPIO0 + pinNumber))) {
        return;
    }

    if (NULL == gpio[pinNumber]) {
        if (0 != drv_gpio_inst_create(pinNumber, &gpio[pinNumber])) {
            return;
        }
    }

    switch (pinMode) {
    case OUTPUT:
        mode = GPIO_DM_OUTPUT;
        break;
    case INPUT:
        mode = GPIO_DM_INPUT;
        break;
    case INPUT_PULLUP:
        mode = GPIO_DM_INPUT_PULLUP;
        break;
    case INPUT_PULLDOWN:
        mode = GPIO_DM_INPUT_PULLDOWN;
        break;
    case OUTPUT_OPENDRAIN:
        mode = GPIO_DM_OUTPUT_OD;
        break;
    default:
        printf("%s: invalid mode\n", __func__);
        return;
    }

    if (0 != drv_gpio_mode_set(gpio[pinNumber], mode)) {
        printf("%s: drv_gpio_mode_set fail\n", __func__);
    }
}

void digitalWrite(pin_size_t pinNumber, PinStatus status)
{
    if (GPIO_MAX_NUM <= pinNumber) {
        printf("%s: invalid gpio number\n", __func__);
        return;
    }

    if (0 != drv_gpio_value_set(gpio[pinNumber], (LOW == status)? GPIO_PV_LOW : GPIO_PV_HIGH)) {
        printf("%s: drv_gpio_value_set fail\n", __func__);
    }
}

PinStatus digitalRead(pin_size_t pinNumber)
{
    gpio_pin_value_t val = GPIO_PV_LOW;

    if (GPIO_MAX_NUM <= pinNumber) {
        printf("%s: invalid gpio number\n", __func__);
        goto out;
    }

    val = drv_gpio_value_get(gpio[pinNumber]);
    if (val < 0) {
        val = GPIO_PV_LOW;
    }

out:
    return (GPIO_PV_LOW == val) ? LOW : HIGH;
}

void _attachInterruptArg(pin_size_t pinNumber, voidFuncPtrParam callback, void *param, PinStatus mode)
{
    gpio_pin_edge_t _mode;

    if (GPIO_MAX_NUM <= pinNumber) {
        printf("%s: invalid gpio number\n", __func__);
        return;
    }

    switch (mode) {
    case LOW:
        _mode = GPIO_PE_LOW;
        break;
    case HIGH:
        _mode = GPIO_PE_HIGH;
        break;
    case CHANGE:
        _mode = GPIO_PE_BOTH;
        break;
    case FALLING:
        _mode = GPIO_PE_FALLING;
        break;
    case RISING:
        _mode = GPIO_PE_RISING;
        break;
    default:
        printf("%s: invalid mode\n", __func__);
        return;
    }

    if (0 != drv_gpio_register_irq(gpio[pinNumber], _mode, 10, callback, param)) {
        printf("%s: drv_gpio_register_irq fail\n", __func__);
        return;
    }

    if (0 != drv_gpio_enable_irq(gpio[pinNumber])) {
        printf("%s: drv_gpio_enable_irq fail\n", __func__);
        return;
    }
}

void attachInterrupt(pin_size_t pinNumber, voidFuncPtr callback, PinStatus mode)
{
    _attachInterruptArg(pinNumber, (voidFuncPtrParam)callback, NULL, mode);
}

void attachInterruptArg(pin_size_t pinNumber, voidFuncPtrParam callback, void *param, PinStatus mode)
{
    _attachInterruptArg(pinNumber, callback, param, mode);
}

void detachInterrupt(pin_size_t pinNumber)
{
    if (GPIO_MAX_NUM <= pinNumber) {
        printf("%s: invalid gpio number\n", __func__);
        return;
    }

    if (0 != drv_gpio_disable_irq(gpio[pinNumber])) {
        printf("%s: drv_gpio_disable_irq fail\n", __func__);
        return;
    }

    if (0 != drv_gpio_unregister_irq(gpio[pinNumber])) {
        printf("%s: drv_gpio_unregister_irq fail\n", __func__);
        return;
    }
}

uint8_t shiftIn(pin_size_t dataPin, pin_size_t clockPin, BitOrder bitOrder)
{
    uint8_t value = 0;
    uint8_t i;

    for (i = 0; i < 8; ++i) {
        //digitalWrite(clockPin, HIGH);
        if (bitOrder == LSBFIRST) {
            value |= digitalRead(dataPin) << i;
        } else {
            value |= digitalRead(dataPin) << (7 - i);
        }
        digitalWrite(clockPin, HIGH);
        digitalWrite(clockPin, LOW);
    }
    return value;
}

void shiftOut(pin_size_t dataPin, pin_size_t clockPin, BitOrder bitOrder, uint8_t val)
{
    uint8_t i;

    for (i = 0; i < 8; i++) {
        if (bitOrder == LSBFIRST) {
            digitalWrite(dataPin, !!(val & (1 << i)));
        } else {
            digitalWrite(dataPin, !!(val & (1 << (7 - i))));
        }

        digitalWrite(clockPin, HIGH);
        digitalWrite(clockPin, LOW);
    }
}

#define WAIT_FOR_PIN_STATE(state)                                         \
  while (digitalRead(pin) != (state)) {                                   \
    if (utils_cpu_ticks() - start_cycle_count > timeout_cycles) { \
      return 0;                                                           \
    }                                                                     \
  }

unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout)
{
    const uint32_t max_timeout_us = clockCyclesToMicroseconds(UINT_MAX);
    if (timeout > max_timeout_us) {
        timeout = max_timeout_us;
    }
    const uint32_t timeout_cycles = microsecondsToClockCycles(timeout);
    const uint32_t start_cycle_count = utils_cpu_ticks();
    WAIT_FOR_PIN_STATE(!state);
    WAIT_FOR_PIN_STATE(state);
    const uint32_t pulse_start_cycle_count = utils_cpu_ticks();
    WAIT_FOR_PIN_STATE(!state);
    return clockCyclesToMicroseconds(utils_cpu_ticks() - pulse_start_cycle_count);
}

unsigned long pulseInLong(uint8_t pin, uint8_t state, unsigned long timeout)
{
    return pulseIn(pin, state, timeout);
}

#define DEFAULT_ADC_RESOLUTION_BIT (12)
static int adc_resolution = 10;

int analogRead(int channel)
{
    int ret;
    uint32_t raw_value;

    ret = drv_adc_init();
    if (0 != ret) {
        printf("%s: adc init fail\n", __func__);
        return ret;
    }

    raw_value = drv_adc_read(channel);
    if (raw_value == __UINT32_MAX__) {
        drv_adc_deinit();
        return -1;
    }

    if (adc_resolution < DEFAULT_ADC_RESOLUTION_BIT) {
        raw_value = raw_value >> (DEFAULT_ADC_RESOLUTION_BIT - adc_resolution);
    } else if (adc_resolution > DEFAULT_ADC_RESOLUTION_BIT) {
        raw_value = raw_value << (adc_resolution - DEFAULT_ADC_RESOLUTION_BIT);
    }

    drv_adc_deinit();

    return (int)raw_value;
}

void analogReadResolution(int resolution)
{
    if (resolution >= 8 && resolution <= 12) {
        adc_resolution = resolution;
    } else {
        printf("%s: unsurpport resolution = %d\n", __func__, resolution);
    }
}

static uint8_t analog_resolution = 8;
static int analog_frequency = 1000;
static int channel[GPIO_MAX_NUM] = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                                    -1, -1, -1, -1};

static int pin2channel(uint8_t pinNumber)
{
    if (-1 != channel[pinNumber]) {
        goto set_fpioa;
    }

    for (int ch = 0; ch <= 5; ++ch) {
        if (drv_fpioa_is_func_supported_by_pin(pinNumber, (fpioa_func_t)(PWM0 + ch))) {
            channel[pinNumber] = ch;
            break;
        }
    }

    if (-1 == channel[pinNumber]) {
        printf("%s: pin %d does not support any PWM function\n", __func__, pinNumber);
        goto out;
    }

set_fpioa:
    if (drv_fpioa_set_pin_func(pinNumber, (fpioa_func_t)(PWM0 + channel[pinNumber])) != 0) {
        printf("%s: failed to assign pin %d to PWM(%d)\n", __func__, pinNumber, channel[pinNumber]);
        channel[pinNumber] = -1;
    }

out:
    return channel[pinNumber];
}

void analogWrite(pin_size_t pinNumber, int value)
{
    uint32_t period;
    int ch;

    if (GPIO_MAX_NUM <= pinNumber) {
        printf("%s: invalid pin number\n", __func__);
        goto out;
    }

    ch = pin2channel(pinNumber);
    if (-1 == ch) {
        goto out;
    }

    if (0 != drv_pwm_init()) {
        goto out;
    }

    if (0 != drv_pwm_set_freq(ch, analog_frequency)) {
        printf("%s: drv_pwm_set_freq failed\n", __func__);
        goto deinit;
    }

    period = NSEC_PER_SEC / analog_frequency;
    if (0 != drv_pwm_set_duty_ns(ch, ((uint64_t)period * value) >> analog_resolution)) {
        printf("%s: drv_pwm_set_duty_ns failed\n", __func__);
        goto deinit;
    }

    if (0 != drv_pwm_enable(ch)){
        printf("%s: drv_pwm_enable failed\n", __func__);
        goto deinit;
    }

deinit:
    drv_pwm_deinit();
out:
    return;
}

void analogWriteFrequency(pin_size_t pinNumber, uint32_t freq)
{
    int ch;
    if (GPIO_MAX_NUM <= pinNumber) {
        printf("%s: invalid pin number\n", __func__);
        goto out;
    }

    ch = pin2channel(pinNumber);
    if (-1 == ch) {
        goto out;
    }

    if (0 == freq) {
        printf("%s: invalid freq = %d\n", __func__, freq);
        goto out;
    }

    if (0 != drv_pwm_init()) {
        goto out;
    }

    if (0 != drv_pwm_set_freq(ch, freq)) {
        printf("%s: drv_pwm_set_freq failed\n", __func__);
        goto deinit;
    }

    analog_frequency = freq;

deinit:
    drv_pwm_deinit();
out:
    return;
}

void analogWriteResolution(uint8_t resolution)
{
    analog_resolution = resolution;
}
