#include <stdio.h>

#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "freertos/task.h"
#include "freertos/semphr.h"

#include "esp_timer.h"
#include "esp_log.h"
#include "esp_err.h"

#include "transaction.h"


#define TAG    "TRANS"

static transaction_t transactions[MAX_TRANSACTION_COUNT];
static SemaphoreHandle_t trans_mutex;

static uint32_t utils_get_system_ts(void)
{
    return esp_log_timestamp();
}

static void transaction_reset(transaction_t *trans)
{
    EventBits_t bits;

    ESP_LOGV(TAG, "transaction reset: %x", (uint32_t) trans);
    // set to inactive state and clear all bits of the transaction
    xSemaphoreTakeRecursive(trans_mutex, portMAX_DELAY);
    trans->type = 0;
    trans->sub_type = 0;
    trans->current_bits = 0;
    bits = xEventGroupGetBits(trans->event_group);
    xEventGroupClearBits(trans->event_group, bits);
    trans->state = TRANSACTION_INACTIVE;
    trans->ret = ESP_OK;
    xSemaphoreGiveRecursive(trans_mutex);
}

void transaction_deinit(transaction_t *trans)
{
    if (trans != NULL) {
        transaction_reset(trans);
    }
}

esp_err_t transaction_set_events(transaction_t *trans, EventBits_t events)
{
    esp_err_t ret = ESP_OK;
    xSemaphoreTakeRecursive(trans_mutex, portMAX_DELAY);
    if (trans) {
        if (trans->state == TRANSACTION_INACTIVE) {
            ret = TRANS_RET_STATE_ERR;
        } else {
            // if the task (task A) setting current bits is with higher priority than the task (task B) run transaction,
            // current_bits might not be updated until task A yield (not it's only update in run_transaction function).
            // If task A set events and immediately use current_bits, current_bits is not correct.
            // update current_bits here to make sure it's updated
            trans->current_bits |= events;
            xEventGroupSetBits(trans->event_group, events);
        }
        ESP_LOGD(TAG, "transactions set events: %x, %x, %x, %x; ret: %x", (uint32_t) trans, trans->type, trans->sub_type, events, ret);
    } else {
        ret = TRANS_RET_INVALID_TRNSACTION;
    }
    xSemaphoreGiveRecursive(trans_mutex);
    return ret;
}

esp_err_t transaction_test_events(transaction_t *trans, EventBits_t events)
{
    esp_err_t ret = TRANS_RET_TEST_EVENT_FAILED;

    xSemaphoreTakeRecursive(trans_mutex, portMAX_DELAY);
    if (trans) {
        if (trans->state == TRANSACTION_INACTIVE) {
            ret = TRANS_RET_STATE_ERR;
        } else {
            if ((trans->current_bits & events) == events) {
                ret = ESP_OK;
            }
        }
        ESP_LOGV(TAG, "transactions test events: %x, %x; ret: %x", (uint32_t) trans, events, ret);
    } else {
        ret = TRANS_RET_INVALID_TRNSACTION;
    }
    xSemaphoreGiveRecursive(trans_mutex);
    return ret;
}

esp_err_t transaction_clear_events(transaction_t *trans, EventBits_t events)
{
    esp_err_t ret = ESP_OK;
    xSemaphoreTakeRecursive(trans_mutex, portMAX_DELAY);
    if (trans) {
        if (trans->state == TRANSACTION_INACTIVE) {
            ret = TRANS_RET_STATE_ERR;
        } else {
            trans->current_bits &= ~events;
            xEventGroupClearBits(trans->event_group, events);
        }
        ESP_LOGD(TAG, "transactions clear events: %x, %x, %x, %x; ret: %x", (uint32_t) trans, trans->type, trans->sub_type, events, ret);
    } else {
        ret = TRANS_RET_INVALID_TRNSACTION;
    }
    xSemaphoreGiveRecursive(trans_mutex);
    return ret;
}

esp_err_t transaction_abort(transaction_t *trans, esp_err_t reason)
{
    esp_err_t ret = ESP_OK;
    xSemaphoreTakeRecursive(trans_mutex, portMAX_DELAY);
    if (trans) {
        if (trans->state == TRANSACTION_INACTIVE) {
            ret = TRANS_RET_STATE_ERR;
        } else {
            trans->ret = reason;
            xEventGroupSetBits(trans->event_group, TRANSACTION_ABORT_EVENT);
        }
        ESP_LOGD(TAG, "transactions abort: %x, %x, %x, %x; ret: %x", (uint32_t) trans, trans->type, trans->sub_type, reason, ret);
    } else {
        ret = TRANS_RET_INVALID_TRNSACTION;
    }
    xSemaphoreGiveRecursive(trans_mutex);
    return ret;
}

esp_err_t transaction_init(transaction_t **trans, uint8_t type, uint32_t sub_type, EventBits_t wait_events, uint32_t timeout, void *input, void *output)
{
    esp_err_t ret = ESP_OK;
    uint8_t i;

    if ((wait_events & TRANSACTION_EVENT_MASK)
            && wait_events != TRANSACTION_TIMEOUT_EVENT) {
        ret = TRANS_RET_EVENTS_CONFLICT;
        return ret;
    }

    xSemaphoreTakeRecursive(trans_mutex, portMAX_DELAY);
    for (i = 0; i < MAX_TRANSACTION_COUNT; i++) {
        if ( transactions[i].state == TRANSACTION_INACTIVE ) {
            transactions[i].state = TRANSACTION_ACTIVE;
            *trans = &transactions[i];
            break;
        }
    }

    if ( i == MAX_TRANSACTION_COUNT ) {
        ret = TRANS_RET_FAILED_TO_ALLOCATE;
    } else {
        // init transaction
        transactions[i].type = type;
        transactions[i].wait_events = wait_events;
        transactions[i].sub_type = sub_type;
        transactions[i].timeout = timeout;
        transactions[i].ret = ESP_OK;
        transactions[i].input = input;
        transactions[i].output = output;
    }
    xSemaphoreGiveRecursive(trans_mutex);

    if (ret == ESP_OK) {
        ESP_LOGD(TAG, "transaction created: %x, %x, %x; ret: %x", type, sub_type, (uint32_t) *trans, ret);
    }
    return ret;
}

esp_err_t transaction_run(transaction_t *trans)
{
    esp_err_t ret = ESP_OK;
    uint32_t start_time;
    int32_t wait_time;
    EventBits_t current_bits;

    if (trans) {
        start_time = utils_get_system_ts();

        // wait for wait events
        while (1) {
            //TODO: we didn't handle ts overflow
            wait_time = start_time + trans->timeout - utils_get_system_ts();

            if ( wait_time < 0 ) {
                ESP_LOGI(TAG, "transaction timeout: %x, %x, %x, %x, %x", (uint32_t) trans, trans->type, trans->sub_type, trans->wait_events, trans->current_bits);
                ret = TRANS_RET_TIMEOUT;
                break;
            }

            // trans->event_group and trans->wait_events will not be changed once trans is created, so we don't need protect them
            current_bits = xEventGroupWaitBits(trans->event_group, trans->wait_events | TRANSACTION_ABORT_EVENT,
                    1, 0, wait_time/portTICK_RATE_MS);

            xSemaphoreTakeRecursive(trans_mutex, portMAX_DELAY);
            trans->current_bits |= current_bits;

            if (trans->current_bits == trans->wait_events) {
                // wait succeeded, we copy the trans->ret as ret of run transaction. This value could be changed by
                ret = trans->ret;
                xSemaphoreGiveRecursive(trans_mutex);
                break;
            } else if ( trans->current_bits & TRANSACTION_ABORT_EVENT ) {
                if ( trans->ret ) {
                    // copy user defined ret value if it's set
                    ret = trans->ret;
                } else {
                    ret = TRANS_RET_ABORTED;
                }
                xSemaphoreGiveRecursive(trans_mutex);
                break;
            }
            xSemaphoreGiveRecursive(trans_mutex);
        }
        ESP_LOGD(TAG, "transaction run: %x, %x, %x; ret: %x", (uint32_t) trans, trans->type, trans->sub_type, ret);
        // reset after it's finished
        transaction_reset(trans);
    } else {
        ESP_LOGD(TAG, "transaction run: %x; ret: %x", (uint32_t) trans, ret);
        ret = TRANS_RET_INVALID_TRNSACTION;
    }

    return ret;
}

transaction_t *transaction_get(uint8_t type, uint32_t sub_type, transaction_t *start)
{
    uint8_t i, start_index;
    transaction_t *trans = NULL;

    if ( start == NULL ) {
        start_index = 0;
    } else {
        start_index = (start - transactions) + 1;
    }

    xSemaphoreTakeRecursive(trans_mutex, portMAX_DELAY);
    for (i = start_index; i < MAX_TRANSACTION_COUNT; i++) {
        if ( transactions[i].state == TRANSACTION_ACTIVE ) {
            if ( (transactions[i].type == type) && (transactions[i].sub_type & sub_type) ) {
                trans = &transactions[i];
                break;
            }
        }
    }
    xSemaphoreGiveRecursive(trans_mutex);

    ESP_LOGV(TAG, "transaction get: %x, %x, %x, %x", type, sub_type, (uint32_t) start, (uint32_t) trans);
    return trans;
}

void init_transactions(void)
{
    uint8_t i;

    ESP_LOGI(TAG, "init transactions");

    trans_mutex = xSemaphoreCreateRecursiveMutex();

    for (i = 0; i < MAX_TRANSACTION_COUNT; i++) {
        transactions[i].event_group = xEventGroupCreate();
        transaction_reset(&transactions[i]);
    }
}
