#include "FlexRestClient.h"

#include <ArduinoJson.h>

#include "net.h"

// Uncomment to debug
#define ESP32_RESTCLIENT_DEBUG

// change if you want to be able to capture more or less REST response headers
#define MAXHEADERS 30

#ifdef ESP32_RESTCLIENT_DEBUG
#define DEBUG_PRINT(x) Serial.print(x);
#else
#define DEBUG_PRINT(x)
#endif
//TODO: Add Timeout to send and read requests

const char *contentType = "text/plain";

// FlexRestClient restClient = FlexRestClient("localhost",8421); // initialize to dummy values at start, final values at setupRestClient

FlexRestClient::FlexRestClient(const char *_host, const int _port)
{
    port = _port;
    host = _host;
    num_headers = 0;
}

int FlexRestClient::begin(const char *ssid, const char *pass)
{
    WiFi.begin(ssid, pass);
    DEBUG_PRINT("\n[Connecting] [");
    while (WiFi.status() != WL_CONNECTED)
    {
        delay(500);
        DEBUG_PRINT("-");
    }

    DEBUG_PRINT("]\n");
    DEBUG_PRINT("[Connected to: ");
    DEBUG_PRINT(ssid);
    DEBUG_PRINT("]\n");
    DEBUG_PRINT("[IP address: ");
    DEBUG_PRINT(netLocalAddress());
    DEBUG_PRINT("]\n");
    return WiFi.status();
}

int FlexRestClient::get(const char *path, int timeout)
{
    return request("GET", path, NULL, timeout);
}

int FlexRestClient::post(const char *path, const char *body, int timeout)
{
    return request("POST", path, body, timeout);
}

void FlexRestClient::setHeader(const char *header)
{
    headers[num_headers] = header;
    num_headers++;
}

void FlexRestClient::setContentType(const char *contentTypeValue)
{
    contentType = contentTypeValue;
}

void debugPrintCharSafe(const char c) {
	if ((c>=32) && (c<=126)) {
		DEBUG_PRINT(c);
	} else {
		DEBUG_PRINT("<");
		DEBUG_PRINT((int)c);
		DEBUG_PRINT(">");
	}
}	

void debugPrintCharConsole(const char c) {
	if ((c==10) || (c==13) || ((c>=32) && (c<=126))) {
		DEBUG_PRINT(c);
	} else {
		DEBUG_PRINT("<");
		DEBUG_PRINT((int)c);
		DEBUG_PRINT(">");
	}
}

void debugPrintSafe(const char *string) {
	int index = 0;
	char c;
	while ((c=string[index++])!='\0') {
		debugPrintCharSafe(c);
	}
}

void FlexRestClient::write(const char *string)
{
	DEBUG_PRINT("[write: ");
    debugPrintSafe(string);
	DEBUG_PRINT("]\n");
#if defined(ENV_ESP32)
    client_s.print(string);
#elif defined(ENV_RP2040)
    client.print(string);
#else
#error unrecognized ENV for FRC.write
#endif
}

void FlexRestClient::writeHeaders()
{
    for (int i = 0; i < num_headers; i++)
    {
        write(headers[i]);
        write("\r\n");
    }
}

void FlexRestClient::writeBody(const char *body)
{
    if (body != NULL)
    {
        char contentLength[30];
        sprintf(contentLength, "Content-Length: %d\r\n", strlen(body));
        write(contentLength);

        write("Content-Type: ");
        write(contentType);
        write("\r\n");
    }

    write("\r\n");

    if (body != NULL)
    {
        write(body);
        write("\r\n");
        write("\r\n");
    }
}

/* *****************************************************************************
 *    CLASS:        FlexRestClient
 *    METHOD:       request
 *    INPUT:        method
 *                  path
 *                  body
 *                  timeout
 *    RESULT:       0 ... not successful
 *    DESCRIPTION:  executes REST request <method> on <path> with <body>
 */
int FlexRestClient::request(const char *method, const char *path, const char *body, int timeout)
{
    int statusCode = -1;
	DEBUG_PRINT("[will connect to host=");
	DEBUG_PRINT(host);
	DEBUG_PRINT(", port=");
	DEBUG_PRINT(port);
	DEBUG_PRINT(", timeout=");
	DEBUG_PRINT(timeout);
	DEBUG_PRINT("]\n");
  #if defined(ENV_ESP32)
    if (!client_s.connect(host, port, timeout))
  #elif defined(ENV_RP2040)
    if (!client.connect(host,port))
  #else
  #error unrecognized ENV in FRC.client.connect
  #endif
    {
        DEBUG_PRINT("[Connection failed]\n");
        return 0;
    }
    DEBUG_PRINT("[Connected to: ");
    DEBUG_PRINT(host);
    DEBUG_PRINT("]\n");
    DEBUG_PRINT("[Request: \n");

    write(method);
    write(" ");
    write(path);
    write(" ");
    write("HTTP/1.1\r\n");
    writeHeaders();
    write("Host: ");
    write(host);
    write("\r\n");
    write("Connection: close\r\n");
    writeBody(body);

    DEBUG_PRINT("][End Request]\n");
    delay(100);
    DEBUG_PRINT("[request - Reading Response]\n");
    statusCode = readResponse(timeout);
    DEBUG_PRINT("[request - End Read Response, Status = \"");
    DEBUG_PRINT(statusCode);
    DEBUG_PRINT("\"]\n");
    DEBUG_PRINT("[Stopping client]\n");
    num_headers = 0;
#if defined(ENV_ESP32)
    client_s.stop();
#elif defined(ENV_RP2040)
    client.stop();
#else
#error unrecognized ENV for FRC.request_9
#endif
    delay(50);
    DEBUG_PRINT("[Client stopped]\n");
    return statusCode;
}

int FlexRestClient::readResponse(int timeout)
{
    boolean inStatus = false;
    boolean inHeaders = true;
    boolean haveCR = false;
    char statusCode[4];
    int i = 0;
    int rcn = 0;
    int code = 0;
    boolean mayReceive = false;
	long int sus = micros();
	long int cus = micros();
    String response("");
    String respLine("");
    String payload("");
    String headers[MAXHEADERS];
    int numOfHeaders = 0;
    int respLineLength = 0;
    int payloadLength = 0;
#if defined(ENV_ESP32)
    while ((mayReceive || client_s.connected()) && (client_s.available() || ((cus-sus)<(1000*timeout))))
#elif defined(ENV_RP2040)
    while ((mayReceive || client.connected()) && (client.available() || ((cus-sus)<(1000*timeout))))
#else
#error unrecognized ENV for FRC.readResponse_1
#endif
    {
#if defined(ENV_ESP32)
        if (client_s.available()) {
            char c = client_s.read();
#elif defined(ENV_RP2040)
        if (client.available()) {
            char c = client.read();
#else
#error unrecognized ENV for FRC.readResponse_2
#endif
            mayReceive = true;
            rcn++;
			DEBUG_PRINT("[response char: \'");
			debugPrintCharSafe(c);
			DEBUG_PRINT("\']\n");
            response += c;
            if (c == ' ' && !inStatus && i < 3 ) {
                inStatus = true;
            }
            if (inStatus && i < 3 && c != ' ') {
                statusCode[i] = c;
				DEBUG_PRINT("[readResponse.statusCodeTemp=\"");
				DEBUG_PRINT(statusCode);
				DEBUG_PRINT("\"]\n");
                i++;
            }
            if (inStatus && i == 3 && c != ' ') {
                statusCode[i] = '\0';
				DEBUG_PRINT("[readResponse.statusCode=\"");
				DEBUG_PRINT(statusCode);
				DEBUG_PRINT("\"]\n");
                code = atoi(statusCode);
				inStatus = false;
            }
            cus = micros();
            DEBUG_PRINT("[readResponse.avail_endloop.cus=");
            DEBUG_PRINT(cus);
            DEBUG_PRINT(".sus=");
            DEBUG_PRINT(sus);
            DEBUG_PRINT(".elapsed_us=");
            DEBUG_PRINT(cus-sus);
            DEBUG_PRINT(".timeout_us=");
            DEBUG_PRINT(timeout*1000);
            DEBUG_PRINT(".rcn=");
            DEBUG_PRINT(rcn);
            DEBUG_PRINT(".available=");
            DEBUG_PRINT(client.available());
            DEBUG_PRINT(".connected=");
            DEBUG_PRINT(client.connected());
            DEBUG_PRINT(".code=");
            DEBUG_PRINT(code);
            DEBUG_PRINT(".i=");
            DEBUG_PRINT(i);
            DEBUG_PRINT(".inHeaders=");
            DEBUG_PRINT(inHeaders);
            DEBUG_PRINT(".numOfHeaders=");
            DEBUG_PRINT(numOfHeaders);
            DEBUG_PRINT(".respLineLength=");
            DEBUG_PRINT(respLineLength);
            DEBUG_PRINT(".payloadLength=");
            DEBUG_PRINT(payloadLength);
            DEBUG_PRINT("]\n");
            if (!inStatus && inHeaders) {
                if (c=='\r') {
                    // ignore CR
                } else if (c=='\n') {
                    if (respLineLength<1) {
                        inHeaders = false;
                        payload = "";
                        payloadLength = 0;
                    } else {
                        headers[numOfHeaders++] = respLine;
                        respLine = "";
                        respLineLength = 0;
                    }
                } else {
                    respLine += c;
                    respLineLength++;
                }
            } else if (!inStatus && !inHeaders) {
                payload += c;
                payloadLength++;
            }
        } else {
#if defined(ENV_ESP32)
            mayReceive = client_s.connected();
#elif defined(ENV_RP2040)
            mayReceive = client.connected();
#else
#error unrecognized ENV in readResponse_3
#endif
        }
    }
	DEBUG_PRINT("[readResponse.DONE.chars_read=");
    DEBUG_PRINT(rcn);
    DEBUG_PRINT(".start_us=");
	DEBUG_PRINT(sus);
	DEBUG_PRINT(".current_us=");
	DEBUG_PRINT(cus);
    DEBUG_PRINT(".elapsed_us=");
    DEBUG_PRINT(cus-sus);
	DEBUG_PRINT(".status=");
	DEBUG_PRINT(code);
    DEBUG_PRINT(".numOfHeaders=");
    DEBUG_PRINT(numOfHeaders);
    DEBUG_PRINT("]\n");
    for (int hn=0; hn<numOfHeaders; hn++) {
        DEBUG_PRINT("[HEADER[");
        DEBUG_PRINT(hn);
        DEBUG_PRINT("]=\"");
        DEBUG_PRINT(headers[hn]);
        DEBUG_PRINT("\"\n");
    }
    DEBUG_PRINT("[payload[");
    DEBUG_PRINT(payloadLength);
    DEBUG_PRINT("]=\"");
    DEBUG_PRINT(payload);
    DEBUG_PRINT("\"]\n");
    lastResponsePayload = payload;
    return code;
}

String FlexRestClient::getLastResponsePayload() {
    return lastResponsePayload;
}
