/*
 * This file is part of the MicroPython project, http://micropython.org/
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2025 Infineon Technologies AG
 *
 * 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.
 */

// std includes
#include "stdio.h"

// MTB includes
#include "mtb_hal_system.h"

// micropython includes
#include "mpconfigport.h"
#include "mphalport.h"

#include "py/runtime.h"

extern void machine_uart_repl_init(void);
extern int machine_uart_repl_readchar(void);
extern void machine_uart_repl_write(const void *buf_in, mp_uint_t size);
extern uintptr_t machine_uart_repl_ioctl(uintptr_t arg);

void mp_hal_stdio_init(void) {
    machine_uart_repl_init();
}

uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) {
    return machine_uart_repl_ioctl(poll_flags);
}

void mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) {
    machine_uart_repl_write(str, len);
}

int mp_hal_stdin_rx_chr(void) {
    return machine_uart_repl_readchar();
}

extern uint32_t get_drive_mode(uint8_t mode, uint8_t pull, uint8_t *value);

void mp_hal_pin_config(mp_hal_pin_obj_t pin, uint32_t mode, uint32_t pull, uint32_t value) {
    uint8_t validated_value = (uint8_t)value;
    uint32_t drive_mode = get_drive_mode(mode, pull, &validated_value);
    Cy_GPIO_Pin_FastInit(Cy_GPIO_PortToAddr(pin->port), pin->pin, drive_mode, validated_value, HSIOM_SEL_GPIO);
}

extern uint8_t pin_get_mode(const machine_pin_obj_t *self);

uint32_t mp_hal_pin_read(mp_hal_pin_obj_t pin) {
    return Cy_GPIO_Read(Cy_GPIO_PortToAddr(pin->port), pin->pin);
}

void mp_hal_pin_write(mp_hal_pin_obj_t pin, uint8_t polarity) {
    Cy_GPIO_Write(Cy_GPIO_PortToAddr(pin->port), pin->pin, polarity);
}

mp_hal_pin_af_obj_t mp_hal_pin_af_find(mp_hal_pin_obj_t pin, machine_pin_af_signal_t af_signal) {
    for (uint8_t i = 0; i < pin->af_num; i++) {
        const machine_pin_af_obj_t *af = &pin->af[i];
        if (af->signal == af_signal) {
            return af;
        }
    }

    mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("Pin '%q' does not support '%s'."), pin->name, machine_pin_af_signal_str[af_signal]);
}

machine_pin_af_unit_t mp_hal_periph_pins_af_get_af_unit(const mp_hal_pin_af_config_t *periph_pins_config, uint8_t num_pins, machine_pin_af_fn_t fn) {
    bool found_first_valid_pin = false;
    machine_pin_af_periph_t periph_ptr = NULL;
    uint32_t unit = MACHINE_PIN_AF_UNIT_NONE;
    for (uint8_t i = 0; i < num_pins; i++) {
        const mp_hal_pin_af_config_t *pin_cfg = &periph_pins_config[i];
        if (pin_cfg->pin == NULL || pin_cfg->af == NULL) {
            continue;
        }
        if (!found_first_valid_pin) {
            periph_ptr = pin_cfg->af->periph;
            unit = pin_cfg->af->unit;
            found_first_valid_pin = true;
        } else {
            if (pin_cfg->af->fn != fn) {
                mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("all pins must belong to the same function."));
            }
            if (pin_cfg->af->periph != periph_ptr) {
                mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("all pins must belong to the same peripheral."));
            }
            if (pin_cfg->af->unit != unit) {
                mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("all pins must belong to the same unit."));
            }
        }
    }

    return unit;
}

void mp_hal_periph_pins_af_resolve_fn_unit(const mp_hal_pin_af_config_t *periph_pins_config, uint8_t pin_num, machine_pin_af_fn_t fn, machine_pin_af_unit_t *fn_unit) {
    uint8_t resolved_fn_unit;
    uint8_t suggested_fn_unit = *fn_unit;

    /**
     * Resolve the AF unit for the provided pins.
     * It can be that the configuration still don't have
     * any pin AF assigned.
     * Therefore, its AF unit is not yet determined.
     */
    machine_pin_af_unit_t pin_af_unit = mp_hal_periph_pins_af_get_af_unit(periph_pins_config, pin_num, fn);

    /* No pins have been defined. */
    if (pin_af_unit == MACHINE_PIN_AF_UNIT_NONE) {
        /* The suggested AF unit candidate is chosen if defined. */
        if (suggested_fn_unit != MACHINE_PIN_AF_UNIT_NONE) {
            resolved_fn_unit = (uint8_t)suggested_fn_unit;
        } else {
            mp_raise_ValueError(MP_ERROR_TEXT("no id or pins have been provided. At least one of them must be specified."));
        }
    } else {
        /* The pins have been defined. */
        /* The suggested AF unit has been also defined.*/
        if (suggested_fn_unit != MACHINE_PIN_AF_UNIT_NONE) {
            if (suggested_fn_unit == pin_af_unit) {
                /* The suggested AF unit matches the AF unit discovered from the passed pins */
                resolved_fn_unit = (uint8_t)suggested_fn_unit;
            } else {
                mp_raise_ValueError(MP_ERROR_TEXT("the id does not match the discovered id from the provided pins."));
            }
        } else {
            /* No suggested AF unit has been passed */
            resolved_fn_unit = pin_af_unit;
        }
    }

    (*fn_unit) = resolved_fn_unit;
}

void mp_hal_periph_pins_af_resolve_pin_af(mp_hal_pin_af_config_t *periph_pins_config, uint8_t pin_num, machine_pin_af_unit_t fn_unit) {
    for (uint8_t i = 0; i < pin_num; i++) {
        if (periph_pins_config[i].af == NULL && periph_pins_config[i].pin == NULL) {
            periph_pins_config[i].pin = machine_pin_get_af_pin(fn_unit, periph_pins_config[i].signal);
            if (periph_pins_config[i].pin == NULL) {
                mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("no signal '%s' available for id %d."), machine_pin_af_signal_str[periph_pins_config[i].signal], fn_unit);
            }
            periph_pins_config[i].af = mp_hal_pin_af_find(periph_pins_config[i].pin, periph_pins_config[i].signal);
        } else {
            /**
             * It should not enter here. As this function should
             * be only called after pin - AF unit matching */
            if (periph_pins_config[i].af->unit != fn_unit && periph_pins_config[i].pin != NULL) {
                mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("the pin '%q' does not match the AF unit %d."), periph_pins_config[i].pin->name, fn_unit);
            }
        }
    }
}

machine_pin_af_periph_t mp_hal_periph_pins_af_init(const mp_hal_pin_af_config_t *periph_pins_config, uint8_t num_pins) {
    for (uint8_t i = 0; i < num_pins; i++) {
        const mp_hal_pin_af_config_t *pin_cfg = &periph_pins_config[i];
        Cy_GPIO_Pin_FastInit(Cy_GPIO_PortToAddr(pin_cfg->pin->port), pin_cfg->pin->pin, pin_cfg->cy_drive_mode, pin_cfg->init_value, pin_cfg->af->idx);
    }

    return periph_pins_config[0].af->periph;
}
