/*
 * @Author: wzh
 * @Date: 2024-11-18 09:43:50
 * @LastEditors: wzh
 * @LastEditTime: 2024-11-26 11:08:57
 * @Description: Encoder driver for 2-phase encoder and 3-phase encoder through GPIO interrupts.
 *
 * Copyright (c) 2024 by wzh, All Rights Reserved.
 */
#include "et_encoder.h"
#include <stdlib.h>

static const int8_t phase2_state_table[4][4] = {
    // New states: 0  1  2  3
    // New states: 0b00 to 0b11
    { 0, +1, -1,  0}, /* Old 0 */
    {-1,  0,  0, +1}, /* Old 1 */
    {+1,  0,  0, -1}, /* Old 2 */
    { 0, -1, +1,  0}  /* Old 3 */
};

/**
 * @brief Initialize the encoder structure.
 * @param encoder Pointer to the encoder structure to be initialized
 */
void et_encoder_init(et_encoder_t *encoder)
{
    if (!encoder)
        return;
    encoder->position    = 0;
    encoder->direction   = 1; // Default forward
    encoder->initialized = true;
}

/**
 * @brief Encoder GPIO interrupt service routine
 * @param encoder Pointer to the encoder structure
 * @param channel_a State of Channel A (true for high, false for low)
 */
void et_encoder_isr(et_encoder_t *encoder, bool channel_a, bool channel_b)
{
    if (!encoder || !encoder->initialized)
        return;

    uint8_t new_state = (channel_a << 1) | channel_b;

    int8_t delta = phase2_state_table[encoder->last_state][new_state];

    if (delta > 0)
        encoder->direction = ET_ENCODER_DIR_FORWARD;
    else if (delta < 0)
        encoder->direction = ET_ENCODER_DIR_REVERSE;
    else
        encoder->direction = ET_ENCODER_DIR_UNKNOWN;

    encoder->last_state  = new_state;
    encoder->position   += delta;
}

ET_EncoderDir et_encoder_get_dir(et_encoder_t *encoder)
{
    if (!encoder || !encoder->initialized)
        return ET_ENCODER_DIR_UNKNOWN;
    return encoder->direction;
}

/**
 * @brief Get the current encoder position
 * @param encoder Pointer to the encoder structure
 */
int32_t et_encoder_get_position(et_encoder_t *encoder)
{
    if (!encoder || !encoder->initialized)
        return 0;
    return encoder->position;
}


/**
 * @brief Reset the encoder position to zero.
 * @param encoder Pointer to the encoder structure
 */
void et_encoder_reset(et_encoder_t *encoder)
{
    if (!encoder || !encoder->initialized)
        return;
    encoder->position = 0;
}

/**
 * @brief Set the encoder position to a specific value
 * @param encoder Pointer to the encoder structure
 * @param position The desired encoder position
 */
void et_encoder_set_position(et_encoder_t *encoder, int32_t position)
{
    if (!encoder || !encoder->initialized)
        return;
    encoder->position = position;
}


/* Encoder state transitions table */
static const int8_t phase3_state_table[8][8] = {
    // New states: 0b000 to 0b111
    { 0,  1, -1,  0, -1,  0,  0,  1}, // Old state: 0b000
    {-1,  0,  0,  1,  0,  1, -1,  0}, // Old state: 0b001
    { 1,  0,  0, -1,  0, -1,  1,  0}, // Old state: 0b010
    { 0, -1,  1,  0,  1,  0,  0, -1}, // Old state: 0b011
    {-1,  0,  0,  1,  0,  1, -1,  0}, // Old state: 0b100
    { 0, -1,  1,  0, -1,  0,  0,  1}, // Old state: 0b101
    { 1,  0,  0, -1,  0, -1,  1,  0}, // Old state: 0b110
    { 0,  1, -1,  0, -1,  0,  0,  1}  // Old state: 0b111
};

/**
 * @brief Initialize the 3-phase encoder structure
 * @param encoder Pointer to the encoder structure to be initialized
 */
void et_encoder_3phase_init(et_encoder_3phase_t *encoder)
{
    if (!encoder) return;
    encoder->position    = 0;
    encoder->direction   = ET_ENCODER_DIR_UNKNOWN; // Default forward
    encoder->last_state  = 0;
    encoder->initialized = true;
}

/**
 * @brief Encoder GPIO interrupt service routine for 3-phase signals.
 * @param encoder Pointer to the encoder structure
 * @param a State of Channel A (true for high, false for low)
 * @param b State of Channel B (true for high, false for low)
 * @param c State of Channel C (true for high, false for low)
 */
void et_encoder_3phase_isr(et_encoder_3phase_t *encoder, bool a, bool b, bool c)
{
    if (!encoder || !encoder->initialized) return;

    // Combine A, B, C states into a single 3-bit value
    uint8_t new_state = (a << 2) | (b << 1) | c;

    // Calculate position change using transition table
    int8_t delta = phase3_state_table[encoder->last_state][new_state];

    // Update encoder position and direction
    encoder->position  += delta;
    encoder->direction  = (delta > 0) ? ET_ENCODER_DIR_FORWARD : (delta < 0) ? ET_ENCODER_DIR_REVERSE
                                                                             : ET_ENCODER_DIR_UNKNOWN;

    // Update last state
    encoder->last_state = new_state;
}

ET_EncoderDir et_encoder_3phase_get_dir(et_encoder_3phase_t *encoder)
{
    if (!encoder || !encoder->initialized)
        return ET_ENCODER_DIR_UNKNOWN;
    return encoder->direction;
}

/**
 * @brief Get the current encoder position.
 * @param encoder Pointer to the encoder structure
 */
int32_t et_encoder_3phase_get_position(et_encoder_3phase_t *encoder)
{
    if (!encoder || !encoder->initialized)
        return 0;
    return encoder->position;
}

/**
 * @brief Reset the encoder position to zero.
 * @param encoder Pointer to the encoder structure
 */
void et_encoder_3phase_reset(et_encoder_3phase_t *encoder)
{
    if (!encoder || !encoder->initialized)
        return;
    encoder->position = 0;
}

/**
 * @brief Set the encoder position to a specific value
 * @param encoder Pointer to the encoder structure
 * @param position The desired encoder position
 */
void et_encoder_3phase_set_position(et_encoder_3phase_t *encoder, int32_t position)
{
    if (!encoder || !encoder->initialized)
        return;
    encoder->position = position;
}

