// Copyright 2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <stdio.h>
#include "math.h"
#include "sys/time.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "esp_log.h"
#include "soc/ledc_reg.h"
#include "soc/timer_group_struct.h"
#include "soc/ledc_struct.h"
#include "driver/periph_ctrl.h"
#include "driver/timer.h"
#include "driver/ledc.h"
#include "iot_light.h"

static const char *TAG = "light";

#define IOT_CHECK(tag, a, ret)  if(!(a)) {       \
        return (ret);                            \
    }
#define ERR_ASSERT(tag, param, ret)  IOT_CHECK(tag, (param) == ESP_OK, ret)
#define POINT_ASSERT(tag, param)    IOT_CHECK(tag, (param) != NULL, ESP_FAIL)
#define LIGHT_NUM_MAX   4

typedef enum {
    LIGHT_CH_NUM_1 = 1,             /*!< Light channel number */
    LIGHT_CH_NUM_2 = 2,             /*!< Light channel number */
    LIGHT_CH_NUM_3 = 3,             /*!< Light channel number */
    LIGHT_CH_NUM_4 = 4,             /*!< Light channel number */
    LIGHT_CH_NUM_5 = 5,             /*!< Light channel number */
    LIGHT_CH_NUM_MAX,               /*!< user shouldn't use this */
} light_channel_num_t;

typedef struct {
    timer_group_t timer_group;
    timer_idx_t timer_id;
} hw_timer_idx_t;

typedef struct {
    gpio_num_t io_num;
    ledc_mode_t mode;
    ledc_channel_t channel;
    int breath_period;
    int blink_period;
    uint32_t fade_step_num;
    uint32_t *fade_duty_step;
    int fade_duty_counter;
    bool fade_step_up;
    bool fade_once;
    bool fade_start;
} light_channel_t;

typedef struct {
    uint8_t channel_num;
    ledc_mode_t mode;
    ledc_timer_t ledc_timer;
    uint32_t full_duty;
    uint32_t freq_hz;
    ledc_timer_bit_t timer_bit;
    hw_timer_idx_t hw_timer;
    light_channel_t *channel_group[0];
} light_t;

static bool g_fade_installed                 = false;
static bool g_hw_timer_started               = false;
static light_t *g_light_group[LIGHT_NUM_MAX] = {NULL};
static esp_err_t iot_light_duty_set(light_handle_t light_handle, uint8_t channel_id, uint32_t duty);

static void iot_timer_create(hw_timer_idx_t *timer_id, bool auto_reload, double timer_interval_sec, timer_isr_handle_t *isr_handle)
{
    /* Select and initialize basic parameters of the timer */
    timer_config_t config;
    config.divider     = HW_TIMER_DIVIDER;
    config.counter_dir = TIMER_COUNT_UP;
    config.counter_en  = TIMER_PAUSE;
    config.alarm_en    = TIMER_ALARM_EN;
    config.intr_type   = TIMER_INTR_LEVEL;
    config.auto_reload = auto_reload;
    timer_init(timer_id->timer_group, timer_id->timer_id, &config);

    /* Timer's counter will initially start from value below.
       Also, if auto_reload is set, this value will be automatically reload on alarm */
    timer_set_counter_value(timer_id->timer_group, timer_id->timer_id, 0x00000000ULL);

    /* Configure the alarm value and the interrupt on alarm. */
    timer_set_alarm_value(timer_id->timer_group, timer_id->timer_id, timer_interval_sec * HW_TIMER_SCALE);
    timer_enable_intr(timer_id->timer_group, timer_id->timer_id);
    timer_isr_register(timer_id->timer_group, timer_id->timer_id, (void *)isr_handle,
                       (void *) timer_id->timer_id, ESP_INTR_FLAG_IRAM, NULL);
}

static void iot_timer_start(hw_timer_idx_t *timer_id)
{
    timer_start(timer_id->timer_group, timer_id->timer_id);
    g_hw_timer_started = true;
}

static void iot_timer_stop(hw_timer_idx_t *timer_id)
{
    timer_disable_intr(timer_id->timer_group, timer_id->timer_id);
    timer_pause(timer_id->timer_group, timer_id->timer_id);
    g_hw_timer_started = false;
}

static IRAM_ATTR void iot_ledc_ls_channel_update(ledc_mode_t speed_mode, ledc_channel_t channel_num)
{
    if (speed_mode == LEDC_LOW_SPEED_MODE) {
        LEDC.channel_group[speed_mode].channel[channel_num].conf0.low_speed_update = 1;
    }
}

static IRAM_ATTR esp_err_t iot_ledc_duty_config(ledc_mode_t speed_mode, ledc_channel_t channel_num, int hpoint_val, int duty_val,
        uint32_t duty_direction, uint32_t duty_num, uint32_t duty_cycle, uint32_t duty_scale)
{
    if (hpoint_val >= 0) {
        LEDC.channel_group[speed_mode].channel[channel_num].hpoint.hpoint = hpoint_val & LEDC_HPOINT_HSCH1_V;
    }

    if (duty_val >= 0) {
        LEDC.channel_group[speed_mode].channel[channel_num].duty.duty = duty_val;
    }

    LEDC.channel_group[speed_mode].channel[channel_num].conf1.val = ((duty_direction & LEDC_DUTY_INC_HSCH0_V) << LEDC_DUTY_INC_HSCH0_S) |
            ((duty_num & LEDC_DUTY_NUM_HSCH0_V) << LEDC_DUTY_NUM_HSCH0_S) |
            ((duty_cycle & LEDC_DUTY_CYCLE_HSCH0_V) << LEDC_DUTY_CYCLE_HSCH0_S) |
            ((duty_scale & LEDC_DUTY_SCALE_HSCH0_V) << LEDC_DUTY_SCALE_HSCH0_S);
    iot_ledc_ls_channel_update(speed_mode, channel_num);
    return ESP_OK;
}

static IRAM_ATTR esp_err_t iot_ledc_set_duty(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty)
{
    return iot_ledc_duty_config(speed_mode,
                                channel,         //uint32_t chan_num,
                                -1,
                                duty << 4,       //uint32_t duty_val,the least 4 bits are decimal part
                                1,               //uint32_t increase,
                                1,               //uint32_t duty_num,
                                1,               //uint32_t duty_cycle,
                                0                //uint32_t duty_scale
                               );
}

static IRAM_ATTR esp_err_t iot_ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel)
{
    LEDC.channel_group[speed_mode].channel[channel].conf0.sig_out_en = 1;
    LEDC.channel_group[speed_mode].channel[channel].conf1.duty_start = 1;
    iot_ledc_ls_channel_update(speed_mode, channel);
    return ESP_OK;
}

static IRAM_ATTR void breath_timer_callback(void *para)
{
    int timer_idx = (int) para;

    if (HW_TIMER_GROUP == TIMER_GROUP_0) {
        /* Retrieve the interrupt status */
        uint32_t intr_status = TIMERG0.int_st_timers.val;
        TIMERG0.hw_timer[timer_idx].update = 1;

        /* Clear the interrupt */
        if ((intr_status & BIT(timer_idx)) && timer_idx == TIMER_0) {
            TIMERG0.int_clr_timers.t0 = 1;
        } else if ((intr_status & BIT(timer_idx)) && timer_idx == TIMER_1) {
            TIMERG0.int_clr_timers.t1 = 1;
        }

        /* After the alarm has been triggered
          we need enable it again, so it is triggered the next time */
        TIMERG0.hw_timer[timer_idx].config.alarm_en = TIMER_ALARM_EN;
    } else if (HW_TIMER_GROUP == TIMER_GROUP_1) {
        uint32_t intr_status = TIMERG1.int_st_timers.val;
        TIMERG1.hw_timer[timer_idx].update = 1;

        if ((intr_status & BIT(timer_idx)) && timer_idx == TIMER_0) {
            TIMERG1.int_clr_timers.t0 = 1;
        } else if ((intr_status & BIT(timer_idx)) && timer_idx == TIMER_1) {
            TIMERG1.int_clr_timers.t1 = 1;
        }

        TIMERG1.hw_timer[timer_idx].config.alarm_en = TIMER_ALARM_EN;
    }

    for (int i = 0; i < LIGHT_NUM_MAX; i++) {
        if (g_light_group[i] != NULL) {
            light_t *light = g_light_group[i];

            for (int j = 0; j < light->channel_num; j++) {
                light_channel_t *l_chn = light->channel_group[j];

                if (l_chn->fade_start == true) {
                    if (l_chn->fade_step_up == true) {
                        l_chn->fade_duty_counter++;
                    } else {
                        l_chn->fade_duty_counter--;
                    }

                    if ((l_chn->fade_duty_counter >= 0) && (l_chn->fade_duty_counter < l_chn->fade_step_num)) {
                        iot_ledc_set_duty(l_chn->mode, l_chn->channel, l_chn->fade_duty_step[l_chn->fade_duty_counter]);
                        iot_ledc_update_duty(l_chn->mode, l_chn->channel);
                    } else {
                        if (l_chn->fade_once != true) {
                            l_chn->fade_step_up = 1 - l_chn->fade_step_up;
                        } else {
                            l_chn->fade_start = false;
                        }
                    }
                }
            }
        }
    }
}

static light_channel_t *light_channel_create(gpio_num_t io_num, ledc_channel_t channel, ledc_mode_t mode, ledc_timer_t timer)
{
    ledc_channel_config_t ledc_channel = {
        .channel    = channel,
        .duty       = 0,
        .gpio_num   = io_num,
        .intr_type  = LEDC_INTR_FADE_END,
        .speed_mode = mode,
        .timer_sel  = timer
    };
    ERR_ASSERT(TAG, ledc_channel_config(&ledc_channel), NULL);
    light_channel_t *pwm = (light_channel_t *)calloc(1, sizeof(light_channel_t));
    pwm->io_num            = io_num;
    pwm->channel           = channel;
    pwm->mode              = mode;
    pwm->breath_period     = 0;
    pwm->blink_period      = 0;
    pwm->fade_step_num     = 0;
    pwm->fade_duty_step    = NULL;
    pwm->fade_duty_counter = 0;
    pwm->fade_step_up      = true;
    pwm->fade_once         = false;
    pwm->fade_start        = false;
    return pwm;
}

static esp_err_t light_channel_delete(light_channel_t *light_channel)
{
    POINT_ASSERT(TAG, light_channel);

    if (light_channel->fade_duty_step != NULL) {
        heap_caps_free(light_channel->fade_duty_step);
        light_channel->fade_duty_step = NULL;
    }

    free(light_channel);
    return ESP_OK;
}

static esp_err_t light_channel_fade_clear(light_channel_t *light_channel)
{
    light_channel->fade_start        = false;
    light_channel->fade_step_num     = 0;
    light_channel->fade_duty_counter = 0;
    light_channel->fade_step_up      = true;
    light_channel->fade_once         = false;

    if (light_channel->fade_duty_step != NULL) {
        heap_caps_free(light_channel->fade_duty_step);
        light_channel->fade_duty_step = NULL;
    }

    return ESP_OK;
}

light_handle_t iot_light_create(ledc_timer_t timer, ledc_mode_t speed_mode, uint32_t freq_hz, uint8_t channel_num, ledc_timer_bit_t timer_bit)
{
    IOT_CHECK(TAG, channel_num != 0, NULL);
    ledc_timer_config_t timer_conf = {
        .timer_num = timer,
        .speed_mode = speed_mode,
        .duty_resolution = timer_bit,
        .freq_hz = freq_hz,
    };
    ERR_ASSERT(TAG, ledc_timer_config(&timer_conf), NULL);
    light_t *light_ptr = (light_t *)calloc(1, sizeof(light_t) + sizeof(light_channel_t *) * channel_num);
    light_ptr->channel_num          = channel_num;
    light_ptr->ledc_timer           = timer;
    light_ptr->full_duty            = (1 << timer_bit) - 1;
    light_ptr->freq_hz              = freq_hz;
    light_ptr->mode                 = speed_mode;
    light_ptr->timer_bit            = timer_bit;
    light_ptr->hw_timer.timer_group = HW_TIMER_GROUP;
    light_ptr->hw_timer.timer_id    = HW_TIMER_ID;

    if (g_hw_timer_started == false) {
        iot_timer_create(&(light_ptr->hw_timer), 1, (double)DUTY_SET_CYCLE / 1000, (void *)breath_timer_callback);
        iot_timer_start(&(light_ptr->hw_timer));
    }

    for (int i = 0; i < channel_num; i++) {
        light_ptr->channel_group[i] = NULL;
    }

    for (int i = 0; i < LIGHT_NUM_MAX; i++) {
        if (g_light_group[i] == NULL) {
            g_light_group[i] = light_ptr;
            break;
        }
    }

    return (light_handle_t)light_ptr;
}

esp_err_t iot_light_delete(light_handle_t light_handle)
{
    light_t *light = (light_t *)light_handle;
    POINT_ASSERT(TAG, light_handle);

    for (int i = 0; i < light->channel_num; i++) {
        if (light->channel_group[i] != NULL) {
            iot_light_duty_set(light, i, 0);
            light_channel_delete(light->channel_group[i]);
        }
    }

    for (int i = 0; i < LIGHT_NUM_MAX; i++) {
        if (g_light_group[i] == light) {
            g_light_group[i] = NULL;
            break;
        }
    }

    for (int i = 0; i < LIGHT_NUM_MAX; i++) {
        if (g_light_group[i] != NULL) {
            goto FREE_MEM;
        }
    }

    ledc_fade_func_uninstall();
    g_fade_installed = false;
    iot_timer_stop(&(light->hw_timer));
FREE_MEM:
    free(light_handle);
    return ESP_OK;
}

esp_err_t iot_light_channel_regist(light_handle_t light_handle, uint8_t channel_idx, gpio_num_t io_num, ledc_channel_t channel)
{
    light_t *light = (light_t *)light_handle;
    POINT_ASSERT(TAG, light_handle);
    IOT_CHECK(TAG, channel_idx < light->channel_num, FAIL);

    if (light->channel_group[channel_idx] != NULL) {
        ESP_LOGE(TAG, "this channel index has been registered");
        return ESP_FAIL;
    }

    light->channel_group[channel_idx] = light_channel_create(io_num, channel, light->mode, light->ledc_timer);

    if (g_fade_installed == false) {
        ledc_fade_func_install(0);
        g_fade_installed = true;
    }

    return ESP_OK;
}

static esp_err_t iot_light_duty_set(light_handle_t light_handle, uint8_t channel_id, uint32_t duty)
{
    light_t *light = (light_t *)light_handle;
    POINT_ASSERT(TAG, light_handle);
    IOT_CHECK(TAG, channel_id < light->channel_num, ESP_FAIL);
    POINT_ASSERT(TAG, light->channel_group[channel_id]);
    light_channel_t *l_chn = light->channel_group[channel_id];

    light_channel_fade_clear(l_chn);

    iot_ledc_set_duty(l_chn->mode, l_chn->channel, duty);
    iot_ledc_update_duty(l_chn->mode, l_chn->channel);
    return ESP_OK;
}

uint32_t iot_light_duty_get(light_handle_t light_handle, uint8_t channel_id)
{
    light_t *light = (light_t *)light_handle;
    POINT_ASSERT(TAG, light_handle);
    IOT_CHECK(TAG, channel_id < light->channel_num, ESP_FAIL);
    POINT_ASSERT(TAG, light->channel_group[channel_id]);
    light_channel_t *l_chn = light->channel_group[channel_id];

    return ledc_get_duty(l_chn->mode, l_chn->channel);
}

esp_err_t iot_light_fade_with_time(light_handle_t light_handle, uint8_t channel_id, uint32_t duty, uint32_t fade_period_ms)
{
    light_t *light = (light_t *)light_handle;
    POINT_ASSERT(TAG, light_handle);
    IOT_CHECK(TAG, channel_id < light->channel_num, ESP_FAIL);
    POINT_ASSERT(TAG, light->channel_group[channel_id]);
    light_channel_t *l_chn = light->channel_group[channel_id];

    if (fade_period_ms == 0) {
        return iot_light_duty_set(light, channel_id, duty);
    }

    light_channel_fade_clear(l_chn);

    l_chn->fade_step_num = fade_period_ms / DUTY_SET_CYCLE + 1;
    l_chn->fade_duty_step = (uint32_t *)heap_caps_malloc(l_chn->fade_step_num * sizeof(uint32_t), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);

    uint32_t duty_cur = iot_light_duty_get(light, channel_id);
    int duty_delta = duty - duty_cur;

    if (duty_delta > 0) {
        float coe = (duty_delta / pow((double)l_chn->fade_step_num, (double)1 / DUTY_SET_GAMMA));

        for (int i = 0; i < l_chn->fade_step_num; i++) {
            l_chn->fade_duty_step[i] = duty_cur + (int)(coe * pow((double)i, (double)1 / DUTY_SET_GAMMA));
        }

        l_chn->fade_step_up = true;
    } else {
        duty_delta = 0 - duty_delta;
        float coe = (duty_delta / pow((double)l_chn->fade_step_num, (double)1 / DUTY_SET_GAMMA));

        for (int i = 0; i < l_chn->fade_step_num; i++) {
            l_chn->fade_duty_step[i] = duty + (int)(coe * pow((double)i, (double)1 / DUTY_SET_GAMMA));
        }

        l_chn->fade_duty_counter = l_chn->fade_step_num;
        l_chn->fade_step_up = false;
    }

    l_chn->fade_once  = true;
    l_chn->fade_start = true;

    return ESP_OK;
}

esp_err_t iot_light_breath_config(light_handle_t light_handle, uint8_t channel_id, uint32_t duty, uint32_t breath_period_ms)
{
    light_t *light = (light_t *)light_handle;
    POINT_ASSERT(TAG, light_handle);
    IOT_CHECK(TAG, channel_id < light->channel_num, ESP_FAIL);
    POINT_ASSERT(TAG, light->channel_group[channel_id]);
    light_channel_t *l_chn = light->channel_group[channel_id];

    light_channel_fade_clear(l_chn);

    /**
     * @brief control light with nonlinearity fade, the duty and the fade step
     *        conform to gamma curve. gamma curve formula: y=a*x^(1/gm) firstly,
     *        use the target duty and step number to calculate coefficient `a`,
     *        secondly, calculate the duty for every step
     */

    l_chn->fade_step_num = (breath_period_ms / 2) / DUTY_SET_CYCLE + 1;
    float coe = (duty / pow((double)l_chn->fade_step_num, (double)1 / DUTY_SET_GAMMA));

    l_chn->fade_duty_step = (uint32_t *)heap_caps_malloc(l_chn->fade_step_num * sizeof(uint32_t), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);

    for (int i = 0; i < l_chn->fade_step_num; i++) {
        l_chn->fade_duty_step[i] = (int)(coe * pow((double)i, (double)1 / DUTY_SET_GAMMA));
    }

    return ESP_OK;
}

esp_err_t iot_light_blink_config(light_handle_t light_handle, uint8_t channel_id, uint32_t blink_period_ms)
{
    light_t *light = (light_t *)light_handle;
    POINT_ASSERT(TAG, light_handle);
    IOT_CHECK(TAG, channel_id < light->channel_num, ESP_FAIL);
    light_channel_t *l_chn = light->channel_group[channel_id];

    light_channel_fade_clear(l_chn);

    l_chn->fade_step_num  = (blink_period_ms / 2) / DUTY_SET_CYCLE;
    l_chn->fade_duty_step = (uint32_t *)heap_caps_malloc(l_chn->fade_step_num * sizeof(uint32_t), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
    int i = 0;

    for (i = 0; i < (l_chn->fade_step_num / 2); i++) {
        l_chn->fade_duty_step[i] = 0;
    }

    for (; i < l_chn->fade_step_num; i++) {
        l_chn->fade_duty_step[i] = light->full_duty;
    }

    return ESP_OK;
}

esp_err_t iot_light_operate_start(light_handle_t light_handle, uint8_t channel_id)
{
    light_t *light = (light_t *)light_handle;
    POINT_ASSERT(TAG, light_handle);
    IOT_CHECK(TAG, channel_id < light->channel_num, ESP_FAIL);
    light->channel_group[channel_id]->fade_start = true;
    return ESP_OK;
}

esp_err_t iot_light_operate_stop(light_handle_t light_handle, uint8_t channel_id)
{
    light_t *light = (light_t *)light_handle;
    POINT_ASSERT(TAG, light_handle);
    IOT_CHECK(TAG, channel_id < light->channel_num, ESP_FAIL);
    light->channel_group[channel_id]->fade_start = false;
    // iot_light_duty_set(light, channel_id, 0);
    return ESP_OK;
}
