/* 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 <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include "hal_syscall.h"
#include "./api/Common.h"

#include "drv_fpioa.h"
#include "drv_pwm.h"

static pthread_t _tone_thread;
static bool is_tone_thread_create = false;
static void *_tone_queue = NULL;
static int8_t _pin = -1;
static int _channel = -1;

typedef enum {
    TONE_START,
    TONE_END
} tone_cmd_t;

typedef struct {
    tone_cmd_t tone_cmd;
    uint8_t pin;
    int channel;
    unsigned int frequency;
    unsigned long duration;
} tone_msg_t;

#define MAX_MSG (20)
#define TIMEOUT (1000000)

static void *tone_task(void *)
{
    tone_msg_t tone_msg;

    while (1) {
        if (0 != rt_mq_recv(_tone_queue, (void *)&tone_msg, sizeof(tone_msg), TIMEOUT)) {
            printf("%s: rt_mq_recv failed\n", __func__);
            continue;
        }

        switch (tone_msg.tone_cmd) {
        case TONE_START:
#if K230_DEBUG
            printf("Task received from queue TONE_START: pin=%d, frequency=%u Hz, duration=%lu ms\n",
                   tone_msg.pin, tone_msg.frequency, tone_msg.duration);
#endif

            if (_pin == -1) {
                if ((0 != drv_pwm_init()) || (0 != drv_pwm_set_freq(tone_msg.channel, tone_msg.frequency)) ||
                    (0 != drv_pwm_set_duty(tone_msg.channel, 50))) {
                    printf("%s: drv_pwm_init/drv_pwm_set_freq/drv_pwm_set_duty failed\n", __func__);
                    break;
                }
                _pin = tone_msg.pin;
            }

            if ((0 != drv_pwm_set_freq(tone_msg.channel, tone_msg.frequency)) ||
                (0 != drv_pwm_enable(tone_msg.channel))) {
                printf("%s: drv_pwm_set_freq/drv_pwm_enable failed\n", __func__);
                break;
            }

            if (tone_msg.duration) {

                usleep(tone_msg.duration * 1000);

                if (0 != drv_pwm_disable(tone_msg.channel)) {
                    printf("%s: drv_pwm_disable failed\n", __func__);
                    break;
                }
            }
            break;

        case TONE_END:
#if K230_DEBUG
            printf("Task received from queue TONE_END: pin=%d\n", tone_msg.pin);
#endif
            if (0 != drv_pwm_disable(tone_msg.channel)) {
                printf("%s: drv_pwm_disable failed\n", __func__);
                break;
            }
            drv_pwm_deinit();
            _channel = -1;
            _pin = -1;
            break;

        default:;  // do nothing
        }  // switch
    }  // infinite loop
}

static int tone_init()
{
    if (NULL == _tone_queue) {

        _tone_queue = rt_mq_create("tone_queue", sizeof(tone_msg_t), MAX_MSG, 0);

        if (NULL == _tone_queue) {
            printf("Could not open tone queue\n");
            return 0;  // ERR
        }
    }

    if (!is_tone_thread_create) {
        if (0 != pthread_create(&_tone_thread, NULL, tone_task, NULL)) {
            printf("Could not create tone thread\n");
            return 0;
        }
        is_tone_thread_create = true;
    }
    return 1;  // OK
}

void noTone(uint8_t pin)
{
    if (_pin == pin) {
        if (tone_init()) {
            tone_msg_t tone_msg = {
                .tone_cmd = TONE_END,
                .pin = pin,
                .channel = _channel,
                .frequency = 0,  // Ignored
                .duration = 0,   // Ignored
            };

            if (0 != rt_mq_send(_tone_queue, (void *)&tone_msg, sizeof(tone_msg))) {
                printf("%s: rt_mq_send failed\n", __func__);
            }
        }
    } else {
        printf("Tone is not running on given pin %d\n", pin);
    }
}

static int pin2channel(uint8_t pin)
{
    if (-1 != _channel) {
        goto set_fpioa;
    }

    for (int ch = 0; ch <= 5; ++ch) {
        if (drv_fpioa_is_func_supported_by_pin(pin, (fpioa_func_t)(PWM0 + ch))) {
            _channel = ch;
            break;
        }
    }

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

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

out:
    return _channel;
}

// parameters:
// pin - pin number which will output the signal
// frequency - PWM frequency in Hz
// duration - time in ms - how long will the signal be outputted.
//   If not provided, or 0 you must manually call noTone to end output
void tone(uint8_t pin, unsigned int frequency, unsigned long duration)
{
    if (_pin == -1 || _pin == pin) {
        if ((-1 != pin2channel(pin)) && tone_init()) {

            tone_msg_t tone_msg = {
                .tone_cmd = TONE_START,
                .pin = pin,
                .channel = _channel,
                .frequency = frequency,
                .duration = duration,
            };

            if (0 != rt_mq_send(_tone_queue, (void *)&tone_msg, sizeof(tone_msg))) {
                printf("%s: rt_mq_send failed\n", __func__);
            }

            return;
        } else {
            printf("tone failed\n");
        }
    } else {
        printf("Tone is still running on pin %d, call noTone(%d) first!\n", _pin, _pin);

        return;
    }
}

