//
// Created by miti on 2019-12-28.
//

#include "MainLogic.h"
#include "crypto.h"
#include "stdio.h"
#include <errno.h>
#include <string.h>

// For socket programming
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define DECRYPTOR_PORT_DATA 3825
#define DECRYPTOR_PORT_HEADERS 3826

// Sets up a socket connected to the port passed as input - returns the socket FD on success and -1 on error.
// Also prints the errno on error.
int MainLogic::set_up_socket_connect(int port)
{
    int sock = 0;
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        printf("\n Error in socket call - errno is %d \n", errno); fflush(stdout);
        return -1;
    }

    struct sockaddr_in serv_addr;
    memset(&serv_addr, '0', sizeof(serv_addr));

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(port);

    // Convert IPv4 and IPv6 addresses from text to binary form
    if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr)<=0)
    {
        printf("\nError in inet_pton - errno is %d\n", errno); fflush(stdout);
        return -1;
    }

    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
    {
        printf("\nError in connect - errno is %d \n", errno); fflush(stdout);
        return -1;
    }

    return sock;
}

int MainLogic::conduct_la()
{
    uint8_t key[16];
    uint32_t ret_status;
    int data_fd;

    data_fd = set_up_socket_connect(DECRYPTOR_PORT_DATA);
    printf("About to setup socket.\n");
    if(data_fd <= 0)
    {
        printf("Could not set up a socket with port %d\n", DECRYPTOR_PORT_DATA);
        fflush(stdout);
        return 0x1;
    }

    // Conduct LA.
    ret_status = laInitiator.conduct_la(data_fd);
    if (ret_status != 0) {
        printf("LA initiator returned an error: %d\n", ret_status);
        fflush(stdout);
        return ret_status;
    }
    printf("\nSuccessful LA with port %d.\n", data_fd);
    fflush(stdout);

    printf("Setting up fds for post LA messaging.\n");
    fflush(stdout);
    // Use the same channel for sending client data as the one used above for LA messages.
    postLaMessaging.set_data_fd(data_fd);

    // Use the symmetric key from LA to send messages for the rest of the deployment stage.
    printf("Retrieving key from LA inititator.\n");
    fflush(stdout);
    laInitiator.get_la_symmetric_key(key);
    printf("Setting key for post LA messaging for both data and headers channels.\n");
    fflush(stdout);
    postLaMessaging.set_la_symmetric_key(key);

    return 0;
}

int MainLogic::get_initial_headers()
{
    int headers_fd, ret_status;

    headers_fd = set_up_socket_connect(DECRYPTOR_PORT_HEADERS);
    if(headers_fd <= 0)
    {
        // TODO: Proper error handling.
        printf("Could not set up a socket with port %d\n", DECRYPTOR_PORT_HEADERS);
        fflush(stdout);
        return 0x1;
    }
    printf("Set up a socket to receive headers from the decryptor.\n"); fflush(stdout);

    // Use a different channel for receiving headers asynchronously.
    postLaMessaging.set_headers_fd(headers_fd);

    ret_status = postLaMessaging.receive_header_from_decryptor(last_header_value);
    if(ret_status != 0)
    {
        printf("Could not receive the header from the decryptor. Had error %d\n", ret_status);
        fflush(stdout);
    }
    return 0;
}

int MainLogic::decode_base64_fields_list(std::vector <std::string> &base64_fields_list, std::vector <std::vector<unsigned char>> &binary_fields_list)
{
    int binary_field_size, base64_field_size, counter;
    const char* base64_field_ptr;
    unsigned char* binary_field_ptr = NULL;

    printf("In the base64 decode list function.\n"); fflush(stdout);
    for (auto &base64_field : base64_fields_list)
    {
        base64_field_size = base64_field.size();
        base64_field_ptr = base64_field.c_str();
        // upper limit - the binary data will always be smaller than this (base64 length ~= 4/3 * binary length)
        binary_field_ptr = (unsigned char*) realloc(binary_field_ptr, base64_field_size);
        printf("About to call the decoder on the following field:%s of the following size%d\n", base64_field_ptr, base64_field_size); fflush(stdout);
        binary_field_size = base64_decoding_wrapper(base64_field_ptr, base64_field_size, binary_field_ptr);
        printf("Called the decoder and got the following size: %d.\n", binary_field_size );
        if(binary_field_size <= 0)
        {
            printf("Had an error in decoding.\n"); fflush(stdout);
            free(binary_field_ptr);
            return 0x1;
        }
        std::vector<unsigned char> binary_field(binary_field_ptr, binary_field_ptr + binary_field_size);
        binary_fields_list.push_back(binary_field);
    }
    free(binary_field_ptr);
    for (auto binaryField : binary_fields_list)
    {
        for(auto myByte : binaryField)
            printf("%02x ", myByte);
        printf("\n");
    }

    return 0;
}

void MainLogic::deployment_stage() {
    int ret_status = conduct_la();
    if(ret_status != 0) {
        printf("Error in conducting LA: %d.\n", ret_status);
        fflush(stdout);
        return;
    }

    ret_status = get_initial_headers();
    if(ret_status !=0 )
    {
        printf("Error in getting initial headers: %d.\n", ret_status);
        fflush(stdout);
        return;
    }

    // time_file_fd=open("target_time.txt", O_APPEND | O_WRONLY);
}

Php::Value MainLogic::php_decrypt_wrapper(Php::Parameters &params  ) {
    std::vector <std::string> base64_fields_list;
    std::vector <std::vector<unsigned char>> binary_fields_list, plaintext_fields_list;
    uint32_t ret_status, no_of_fields, counter, counter2;
    Php::Object ret_object;
    ret_object["success"]="false";

    no_of_fields = params[0].size();
    printf("Received client's ciphertext fields, performing base 64 decoding.\n"); fflush(stdout);
    base64_fields_list = params[0]; //Php::array_values(Php::array_values(params)[0]);
    printf("In the base64 decode list function.\n"); fflush(stdout);
    /*
        printf("Field: \n");
        for(counter2=0; counter2<base64_fields_list[counter].length(); counter2++)
            printf("%02x ", base64_fields_list[counter][counter2]);
    */
    ret_status = decode_base64_fields_list(base64_fields_list, binary_fields_list);
    if (ret_status != 0) {
        printf("Could not perform base64 decoding correctly.");
        fflush(stdout);
        ret_object["error"] = "Could not perform base64 decoding correctly.";
        return ret_object;
    }

    printf("Sending data to the decryptor.\n"); fflush(stdout);
    ret_status = postLaMessaging.send_data_to_decryptor(binary_fields_list);
    if(ret_status != 0)
    {
        printf("Cannot send messages to the decryptor.\n"); fflush(stdout);
        ret_object["error"]="Cannot send messages to the decryptor.\n";
        return ret_object;
    }

    printf("Receiving data from the decryptor.\n"); fflush(stdout);
    ret_status = postLaMessaging.receive_data_from_decryptor(plaintext_fields_list);
    if(ret_status != 0)
    {
        printf("Cannot receive messages from the decryptor.\n"); fflush(stdout);
        ret_object["error"]="Cannot receive messages from the decryptor.\n";
        return ret_object;
    }
    ret_object["success"]="true";
    no_of_fields = plaintext_fields_list.size();
    std::vector<unsigned char> temp_vector;
    Php::Array ret_php_array;
    for(counter=0; counter<no_of_fields; counter++)
    {
        temp_vector = plaintext_fields_list[counter];
        ret_php_array[counter] = std::string((char*) &temp_vector.front(), (ssize_t) temp_vector.size());
    }
    ret_object["fields"]=ret_php_array;

    for (auto binaryField : plaintext_fields_list)
    {

        for(auto myByte : binaryField)
            printf("%c", myByte);
        printf("\n");
    }
    printf("Returning back to good old php.\n"); fflush(stdout);

    return ret_object;
}

Php::Value MainLogic::get_mitigator_header() {
    std::string header_value;
    uint32_t ret_status;
    if(header_refresh_counter < 100)
    {
        header_value = last_header_value;
        header_refresh_counter++;
    }
    else
    {
        ret_status = postLaMessaging.receive_header_from_decryptor(header_value);
        if(ret_status != 0)
        {
            printf(" Cannot obtain a header from the decryptor.\n"); fflush(stdout);
            header_value = "!! Cannot obtain a header from the decryptor.";
        }
        header_refresh_counter = 0;
    }
    printf("Returning this header value:%s\n", header_value.c_str()); fflush(stdout);
    return header_value;
}
