#include "periph/gpio.h"
#include "periph/uart.h"
#include "include/stepper.h"
#include "include/stepper_params.h"
#include "include/sensor.h"
#include "include/gb_math.h"

#define ENABLE_DEBUG (0)
#include "debug.h"

#define STEP_PIN (stepper->p.step_pin)
#define DIR_PIN (stepper->p.dir_pin)
#define ENABLE_PIN (stepper->p.enable_pin)
#define UART (stepper->p.uart)


static void stepper_event(event_t *_event)
{
    stepper_t *stepper = container_of(_event, stepper_t, event);
    stepper_step(stepper);
    stepper_update(stepper);
}

int stepper_init(stepper_t *stepper, const stepper_params_t *params)
{
    stepper->p = *params;
    event_t event = { .handler = stepper_event };
    stepper->event = event;
    stepper->velocity_setpoint = 0.0;
    stepper->step_count = 0;
    stepper->time_step = 0;
    stepper->step_angle = GB_MATH_TAU / (float)(stepper->p.division * stepper->p.microstep);

    event_timeout_init(&(stepper->event_timeout), stepper->queue, (event_t*)&(stepper->event));

    gpio_init(STEP_PIN, GPIO_OUT);
    gpio_init(DIR_PIN, GPIO_OUT);
    gpio_init(ENABLE_PIN, GPIO_OD);

    stepper_enable(stepper);

    if(uart_init(UART, 38400, NULL, NULL))
        return STEPPER_ERR;

    le_uint32_t data;
    data.u32 = STEPPER_GCONF_VALUES;

    stepper_write(stepper, STEPPER_GCONF_ADDR, &data);
    data.u32 = STEPPER_CHOPCONF_VALUES;
    stepper_write(stepper, STEPPER_CHOPCONF_ADDR, &data);

    //event_post(stepper->queue, &(stepper->event));
    printf("initialized descriptor 0x%08x\n", (unsigned)stepper);
    printf("initialized event 0x%08x\n", (unsigned)&(stepper->event));
    printf("initialized queue 0x%08x\n", (unsigned)stepper->queue);

    return STEPPER_OK;
}

void stepper_set_setpoint(stepper_t *stepper, int16_t count, uint32_t time_step)
{
    //Write the count to the setpoint field.
    stepper->step_setpoint = count;
    //Set direction
    if (stepper->step_setpoint < stepper->step_count)
    {
        stepper_set_direction(stepper, STEPPER_REVERSE);
        //Write the time_step to the time_step field.
        stepper->time_step = time_step/(stepper->step_count - stepper->step_setpoint);
    }
    else 
    {
        stepper_set_direction(stepper, STEPPER_FORWARD);
        //Write the time_step to the time_step field.
        stepper->time_step = time_step/(stepper->step_setpoint - stepper->step_count);
    }
    if (stepper->time_step <= 400){
        stepper->time_step = 400;
    }
    //Call update to set a timeout.
    stepper_update(stepper);
}

void stepper_get_angle(stepper_t *stepper, float *angle)
{
    *angle = stepper->step_count * stepper->step_angle;
    return;
}

int32_t stepper_get_count(stepper_t *stepper, float angle)
{
    return (int32_t) gb_round(angle/stepper->step_angle);
}

void stepper_update(stepper_t *stepper)
{
    // Set thread flags and return if the stepper is at the desired position.
    if(stepper->step_count == stepper->step_setpoint)
    {
        thread_flags_set(stepper->thread, stepper->flags);
        return;
    }
    // Set a new timeout event.
    event_timeout_set(&stepper->event_timeout, stepper->time_step);
}

/*
int stepper_time_step(stepper_t *stepper){
    if (stepper->velocity_setpoint == 0.0) {
        return STEPPER_ERR;
    }
    float step_time = stepper->step_angle / stepper->velocity_setpoint;
    stepper->time_step = (uint32_t)(step_time*1000000.0); 
    return STEPPER_OK;
}
*/

int stepper_step(stepper_t *stepper)
{
    if (!stepper->enable) {
        return STEPPER_DISABLED;
    }
    if (stepper->direction == STEPPER_FORWARD)
    {
        stepper->step_count++;
    }
    else
    {
        stepper->step_count--;
    }
    gpio_toggle(STEP_PIN);
    //printf("Stepper %c @ %i\n", stepper->id, stepper->step_count);
    return STEPPER_OK;
}

uint8_t stepper_crc8(const uint8_t *data, size_t len, uint8_t g_polynom, uint8_t crc){
    for (size_t i=0; i < len; i++)
    {
        uint8_t current_byte = data[i];
        for (size_t i=0; i < 8; i++)
        {
            bool xor = (crc >> 7) ^ (current_byte & 0x01);
            crc = crc <<1;
            crc = xor ? crc ^ g_polynom : crc;
            current_byte = current_byte >> 1;
        }
    }
    return crc;
}

/*
void stepper_set_velocity(stepper_t *stepper, float velocity)
{
    if (velocity > 0)
    {
        stepper->velocity_setpoint = gb_min(velocity,20);//20
        stepper_set_direction(stepper, STEPPER_FORWARD);
    }
    else
    {
        stepper->velocity_setpoint = gb_min(-velocity,20);//20
        stepper_set_direction(stepper, STEPPER_REVERSE);
    }
    stepper_update(stepper);
}
*/

void stepper_set_direction(stepper_t *stepper, stepper_direction_t direction){
    stepper->direction = direction;
    if (direction == STEPPER_REVERSE)
    {
        gpio_set(DIR_PIN);
    }
    else
    {
        gpio_clear(DIR_PIN);
    }
}

void stepper_write(stepper_t *stepper, uint8_t reg, le_uint32_t *data)
{
    uint8_t* message;
    message = (uint8_t*) malloc(8 * sizeof(uint8_t));
    message[0] = STEPPER_UART_SYNC;
    message[1] = STEPPER_UART_SLAVE_ADDR;
    message[2] = reg | 0x80;
    memcpy(&message[3], data->u8, 4);
    message[7] = 0x0;

    uint8_t crc = stepper_crc8(message, 7, 0x07, 0x0);
    message[7] = crc;

    uart_write(UART, message, 8);
/*    printf("\n");
    for (int i = 0; i<8; i++)
    {
           printf("%0.2x",message[i]);
    }
    printf("\n");
    */
    return;
}

void stepper_read(stepper_t *stepper, uint8_t reg)
{
    uint8_t* message;
    message = (uint8_t*) malloc(4 * sizeof(uint8_t));
    message[0] = STEPPER_UART_SYNC;
    message[1] = STEPPER_UART_SLAVE_ADDR;
    message[2] = reg;
    message[3] = stepper_crc8(message, 3, 0x07, 0x0);
    uart_write(UART, message, 4);
    return;
}

void stepper_enable(stepper_t *stepper){
    stepper->enable = true;
    gpio_clear(ENABLE_PIN);
}

void stepper_disable(stepper_t *stepper){
    stepper->enable = false;
    gpio_set(ENABLE_PIN);
}






