#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <syslog.h>
#include <stdint.h>
#include <math.h>

#include <curl/curl.h>

#include "libyuarel/yuarel.h"
#include "json-c/json.h"
#include "sensob/sensob.h"
#include "aes.h"

//#include "../../status.h"
#include "../../feeder.h"



#define RECVD_HISTORY_SIZE 32//must be dividable by 8

typedef struct unit_t
{
    uint32_t id;
    uint8_t recvd[RECVD_HISTORY_SIZE];
    uint8_t pos, recvd_size;
    uint32_t seq;
    uint8_t ack;
    struct unit_t * next;
} unit_t;

static struct unit_t * units, * unit_now;
static uint64_t jid = 0;

//static uint32_t seq;
static uint8_t ack;

static void (* feederLog)(int priority, const char * fmt, ...);

int setLog(void * func)
{
    feederLog = (void(*)(int, const char * , ...))func;
    return 0;
}

static FILE * f = NULL;

unsigned int timeout(void * lib_data, int sock, unsigned char * data)
{
    (void)lib_data;
    (void)sock;
    (void)data;

    f = fopen("exportMessages.csv", "r");//todo: zadat confem nebo parametrem
    if (f == NULL)
    {
	feederLog(LOG_ERR, "csv: can not open input file (exportMessages.csv)\n");
	return -1;
    }

    return 0;
}


int init(void * param)
{
    (void)param;
    
//    seq = 0;
    units = unit_now = NULL;
    ack = 0;

    curl_global_init(CURL_GLOBAL_DEFAULT);

    return 0;
}

struct unit_t * unitFind(struct unit_t * unit, uint32_t id)
{
    while (unit != NULL)
    {
	if (unit->id == id)
	    return unit;
	unit = unit->next;
    }
    return NULL;
}

struct unit_t * unitCreate(uint32_t id)
{
    struct unit_t * unit;
    unit = (struct unit_t *)malloc(sizeof(struct unit_t));
    unit->id = id;
    memset(unit->recvd, 0, RECVD_HISTORY_SIZE);
    unit->seq = 0;
    unit->pos = 0;
    unit->next = NULL;
    return unit;
}


void unitUpdateRecvd(struct unit_t * unit, uint8_t flag)
{
    unit->recvd[unit->pos++] = flag;
    if (unit->pos > RECVD_HISTORY_SIZE)
	unit->pos = 0;
}

void stringToArrayn(uint8_t * data, char * str, uint16_t len)
{
    uint16_t i;
    uint8_t j;
    char c;

    for (i = 0; i < len; i++)
    {
	data[i] = 0;
	for (j = 0; j < 2; j++)
        {
            c = str[i * 2 + j];
            if (c >= 'a')	//to high case
        	c -= 0x20;
            data[i] += (c - ((c < 'A')?'0':'7')) << ((j == 0)?4:0);
        }
    }
}

void arraynToString(char * str, uint8_t * data, uint16_t len)
{
    uint16_t i;
    uint8_t j;
    char c;

    for (i = 0; i < len; i++)
    {
        for (j = 0; j < 2; j++)
        {
            c = (data[i] >> ((j == 0)?4:0)) & 0x0F;
            str[i * 2 + j] = c + ((c < 0x0A)?'0':'7');
        }
    }
    str[len * 2] = '\0';
}





#define CMD_DATETIME_REQ	0xD0
#define CMD_DATETIME_SYNC	0xD1
#define CMD_DATETIME_RESPONSE	0xD2
#define CMD_VARVAL		0xD3
#define CMD_NONE 		0xCF

typedef struct cmd_datetimereq_t
{
    uint32_t tm;
} __attribute__((packed)) cmd_datetimereq_t;

typedef struct cmd_datetimeresponse_t
{
    int32_t diff;
} __attribute__((packed)) cmd_datetimeresponse_t;

typedef struct cmd_datetimesync_t
{
} __attribute__((packed)) cmd_datetimesync_t;

typedef struct cmd_header_t
{
//    uint8_t delimiter;
//    uint64_t id;
    uint8_t cmd;
} __attribute__((packed)) cmd_header_t;

typedef struct cmd_footer_t
{
//	uint16_t crc;
} __attribute__((packed)) cmd_footer_t;


int curlDebug(CURL * curl, curl_infotype info, char * string, size_t size, void * data)
{
    (void)curl;
    (void)data;
    
    if (feederLog != NULL)
    {
	if (info == CURLINFO_TEXT)
	{
	    string[size] = 0;
	    feederLog(LOG_DEBUG, "cra: curl debug: %s", string);
	}
    }
    return 0;
}

typedef struct cb_t
{
    uint8_t cmd;
} cb_t;

size_t curlWriteCB(void *contents, size_t size, size_t nmemb, void *userp)
{
    size_t realsize;
    char * fetch = (char *)userp;
    
    realsize = size * nmemb;
    feederLog(LOG_DEBUG, "cra: Fetched %s\n", (char *)contents);
    memcpy(fetch, contents, realsize);
    fetch[realsize] = 0;

    /* return size */
    return realsize;
}


void craApi(char * method, char * header_id, char * req_body, char * fetch)
{
    CURL *ch;
    CURLcode res;
    char url[1024];
    char post[2048];
    char header[256];
    int info;
    struct curl_slist * headers = NULL;

    ch = curl_easy_init();
    if (ch)
    {
        curl_easy_setopt(ch, CURLOPT_DEBUGFUNCTION, curlDebug);
        curl_easy_setopt(ch, CURLOPT_VERBOSE, 1L);
//	strcpy(url, "http://82.99.180.180/Message");
	strcpy(url, "https://api.iot.cra.cz/cxf/IOTServices/v2/");
	strcpy(url + strlen(url), method);
	curl_easy_setopt(ch, CURLOPT_URL, url);
	curl_easy_setopt(ch, CURLOPT_POST, 1L);
	headers = curl_slist_append(headers, "Accept: application/json");
	headers = curl_slist_append(headers, "Content-Type: application/json");
	if (header_id != NULL)
	{
	    strcpy(header, "SessionId: ");
	    strcpy(header + strlen(header), header_id);
	    headers = curl_slist_append(headers, header);
	}
    	curl_easy_setopt(ch, CURLOPT_HTTPHEADER, headers);

//	curl_easy_setopt(ch, CURLOPT_HEADER, 0);
//	curl_easy_setopt(ch, CURLOPT_SSL_VERIFYPEER, 0);

/*        sprintf((char *)post,
		"{\r\n\t\"EUI\": \"%s\",\r\n\t\"data\": \"{\\\"cmd\\\":\\\"tx\\\",\\\"port\\\":1,\\\"data\\\":\\\"%s\\\",\\\"EUI\\\":\\\"%s\\\"}\"\r\n}\r\n",
		"0004A30B0022088F", "2C","0004A30B0022088F");
*/
	strcpy(post, req_body);

	curl_easy_setopt(ch, CURLOPT_CUSTOMREQUEST, "POST");
	curl_easy_setopt(ch, CURLOPT_POSTFIELDS, post);
	curl_easy_setopt(ch, CURLOPT_POSTFIELDSIZE, strlen(post));
	
//	cf.payload = (char *)calloc(1, sizeof(cf.payload));
//	cf.size = 0;

//	curl_easy_setopt(ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_0);	//!!!!!!!!!!!!

	if (fetch != NULL)
	{
    	    curl_easy_setopt(ch, CURLOPT_WRITEFUNCTION, curlWriteCB);
	    curl_easy_setopt(ch, CURLOPT_WRITEDATA, (void *)fetch);
	}
		
	//	res = curl_fetch_url(curl_g, path, &cf);
        feederLog(LOG_INFO, "cra: %s %s\n", url, post);
	
	res = curl_easy_perform(ch);
	curl_easy_cleanup(ch);
	
	curl_easy_getinfo(ch, CURLINFO_HTTP_CODE, &info);
	if (res != CURLE_OK)
	{
	    feederLog(LOG_ERR, "cra: curl POST error %d\n", res);
	    return;
	}
	if (info != 200)
	{
	    feederLog(LOG_ERR, "cra: curl page error %d\n", info);
	    return;
	}
    }
}


char * craLogin(void)
{
    char fetch[2048];
    json_object * json;
    enum json_tokener_error jerr = json_tokener_success;
    char * id = NULL;
    
    craApi((char *)"Login", NULL, (char *)"{\r\n\t\"username\": \"krivanek@lesprojekt.cz\",\r\n\t\"password\": \"LeRa072018@\"\r\n}\r\n", fetch);//todo: conf
    json = json_tokener_parse_verbose(fetch, &jerr);
    if (jerr != json_tokener_success)
    {
        feederLog(LOG_ERR, "cra: Failed to parse json err %s\n", json_tokener_error_desc(jerr));
        json_object_put(json);
        return NULL;
    }
    id = (char *)json_object_get_string(json_object_object_get(json, "sessionId"));
    feederLog(LOG_INFO, "cra: Session id %s\n", id);
    
    return id;
}

void cmdMessage(uint8_t * d, uint64_t id, uint8_t cmd)
{
    struct cmd_header_t * header;
//    struct cmd_footer_t * footer;
    (void)id;

    header = (struct cmd_header_t *)(d);
//    footer = (struct cmd_footer_t *)(d + sizeof(struct cmd_header_t) + cmd_tyope_size);//todo: footer
//    header->delimiter = '.';
//    header->id = id;
    header->cmd = cmd;
}

void cmdFooter(uint8_t * d, uint16_t len)
{
    struct cmd_footer_t * footer;
    
    footer = (struct cmd_footer_t *)(d + len);
//    footer->crc = 0;//todo: spocitat
}

void cmdDateTimeReq(struct cmd_datetimereq_t * req, uint64_t id)
{
    char body[2048];
    char message[256];
    uint8_t data[32];
    struct cmd_datetimeresponse_t * response;
    char * sid;
    time_t tm;
    int32_t diff;
    uint16_t len;
    

    
    tm = time(NULL);
    diff = tm - byteSwap32(req->tm);
    feederLog(LOG_INFO, "cra: device time %lld, system time %lld, difference %lld\n", byteSwap32(req->tm), tm, diff);
    

    cmdMessage(data, 0, CMD_DATETIME_RESPONSE);
    response = (struct cmd_datetimeresponse_t *)(data + sizeof(struct cmd_header_t));
    response->diff = byteSwap32(diff);
    cmdFooter(data, sizeof(struct cmd_header_t) + sizeof(struct cmd_datetimeresponse_t));
    len = sizeof(struct cmd_header_t) + sizeof(struct cmd_datetimeresponse_t) +  sizeof(struct cmd_footer_t);

    sid = craLogin();
    if (sid != NULL)
    {
	arraynToString(message, data, len);
/*	sprintf((char *)body,
		"{\r\n\t\"EUI\": \"%s\",\r\n\t\"data\": \"{\\\"cmd\\\":\\\"tx\\\",\\\"port\\\":1,\\\"data\\\":\\\"%s\\\",\\\"EUI\\\":\\\"%s\\\"}\"\r\n}\r\n",
		"0004A30B0022088F", message,"0004A30B0022088F");*/
	sprintf((char *)body,
		"{\r\n\t\"EUI\": \"%016llX\",\r\n\t\"data\": \"{\\\"cmd\\\":\\\"tx\\\",\\\"port\\\":1,\\\"data\\\":\\\"%s\\\",\\\"EUI\\\":\\\"%016llX\\\"}\"\r\n}\r\n",
		id, message, id);
	craApi((char *)"Message", sid, body, NULL);
    }
}

void cmdParse(uint8_t * data, uint16_t len, uint64_t id)
{
    struct cmd_header_t * header = (struct cmd_header_t *)data;

    (void)len;
    switch (header->cmd)
    {
	case CMD_DATETIME_REQ:
	    feederLog(LOG_DEBUG, "cra: Cmd datetimereq\n");
	    cmdDateTimeReq((struct cmd_datetimereq_t *)(data + sizeof(struct cmd_header_t)), id);
	break;
	case CMD_DATETIME_SYNC:
	    feederLog(LOG_DEBUG, "cra: Cmd datetimesync\n");
	break;
	default:
            feederLog(LOG_ERR, "cra: Unknowen command %d\n", header->cmd);
	break;
    }
}


#define LW_KEY_LEN                          (16)


unsigned int
process(void *lib_data, int sock, unsigned char *data,
        unsigned int length, unsigned long long int *id, time_t * tm,
        double *result_array, uint64_t * sensors, unsigned int *type)

{
    uint32_t seq;
    json_object *json, *json_data, * jdata;
    enum json_tokener_error jerr = json_tokener_success;
    char * data_json, * json_data_data_str;
    uint8_t json_data_data_bin[255];
    uint32_t json_data_ts_int;
    uint8_t json_data_bat_int;
    unsigned int res;
    static char * line = NULL;
    static size_t len = 0;
    int size;

    (void)lib_data;
    (void)sock;

    if (f == NULL)
    {
    	feederLog(LOG_ERR, "csv: file not opened\n");

	return -1;
    }


    if ((data == NULL) || (length == 0))
    {
	res = sensob_parse(NULL, 0, (uint64_t *)id, tm, result_array, sensors, type);
	*id = jid;
//	feederLog(LOG_DEBUG, "cra: item sensor %lu, value %f, type %d\n", sensors[0], result_array[0], *type);

	if (res > 0)
	    return res;
    }
    
    if (size = getline(&line, &len, f), size >= 0)
    {
    
    feederLog(LOG_DEBUG, "cra: raw data in:\n%s\n", (char *)line);
    data = line;
    length = strlen(line);

    seq = 0;
    *id = 0;

    data[length] = 0;
    data_json = strchr((char *)data, '{');//todo: proparsit i hlavicku
    if (data_json == NULL)
    {
	feederLog(LOG_ERR, "cra: Json not found\n");
	return -1;
    }
    json = json_tokener_parse_verbose(data_json, &jerr);

    if (jerr != json_tokener_success)
    {
        feederLog(LOG_ERR, "cra: Failed to parse json err %s\n", json_tokener_error_desc(jerr));
        json_object_put(json);
        return -1;
    }

//    jdata = json_object_object_get(json, "data");
    jdata = json;
//  todo: check
    feederLog(LOG_DEBUG, "cra: %s\n", json_object_get_string(jdata));
    json_data = json_tokener_parse_verbose(json_object_get_string(jdata), &jerr);

    jid = strtol(json_object_get_string(json_object_object_get(json_data, "EUI")), NULL, 16);
    feederLog(LOG_DEBUG, "cra: id %lld\n", *id);
    
    json_data_data_str = (char *)json_object_get_string(json_object_object_get(json_data, "data"));
    json_data_ts_int = json_object_get_int64(json_object_object_get(json_data, "ts")) / 1000;
    json_data_bat_int = json_object_get_int64(json_object_object_get(json_data, "bat"));
    
    if (json_data_data_str == NULL)
    {
/*        json_data_data_str = (char *)json_object_get_string(json_object_object_get(json_data, "encdata"));
        feederLog(LOG_DEBUG, "cra: encdata %s\n", json_data_data_str);
        stringToArrayn(json_data_data_bin, json_data_data_str, strlen(json_data_data_str) / 2);
    
    
    
    	aes_context aesContext;
    	uint8_t aeskey[] = {0x45, 0xf3, 0x22, 0x67, 0x98, 0x95, 0x6f, 0xcd, 0x46, 0x67, 0x0b, 0x8c, 0x13, 0xb8, 0x23, 0x15};
	aes_set_key(aeskey, LW_KEY_LEN, &aesContext);
	aes_encrypt(json_data_data_bin, json_data_data_bin, &aesContext);

*/


        feederLog(LOG_ERR, "cra: Encoded data not yet supported\n");
        json_object_put(json);
        return -1;
    }
    else
    {
        feederLog(LOG_DEBUG, "cra: data %s\n", json_data_data_str);
	stringToArrayn(json_data_data_bin, json_data_data_str, strlen(json_data_data_str) / 2);
    }
    feederLog(LOG_DEBUG, "cra: binary data: ");
    uint16_t i;
    for (i = 0; i < (strlen(json_data_data_str) / 2); i++)
	feederLog(LOG_DEBUG, "%02X ", json_data_data_bin[i]);
    feederLog(LOG_DEBUG, "\n");
    
    if (json_data_data_bin[0] == 0xFF)//this is sensob message
    {
	feederLog(LOG_DEBUG, "cra: sensob detected\n");
	
	res = sensob_parse(json_data_data_bin, strlen(json_data_data_str) / 2, (uint64_t *)id, tm, result_array, sensors, type);

	feederLog(LOG_DEBUG, "cra: item sensor %lu, value %f, type %d\n", sensors[0], result_array[0], *type);
	*id = jid;
    }
//    else if (json_data_data_bin[0] == 0xFE)//this is command todo: nebo rozlisit podle portu
    else if (json_data_data_bin[0] > 0xC0)//this is command todo: nebo rozlisit podle portu
    {
	feederLog(LOG_DEBUG, "cra: cmd detected\n");
    
//	cmdParse(json_data_data_bin, strlen(json_data_data_str) / 2, jid);
    }
    else if (json_data_data_bin[0] >= 0xA0) //this is sensob tiny message
    {
    	feederLog(LOG_DEBUG, "cra: sensob tiny detected\n");
    	
    	res = sensobtiny_parse(json_data_data_bin, strlen(json_data_data_str) / 2, json_data_ts_int, tm, result_array, sensors, type);
    	*id = jid;
    }
    else //this is sensob lite message
    {
    	feederLog(LOG_DEBUG, "cra: sensob lite detected\n");

	res = sensoblite_parse(json_data_data_bin, strlen(json_data_data_str) / 2, tm, result_array, sensors, type);
	*id = jid;
    }
    
    result_array[res] = (double)json_data_bat_int;	//get lora battery status as sensor measurement
    sensors[res] = 560030000;
    res++;
    
    return res;
    }
    return -1;
}

int reply(void * lib_data, int sock, unsigned char *data)
{
    struct unit_t * unit;
    char * buf;
    

    (void)lib_data;
    (void)sock;
    
    unit = unit_now;
    
    if (unit == NULL)
    {
	//todo: odpovededet nejakym chybou
	return 0;
    }
    
    if (unit->seq == 0)
	return 0;

    time((time_t *)data);
    /*todo
    memset(data + 4, 0, 4);
    for (i = 0; i < RECVD_HISTORY_SIZE; i++)
    {
	
	j = unit->pos - (RECVD_HISTORY_SIZE + i);
	if (j > RECVD_HISTORY_SIZE)	//rotated (less then zero)
	    j += RECVD_HISTORY_SIZE;
	data[4 + i / 8] |= unit->recvd[j] << (i % 8);
    }
*/
    buf = (char *)data;
    time_t now = time(0);
    struct tm tm = *gmtime(&now);
    sprintf(buf, "HTTP/1.0 204 NO CONTENT\r\nServer: feeder\r\nContent-Length: %d\r\nDate: ", 0);
    strftime(buf + strlen(buf), 1000, "%a, %d %b %Y %H:%M:%S %Z", &tm);
    sprintf(buf + strlen(buf), "\r\n\r\n");
    
    feederLog(LOG_DEBUG, "cra: raw data out: \n%s\n", (char *)data);

    feederLog(LOG_DEBUG, "cra: replying\n");
    
    unit->seq = 0;

    return strlen((char *)data);
}
