/*
 * LEIGH:plc
 * Copyright 2022 Alex Leigh
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <esp_log.h>
#include <esp_netif.h>
#include <memory.h>
#include <esp_http_client.h>

#include "zero.h"
#include "mecha.h"

const char *TAG = "zero";

struct zeroS_state
{
    // Our current ZAI, or NULL
    char *url;
};

// We have only one ZDP stack per machine
struct zeroS_state zdpG_state;
static void zaiFzai_fetch();

/**
 * Initialize the ZDP subsystem. This function must be called before calling any of the
 * other ZDP functions. This function must only be called once.
 */
void zeroFinit()
{
    memset(&zdpG_state, 0, sizeof(struct zeroS_state));
    zaiFzai_fetch();
}

static void zeroF_handler(const char *buf, size_t buf_len)
{
    const struct zeroSbeacon *beacon = (const struct zeroSbeacon *)buf;

    ESP_LOGD(TAG, "Received ZDP beacon. [len: %u] [magic: %hu] [ZAI: %s]",
             buf_len, ntohs(beacon->magic), beacon->url);

    if (zdpG_state.url == NULL)
    {
        ESP_LOGD(TAG, "Current state: UNBOUND");
    }
    else
    {
        ESP_LOGD(TAG, "Current state: BOUND. [zai: %s]", zdpG_state.url);
    }

    if (buf_len < 4 || beacon->magic != htons(5200) || !strFis_terminated(beacon->url, 512) || strlen(beacon->url) == 0)
    {
        // Really just for dev/test
        ESP_LOGW(TAG, "Received corrupt ZDP packet.");
        return;
    }

    bool shouldBind = false;

    // Assume at this point that we have a safe frame

    // We bind if we are unbound, or if the provided ZAI is different and authoritative
    if (zdpG_state.url == NULL)
    {
        shouldBind = true;
    }
    else
    {
        if (strcmp(zdpG_state.url, beacon->url) != 0)
        {
            if ((beacon->flags & ZDP_FLAG_AUTHORATATIVE) == ZDP_FLAG_AUTHORATATIVE)
            {
                shouldBind = true;
            }
        }
    }

    if (shouldBind)
    {
        ESP_LOGI(TAG, "Binding ZAI. [len: %u] [magic: %hu] [ZAI: %s]",
                 buf_len, ntohs(beacon->magic), beacon->url);
        if (zdpG_state.url != NULL)
            free(zdpG_state.url);
        // We previously validated that the src string is null terminated
        zdpG_state.url = strdup(buf);
    }
}

/**
 * Start this FreeRTOS task to begin ZDP operations. Calling zdpFinit() will not start
 * this task, but that function must be called before the task is started.
 */
void zeroFtask()
{
    ESP_LOGI(TAG, "Starting ZDP client...");
    netFudp_listener(zeroF_handler, 5200, 515);
}

esp_err_t _http_event_handle(esp_http_client_event_t *evt)
{
    switch (evt->event_id)
    {
    case HTTP_EVENT_ERROR:
        ESP_LOGI(TAG, "HTTP_EVENT_ERROR");
        break;
    case HTTP_EVENT_ON_CONNECTED:
        ESP_LOGI(TAG, "HTTP_EVENT_ON_CONNECTED");
        break;
    case HTTP_EVENT_HEADER_SENT:
        ESP_LOGI(TAG, "HTTP_EVENT_HEADER_SENT");
        break;
    case HTTP_EVENT_ON_HEADER:
        ESP_LOGI(TAG, "HTTP_EVENT_ON_HEADER");
        printf("%.*s", evt->data_len, (char *)evt->data);
        break;
    case HTTP_EVENT_ON_DATA:
        ESP_LOGI(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
        if (!esp_http_client_is_chunked_response(evt->client))
        {
            printf("%.*s", evt->data_len, (char *)evt->data);
        }
        break;
    case HTTP_EVENT_ON_FINISH:
        ESP_LOGI(TAG, "HTTP_EVENT_ON_FINISH");
        break;
    case HTTP_EVENT_DISCONNECTED:
        ESP_LOGI(TAG, "HTTP_EVENT_DISCONNECTED");
        break;
        default:
        ESP_LOGI(TAG, "Unknown HTTP event");
        break;
    }
    return ESP_OK;
}

static void zaiFzai_fetch()
{
    const TickType_t xDelay = 2000 / portTICK_PERIOD_MS;
    ESP_LOGI(TAG, "zaiFzai_fetch() called.");

    esp_http_client_config_t config = {
        .url = "https://leigh-co.com/cfg/zai/",
        .event_handler = _http_event_handle,
        .buffer_size=10000,
    };
    esp_http_client_handle_t client = esp_http_client_init(&config);

    while (1)
    {
        esp_err_t err = esp_http_client_perform(client);

        if (err == ESP_OK)
        {
            ESP_LOGI(TAG, "Status = %d, content_length = %" PRId64,
                     esp_http_client_get_status_code(client),
                     esp_http_client_get_content_length(client));
            esp_http_client_cleanup(client);
            return;
        }
        else
        {
            ESP_LOGI(TAG, "zaiFzai_fetch(): HTTP request failed. Will retry.");
        }
        vTaskDelay(xDelay);
    }
}