#include "Platform.h"
#include "Rtsp.h"

// Check if String s begins with the given prefix
static bool startsWith(const char* s, const char* prefix) {
    return strncmp(s, prefix, strlen(prefix)) == 0;
}

// Gets the length of the message
static int getMessageLength(PRTSP_MESSAGE msg) {
    POPTION_ITEM current;

    // Initialize to 1 for null terminator
    size_t count = 1;

    // Add the length of the protocol
    count += strlen(msg->protocol);

    // Add length of request-specific strings
    if (msg->type == TYPE_REQUEST) {
        count += strlen(msg->message.request.command);
        count += strlen(msg->message.request.target);

        // two spaces and \r\n
        count += MESSAGE_END_LENGTH;
    }
    // Add length of response-specific strings
    else {
        char statusCodeStr[16];
        snprintf(statusCodeStr, sizeof(statusCodeStr), "%d", msg->message.response.statusCode);
        count += strlen(statusCodeStr);
        count += strlen(msg->message.response.statusString);
        // two spaces and \r\n
        count += MESSAGE_END_LENGTH;
    }
    // Count the size of the options
    current = msg->options;

    while (current != NULL) {
        count += strlen(current->option);
        count += strlen(current->content);
        // :[space] and \r\n
        count += MESSAGE_END_LENGTH;
        current = current->next;
    }
    // /r/n ending
    count += CRLF_LENGTH;

    count += msg->payloadLength;

    return (int)count;
}

// Given an RTSP message string rtspMessage, parse it into an RTSP_MESSAGE struct msg
int parseRtspMessage(PRTSP_MESSAGE msg, char* rtspMessage, int length) {
    char* token;
    char* protocol;
    char* endCheck;
    char* target;
    char* statusStr;
    char* command;
    char* sequence;
    char flag;
    bool messageEnded = false;

    char* strtokCtx = NULL;
    char* payload = NULL;
    char* opt = NULL;
    int statusCode = 0;
    int sequenceNum;
    int exitCode;
    POPTION_ITEM options = NULL;
    POPTION_ITEM newOpt;

    // Delimeter sets for strtok()
    char* delim = " \r\n";
    char* end = "\r\n";
    char* optDelim = " :\r\n";
    char typeFlag = TOKEN_OPTION;

    // Put the raw message into a string we can use
    char* messageBuffer = malloc(length + 1);
    if (messageBuffer == NULL) {
        exitCode = RTSP_ERROR_NO_MEMORY;
        goto ExitFailure;
    }
    memcpy(messageBuffer, rtspMessage, length);

    // The payload logic depends on a null-terminator at the end
    messageBuffer[length] = 0;

    // Get the first token of the message
    token = strtok_r(messageBuffer, delim, &strtokCtx);
    if (token == NULL) {
        exitCode = RTSP_ERROR_MALFORMED;
        goto ExitFailure;
    }

    // The message is a response
    if (startsWith(token, "RTSP")) {
        flag = TYPE_RESPONSE;
        // The current token is the protocol
        protocol = token;

        // Get the status code
        token = strtok_r(NULL, delim, &strtokCtx);
        if (token == NULL) {
            exitCode = RTSP_ERROR_MALFORMED;
            goto ExitFailure;
        }
        statusCode = atoi(token);

        // Get the status string
        statusStr = strtok_r(NULL, end, &strtokCtx);
        if (statusStr == NULL) {
            exitCode = RTSP_ERROR_MALFORMED;
            goto ExitFailure;
        }

        // Request fields - we don't care about them here
        target = NULL;
        command = NULL;
    }

    // The message is a request
    else {
        flag = TYPE_REQUEST;
        command = token;
        target = strtok_r(NULL, delim, &strtokCtx);
        if (target == NULL) {
            exitCode = RTSP_ERROR_MALFORMED;
            goto ExitFailure;
        }
        protocol = strtok_r(NULL, delim, &strtokCtx);
        if (protocol == NULL) {
            exitCode = RTSP_ERROR_MALFORMED;
            goto ExitFailure;
        }
        // Response field - we don't care about it here
        statusStr = NULL;
    }
    if (strcmp(protocol, "RTSP/1.0")) {
        exitCode = RTSP_ERROR_MALFORMED;
        goto ExitFailure;
    }
    // Parse remaining options
    while (token != NULL)
    {
        token = strtok_r(NULL, typeFlag == TOKEN_OPTION ? optDelim : end, &strtokCtx);
        if (token != NULL) {
            if (typeFlag == TOKEN_OPTION) {
                opt = token;
            }
            // The token is content
            else {
                // Create a new node containing the option and content
                newOpt = (POPTION_ITEM)malloc(sizeof(OPTION_ITEM));
                if (newOpt == NULL) {
                    exitCode = RTSP_ERROR_NO_MEMORY;
                    goto ExitFailure;
                }
                newOpt->flags = 0;
                newOpt->option = opt;
                newOpt->content = token + 1; // Skip the protocol defined blank space
                newOpt->next = NULL;
                insertOption(&options, newOpt);

                // Check if we're at the end of the message portion marked by \r\n\r\n
                // endCheck points to the remainder of messageBuffer after the token
                endCheck = &token[0] + strlen(token) + 1;

                // See if we've hit the end of the message. The first \r is missing because it's been tokenized
                if (startsWith(endCheck, "\n") && endCheck[1] == '\0') {
                    // RTSP over ENet doesn't always have the second CRLF for some reason
                    messageEnded = true;

                    break;
                }
                else if (startsWith(endCheck, "\n\r") && endCheck[2] == '\0') {
                    // Previous `if` statement already handle situation when two bytes are missing.
                    // This is the workaround for the same problem, but for only one byte missing.
                    // Sometimes on android emulators last byte or two bytes are missing.
                    // This is link to this bug in Goggle Issue Tracker:
                    // https://issuetracker.google.com/issues/150758736?pli=1
                    messageEnded = true;

                    break;
                }
                else if (startsWith(endCheck, "\n\r\n")) {
                    // We've encountered the end of the message - mark it thus
                    messageEnded = true;

                    // The payload is the remainder of messageBuffer. If none, then payload = null
                    if (endCheck[3] != '\0')
                        payload = &endCheck[3];

                    break;
                }
            }
        }
        typeFlag ^= 1; // flip the flag
    }
    // If we never encountered the double CRLF, then the message is malformed!
    if (!messageEnded) {
        exitCode = RTSP_ERROR_MALFORMED;
        goto ExitFailure;
    }

    // Get sequence number as an integer
    sequence = getOptionContent(options, "CSeq");
    if (sequence != NULL) {
        sequenceNum = atoi(sequence);
    }
    else {
        sequenceNum = SEQ_INVALID;
    }
    // Package the new parsed message into the struct
    if (flag == TYPE_REQUEST) {
        createRtspRequest(msg, messageBuffer, FLAG_ALLOCATED_MESSAGE_BUFFER | FLAG_ALLOCATED_OPTION_ITEMS, command, target,
            protocol, sequenceNum, options, payload, payload ? length - (int)(payload - messageBuffer) : 0);
    }
    else {
        createRtspResponse(msg, messageBuffer, FLAG_ALLOCATED_MESSAGE_BUFFER | FLAG_ALLOCATED_OPTION_ITEMS, protocol, statusCode,
            statusStr, sequenceNum, options, payload, payload ? length - (int)(payload - messageBuffer) : 0);
    }
    return RTSP_ERROR_SUCCESS;

ExitFailure:
    if (options) {
        freeOptionList(options);
    }
    if (messageBuffer) {
        free(messageBuffer);
    }
    return exitCode;
}

// Create new RTSP message struct with response data
void createRtspResponse(PRTSP_MESSAGE msg, char* message, int flags, char* protocol,
    int statusCode, char* statusString, int sequenceNumber, POPTION_ITEM optionsHead, char* payload, int payloadLength) {
    msg->type = TYPE_RESPONSE;
    msg->flags = flags;
    msg->messageBuffer = message;
    msg->protocol = protocol;
    msg->options = optionsHead;
    msg->payload = payload;
    msg->payloadLength = payloadLength;
    msg->sequenceNumber = sequenceNumber;
    msg->message.response.statusString = statusString;
    msg->message.response.statusCode = statusCode;
}

// Create new RTSP message struct with request data
void createRtspRequest(PRTSP_MESSAGE msg, char* message, int flags,
    char* command, char* target, char* protocol, int sequenceNumber, POPTION_ITEM optionsHead, char* payload, int payloadLength) {
    msg->type = TYPE_REQUEST;
    msg->flags = flags;
    msg->protocol = protocol;
    msg->messageBuffer = message;
    msg->options = optionsHead;
    msg->payload = payload;
    msg->payloadLength = payloadLength;
    msg->sequenceNumber = sequenceNumber;
    msg->message.request.command = command;
    msg->message.request.target = target;
}

// Retrieves option content from the linked list given the option title
char* getOptionContent(POPTION_ITEM optionsHead, char* option) {
    POPTION_ITEM current = optionsHead;
    while (current != NULL) {
        // Check if current node is what we're looking for
        if (!strcmp(current->option, option)) {
            return current->content;
        }
        current = current->next;
    }
    // Not found
    return NULL;
}

// Adds new option opt to the struct's option list
void insertOption(POPTION_ITEM* optionsHead, POPTION_ITEM opt) {
    POPTION_ITEM current = *optionsHead;
    opt->next = NULL;

    // Empty options list
    if (*optionsHead == NULL) {
        *optionsHead = opt;
        return;
    }

    // Traverse the list and insert the new option at the end
    while (current != NULL) {
        // Check for duplicate option; if so, replace the option currently there
        if (!strcmp(current->option, opt->option)) {
            current->content = opt->content;
            return;
        }
        if (current->next == NULL) {
            current->next = opt;
            return;
        }
        current = current->next;
    }
}

// Free every node in the message's option list
void freeOptionList(POPTION_ITEM optionsHead) {
    POPTION_ITEM current = optionsHead;
    POPTION_ITEM temp;
    while (current != NULL) {
        temp = current;
        current = current->next;
        if (temp->flags & FLAG_ALLOCATED_OPTION_FIELDS) {
            free(temp->option);
            free(temp->content);
        }
        free(temp);
    }
}

bool appendString(char* dest, int* destOffset, int* destRemainingLength, char* source) {
    int ret = snprintf(&dest[*destOffset], *destRemainingLength, "%s", source);
    if (ret < 0 || ret >= *destRemainingLength) {
        LC_ASSERT(false);
        return false;
    }

    *destOffset += ret;
    *destRemainingLength -= ret;
    return true;
}

// Serialize the message struct into a string containing the RTSP message
char* serializeRtspMessage(PRTSP_MESSAGE msg, int* serializedLength) {
    int size = getMessageLength(msg);
    int offset = 0;
    char* serializedMessage;
    POPTION_ITEM current = msg->options;
    char statusCodeStr[16];

    serializedMessage = malloc(size);
    if (serializedMessage == NULL) {
        return NULL;
    }

    if (msg->type == TYPE_REQUEST) {
        // command [space]
        if (!appendString(serializedMessage, &offset, &size, msg->message.request.command) || !appendString(serializedMessage, &offset, &size, " ")) {
            goto fail;
        }
        // target [space]
        if (!appendString(serializedMessage, &offset, &size, msg->message.request.target) || !appendString(serializedMessage, &offset, &size, " ")) {
            goto fail;
        }
        // protocol \r\n
        if (!appendString(serializedMessage, &offset, &size, msg->protocol) || !appendString(serializedMessage, &offset, &size, "\r\n")) {
            goto fail;
        }
    }
    else {
        // protocol [space]
        if (!appendString(serializedMessage, &offset, &size, msg->protocol) || !appendString(serializedMessage, &offset, &size, " ")) {
            goto fail;
        }
        // status code [space]
        snprintf(statusCodeStr, sizeof(statusCodeStr), "%d", msg->message.response.statusCode);
        if (!appendString(serializedMessage, &offset, &size, statusCodeStr) || !appendString(serializedMessage, &offset, &size, " ")) {
            goto fail;
        }
        // status str\r\n
        if (!appendString(serializedMessage, &offset, &size, msg->message.response.statusString) || !appendString(serializedMessage, &offset, &size, "\r\n")) {
            goto fail;
        }
    }
    // option content\r\n
    while (current != NULL) {
        if (!appendString(serializedMessage, &offset, &size, current->option) || !appendString(serializedMessage, &offset, &size, ": ")) {
            goto fail;
        }
        if (!appendString(serializedMessage, &offset, &size, current->content) || !appendString(serializedMessage, &offset, &size, "\r\n")) {
            goto fail;
        }
        current = current->next;
    }
    // Final \r\n
    if (!appendString(serializedMessage, &offset, &size, "\r\n")) {
        goto fail;
    }

    // payload
    if (msg->payload != NULL) {
        if (msg->payloadLength > size) {
            goto fail;
        }

        // Add the payload after
        memcpy(&serializedMessage[offset], msg->payload, msg->payloadLength);

        *serializedLength = offset + msg->payloadLength;
    }
    else {
        *serializedLength = offset;
    }

    return serializedMessage;

fail:
    free(serializedMessage);
    return NULL;
}

// Free everything in a msg struct
void freeMessage(PRTSP_MESSAGE msg) {
    // If we've allocated the message buffer
    if (msg->flags & FLAG_ALLOCATED_MESSAGE_BUFFER) {
        free(msg->messageBuffer);
    }

    // If we've allocated any option items
    if (msg->flags & FLAG_ALLOCATED_OPTION_ITEMS) {
        freeOptionList(msg->options);
    }

    // If we've allocated the payload
    if (msg->flags & FLAG_ALLOCATED_PAYLOAD) {
        free(msg->payload);
    }
}
