/**
 * @file rx_encoder.c
 * @brief Implementation of the quadrature encoder functionality.
 *
 * This file contains the implementation of the quadrature encoder functionality.
 * It provides functions to initialize the encoder, handle encoder work, and get the encoder count.
 */

/*
 * Copyright (c) 2022 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
 */

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/logging/log.h>

#include "rx_encoder.h"

LOG_MODULE_REGISTER(RX_ENCODER, LOG_LEVEL_INF);

static const struct gpio_dt_spec *enc_a;
static const struct gpio_dt_spec *enc_b;

#define QUAD_ENC_EMUL_PERIOD 1

rx_encoder_t *encoder;
int enc_dir, enc_cnt = 0;
bool clear_flag = false;

/**
 * @brief Clears the count asyncrounously.
 *
 * This function clears count directly, indicating that the count synchronization should be cleared.
 */
void rx_encoder_clear_count_async();

/**
 * @brief Clears the count syncrounously.
 *
 * This function sets the clear_flag variable to true, indicating that the count synchronization should be cleared.
 */
void rx_encoder_clear_count_sync();

/**
 * @brief Sets polarity of the encoder.
 *
 * This function sets the polarity of the encoder.
 */
void rx_encoder_set_polarity(rx_encoder_dir_t dir);

/**
 * @brief Handles the work for the quadrature encoder.
 *
 * This function is responsible for handling the work related to the quadrature encoder.
 * It reads the state of the encoder pins, determines the direction of rotation, and updates the encoder count.
 * Invalid transitions and states are logged as errors.
 *
 * @param work Pointer to the work item.
 */
void qenc_work_handler(struct k_work *work)
{
    int a = gpio_pin_get_dt(enc_a);
    int b = gpio_pin_get_dt(enc_b);

    int state = (a << 1) | b;

    static int prev_state = 0;

    if (clear_flag)
    {
        encoder->count = 0;
        clear_flag = false;
        return;
    }
    if (state == prev_state)
    {
        return;
    }
    switch (state)
    {
    case 0:
        if (prev_state == 1)
        {
            encoder->direction = 1;
            // encoder->count++;
        }
        else if (prev_state == 2)
        {
            encoder->direction = -1;
            // encoder->count--;
        }
        else if (prev_state == 3)
        {
            LOG_ERR("Invalid Encoder Transition Detected");
        }
        break;
    case 1:
        if (prev_state == 3)
        {
            encoder->direction = 1;
            // encoder->count++;
        }
        else if (prev_state == 0)
        {
            encoder->direction = -1;
            // encoder->count--;
        }
        else if (prev_state == 2)
        {
            LOG_ERR("Invalid Encoder Transition Detected");
        }
        break;
    case 2:
        if (prev_state == 0)
        {
            encoder->direction = 1;
            // encoder->count++;
        }
        else if (prev_state == 3)
        {
            encoder->direction = -1;
            // encoder->count--;
        }
        else if (prev_state == 1)
        {
            LOG_ERR("Invalid Encoder Transition Detected");
        }
        break;
    case 3:
        if (prev_state == 2)
        {
            encoder->direction = 1;
            // encoder->count++;
        }
        else if (prev_state == 1)
        {
            encoder->direction = -1;
            // encoder->count--;
        }
        else if (prev_state == 0)
        {
            LOG_ERR("Invalid Encoder Transition Detected");
        }
        break;

    default:
        LOG_ERR("Invalid Encoder State Detected");
        break;
    }
    encoder->count += encoder->direction * encoder->polarity;
    prev_state = state;
}

static K_WORK_DEFINE(qenc_work, qenc_work_handler);

static void qenc_timer_handler(struct k_timer *dummy)
{
    k_work_submit(&qenc_work);
}

static K_TIMER_DEFINE(qenc_timer, qenc_timer_handler, NULL);

/**
 * @brief Initializes the quadrature encoder.
 *
 * This function initializes the quadrature encoder by configuring the encoder pins and starting the timer.
 *
 * @param dose_enc_a GPIO specification for encoder pin A.
 * @param dose_enc_b GPIO specification for encoder pin B.
 */
void rx_encoder_init(rx_encoder_t *en, const struct gpio_dt_spec *dose_enc_a, const struct gpio_dt_spec *dose_enc_b)
{
    LOG_INF("Quadrature %s encoder with %u ms period\n",
            en->name, QUAD_ENC_EMUL_PERIOD);
    encoder = en;
    encoder->clear_sync = rx_encoder_clear_count_sync;
    encoder->clear_async = rx_encoder_clear_count_async;
    encoder->set_polarity = rx_encoder_set_polarity;
    encoder->count = 0;
    encoder->direction = 0;
    encoder->polarity = RX_ENCODER_DIR_CW;

    // strncpy(encoder->name, "RX Encoder", 32);

    enc_a = dose_enc_a;
    enc_b = dose_enc_b;

    if (!gpio_is_ready_dt(enc_a))
    {
        LOG_ERR("%s a device not ready.", encoder->name);
        return;
    }
    gpio_pin_configure_dt(enc_a, GPIO_INPUT);

    if (!gpio_is_ready_dt(enc_b))
    {
        LOG_ERR("%s b device not ready.", encoder->name);
        return;
    }
    gpio_pin_configure_dt(enc_b, GPIO_INPUT);

    encoder->clear_sync();
    k_timer_start(&qenc_timer, K_MSEC(QUAD_ENC_EMUL_PERIOD),
                  K_MSEC(QUAD_ENC_EMUL_PERIOD));
}

void rx_encoder_clear_count_sync()
{
    LOG_WRN("Clearing the %s count", encoder->name);
    clear_flag = true;
}

void rx_encoder_clear_count_async()
{
    clear_flag = true;
}

void rx_encoder_set_polarity(rx_encoder_dir_t dir)
{
    encoder->polarity = dir;
}