// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/*
 * After station connects to AP and gets IP address by smartconfig,
 * it will use UDP to send 'ACK' to cellphone.
 */

#include <string.h>
#include <stdlib.h>
#include <stddef.h>

#include <sys/socket.h>
#include <netdb.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "tcpip_adapter.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_smartconfig.h"
#include "smartconfig_ack.h"

static const char *TAG = "smartconfig";

/* Flag to indicate sending smartconfig ACK or not. */
static bool s_sc_ack_send = false;
static void *s_sc_ack_info = NULL;
static size_t s_sc_ack_info_size = 0;

static int sc_ack_send_get_errno(int fd)
{
    int sock_errno = 0;
    u32_t optlen = sizeof(sock_errno);

    getsockopt(fd, SOL_SOCKET, SO_ERROR, &sock_errno, &optlen);

    return sock_errno;
}

static void sc_ack_send_task(void *pvParameters)
{
    sc_ack_t *ack = (sc_ack_t *)pvParameters;
    tcpip_adapter_ip_info_t local_ip;
    sc_callback_data_t sc_callback_data;
    uint8_t remote_ip[4];
    memset(&sc_callback_data, 0x0, sizeof(sc_callback_data_t));
    if (ack->type == SC_ACK_TYPE_ESPTOUCH) {
        memcpy(sc_callback_data.ip, ack->ctx.ip, sizeof(sc_callback_data.ip));
    }
    memcpy(remote_ip, ack->ctx.ip, sizeof(remote_ip));
    sc_callback_data.type = ack->type;
    int remote_port = (ack->type == SC_ACK_TYPE_ESPTOUCH) ? SC_ACK_TOUCH_SERVER_PORT : SC_ACK_AIRKISS_SERVER_PORT;
    struct sockaddr_in server_addr;
    socklen_t sin_size = sizeof(server_addr);
    int send_sock = -1;
    int optval = 1;
    int sendlen;
    int ack_len = (ack->type == SC_ACK_TYPE_ESPTOUCH) ? SC_ACK_TOUCH_LEN : SC_ACK_AIRKISS_LEN;
    uint8_t packet_count = 1;
    int err;
    int ret;

    bzero(&server_addr, sizeof(struct sockaddr_in));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr((const char*)remote_ip);
    server_addr.sin_port = htons(remote_port);

    esp_wifi_get_mac(WIFI_IF_STA, ack->ctx.mac);

    vTaskDelay(200 / portTICK_RATE_MS);

    while (s_sc_ack_send) {
        /* Get local IP address of station */
        ret = tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &local_ip);
        if ((ESP_OK == ret) && (local_ip.ip.addr != INADDR_ANY)) {
            /* If ESP touch, smartconfig ACK contains local IP address. */
            if (ack->type == SC_ACK_TYPE_ESPTOUCH) {
                memcpy(ack->ctx.ip, &local_ip.ip.addr, 4);
            }

            /* Create UDP socket. */
            send_sock = socket(AF_INET, SOCK_DGRAM, 0);
            if ((send_sock < LWIP_SOCKET_OFFSET) || (send_sock > (FD_SETSIZE - 1))) {
                ESP_LOGE(TAG,  "Creat udp socket failed");
                goto _end;	
            }

            setsockopt(send_sock, SOL_SOCKET, SO_BROADCAST | SO_REUSEADDR, &optval, sizeof(int));

            if (ack->type == SC_ACK_TYPE_AIRKISS) {
                char data = 0;
                struct sockaddr_in local_addr, from;
                socklen_t sockadd_len = sizeof(struct sockaddr);
                struct timeval timeout = {
                    SC_ACK_AIRKISS_TIMEOUT / 1000,
                    SC_ACK_AIRKISS_TIMEOUT % 1000 * 1000
                };

                bzero(&local_addr, sizeof(struct sockaddr_in));
                bzero(&from, sizeof(struct sockaddr_in));
                local_addr.sin_family = AF_INET;
                local_addr.sin_addr.s_addr = INADDR_ANY;
                local_addr.sin_port = htons(SC_ACK_AIRKISS_DEVICE_PORT);

                bind(send_sock, (struct sockaddr *)&local_addr, sockadd_len);
                setsockopt(send_sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));

                recvfrom(send_sock, &data, 1, 0, (struct sockaddr *)&from, &sockadd_len);
                if (from.sin_addr.s_addr != INADDR_ANY) {
                    memcpy(remote_ip, &from.sin_addr, 4);
                    memcpy(sc_callback_data.ip, &from.sin_addr, sizeof(sc_callback_data.ip));
                    server_addr.sin_addr.s_addr = from.sin_addr.s_addr;
                } else {
                    if (ack->cb) {
                        ack->cb(SC_STATUS_LINK_OVER, &sc_callback_data);
                    }
                    goto _end;
                }
            }

            while (s_sc_ack_send) {
                /* Send smartconfig ACK every 100ms. */
                vTaskDelay(100 / portTICK_RATE_MS);

                if (s_sc_ack_info) {
                    sendlen = sendto(send_sock, s_sc_ack_info, s_sc_ack_info_size, 0, (struct sockaddr*) &server_addr, sin_size);
                } else {
                    sendlen = sendto(send_sock, &ack->ctx, ack_len, 0, (struct sockaddr*) &server_addr, sin_size);
                }

                if (sendlen > 0) {
                    /* Totally send 30 smartconfig ACKs. Then smartconfig is successful. */
                    if (packet_count++ >= SC_ACK_MAX_COUNT) {
                        if (ack->link_flag) {
                            *ack->link_flag = 1;
                        }
                        if (ack->cb) {
                            ack->cb(SC_STATUS_LINK_OVER, &sc_callback_data);
                        }
                        goto _end;
                    }
                }
                else {
                    err = sc_ack_send_get_errno(send_sock);
                    if (err == ENOMEM || err == EAGAIN) {
                        ESP_LOGD(TAG, "send failed, errno %d", err);
                        continue;
                    }
                    ESP_LOGE(TAG, "send failed, errno %d", err);
                    goto _end;
                }
            }
        }
        else {
            vTaskDelay((portTickType)(100 / portTICK_RATE_MS));
        }
    }

_end:
    if ((send_sock >= LWIP_SOCKET_OFFSET) && (send_sock <= (FD_SETSIZE - 1))) {
        close(send_sock);
    }
    if (s_sc_ack_info) {
        free(s_sc_ack_info);
        s_sc_ack_info = NULL;
        s_sc_ack_info_size = 0;
    }
    free(ack);
    vTaskDelete(NULL);
}

void sc_ack_send(sc_ack_t *param)
{
    sc_ack_t *ack = NULL;

    if (param == NULL) {
        ESP_LOGE(TAG, "Smart config ack parameter error");
        return;
    }

    ack = malloc(sizeof(sc_ack_t));
    if (ack == NULL) {
        ESP_LOGE(TAG, "Smart config ack parameter malloc fail");
        return;
    }
    memcpy(ack, param, sizeof(sc_ack_t));

    s_sc_ack_send = true;

    if (xTaskCreate(sc_ack_send_task, "sc_ack_send_task", SC_ACK_TASK_STACK_SIZE, ack, SC_ACK_TASK_PRIORITY, NULL) != pdPASS) {
        ESP_LOGE(TAG, "Create sending smartconfig ACK task fail");
        free(ack);
    }
}

void sc_ack_send_stop(void)
{
    s_sc_ack_send = false;
}

bool sc_ack_send_info(void *buffer, size_t size)
{
    if (!buffer || !size)
        return false;

    s_sc_ack_info = s_sc_ack_info ? realloc(s_sc_ack_info, size + 1) : malloc(size + 1);
    if (!s_sc_ack_info)
        return false;

    s_sc_ack_info_size = size;
    memset(s_sc_ack_info, 0, size + 1);
    memcpy(s_sc_ack_info, buffer, size);

    return true;
}
