#include "arc/std/string.h"

#include "arc/std/bool.h"
#include "arc/std/errno.h"
#include <stdint.h>
#include <string.h>
#include <stdlib.h>

void ARC_String_Create(ARC_String **string, char *data, uint64_t length){
    //check if the size is too big to create and error if so
    if(length == ~(uint64_t)0){
        arc_errno = ARC_ERRNO_OVERFLOW;
        ARC_DEBUG_LOG_ERROR("ARC_String_Create(string, data, length), length was max uint64_t which is bigger than allowed");
        *string = NULL;
        return;
    }

    //create the string container and malloc the char array (this will allways be bigger than zero so there will be no issue if zero is passed in)
    *string           = (ARC_String *)malloc(sizeof(ARC_String));
    (*string)->data   = (char       *)malloc(sizeof(char) * (length + 1));
    (*string)->length = length;

    //if the string has a size, copy the string
    if(length > 0){
        strncpy((*string)->data, data, length);
    }

    //set the end of the string to \0 (to mirror how cstrings work)
    (*string)->data[length] = '\0';
}

void ARC_String_CreateWithStrlen(ARC_String **string, char *data){
    //create the string passing in the strlen of data for the length
    ARC_String_Create(string, data, strlen(data));
}

void ARC_String_CreateEmpty(ARC_String **string, uint64_t length){
    //check if the size is too big to create and error if so
    if(length == ~(uint64_t)0){
        arc_errno = ARC_ERRNO_OVERFLOW;
        ARC_DEBUG_LOG_ERROR("ARC_String_Create(string, data, length), length was max uint64_t which is bigger than allowed");
        *string = NULL;
        return;
    }

    //create the string container and initialize data with all 0s for the size
    *string           = (ARC_String *)malloc(sizeof(ARC_String));
    (*string)->data   = (char       *)calloc(sizeof(char), length + 1);
    (*string)->length = length;
}

void ARC_String_Destroy(ARC_String *string){
    //check if the string's data exists and if so free it
    if(string->data != NULL){
        free(string->data);
    }

    //free the string itself
    free(string);
}

void ARC_String_Copy(ARC_String **copy, ARC_String *original){
    if(!original){
        arc_errno = ARC_ERRNO_NULL;
        *copy = NULL;
        return;
    }

    ARC_String_Create(copy, original->data, original->length);
}

void ARC_String_CopySubstring(ARC_String **substring, ARC_String *original, uint64_t start, uint64_t length){
    if(!original){
        arc_errno = ARC_ERRNO_NULL;
        *substring = NULL;
        return;
    }

    if(length == 0){
        *substring = NULL;
        return;
    }

    if(start + length > original->length){
        arc_errno = ARC_ERRNO_DATA;
        *substring = NULL;
        return;
    }

    char data[length];
    for(uint32_t i = 0; i < length; i++){
        data[i] = 0;
    }

    strncpy(data, original->data + start, length);

    ARC_String_Create(substring, data, length);
}

void ARC_String_ReplaceWithSubstring(ARC_String **string, uint64_t start, uint64_t length){
    ARC_String *substring;
    ARC_String_CopySubstring(&substring, *string, start, length);

    //if error or substring is null free memory and return
    if(arc_errno || substring == NULL){
        if(substring != NULL){
            ARC_String_Destroy(substring);
        }
        return;
    }

    ARC_String_Destroy(*string);
    *string = substring;
}

void ARC_String_RemoveSubstring(ARC_String **newString, ARC_String *original, ARC_String *substring){
    uint64_t index = ARC_String_Find(original, substring);
    if(arc_errno){
        newString = NULL;
        return;
    }

    ARC_String_RemoveSection(newString, original, index, original->length);
}

void ARC_String_Append(ARC_String **string, ARC_String *append){
    ARC_String_AppendCString(string, append->data, append->length);
}

void ARC_String_AppendCString(ARC_String **string, const char *cstring, uint64_t length){
    char *data = (char *)malloc(sizeof(char) * ((*string)->length + length + 1));

    strncpy(data, (*string)->data, (*string)->length);
    strncpy(data + (*string)->length, cstring, length);
    data[(*string)->length + length] = '\0';

    free((*string)->data);

    (*string)->data = data;
    (*string)->length = (*string)->length + length;
}

void ARC_String_AppendCStringWithStrlen(ARC_String **string, const char *cstring){
    ARC_String_AppendCString(string, cstring, strlen(cstring));
}

ARC_Bool ARC_String_Equals(ARC_String *first, ARC_String *second){
    if(first->length != second->length){
        return ARC_False;
    }

    if(strncmp(first->data, second->data, first->length)){
        return ARC_False;
    }

    return ARC_True;
}

ARC_Bool ARC_String_EqualsCString(ARC_String *string, const char *cstring, uint64_t length){
    if(string->length != length){
        return ARC_False;
    }

    if(strncmp(string->data, cstring, string->length)){
        return ARC_False;
    }

    return ARC_True;
}

ARC_Bool ARC_String_EqualsCStringWithStrlen(ARC_String *string, const char *cstring){
    return ARC_String_EqualsCString(string, cstring, strlen(cstring));
}

ARC_Bool ARC_String_SubstringEquals(ARC_String *first, uint64_t offset, ARC_String *second){
    return ARC_String_SubstringEqualsCString(first, offset, second->data, second->length);
}

ARC_Bool ARC_String_SubstringEqualsCString(ARC_String *string, uint64_t offset, const char *cstring, uint64_t length){
    if(string->length - offset < length){
        return ARC_False;
    }

    if(strncmp(string->data + offset, cstring, length)){
        return ARC_False;
    }

    return ARC_True;
}

//TODO: fix this
ARC_Bool ARC_String_IsAlpha(ARC_String *string){
    for(uint64_t length = string->length; length; length--){
        if(string->data[length - 1] >= 'a' && string->data[length - 1] <= 'z'){
            continue;
        }

        if(string->data[length - 1] >= 'A' && string->data[length - 1] <= 'Z'){
            continue;
        }

        return ARC_True;
    }

    return ARC_False;
}

ARC_Bool ARC_String_IsNumeric(ARC_String *string){
    for(uint64_t index = 0; index < string->length; index++){
        if(string->data[index] < '0' || string->data[index] > '9'){
            return ARC_False;
        }
    }

    return ARC_True;
}

uint64_t ARC_String_ToUint64_t(ARC_String *string){
    return (uint64_t) strtoul(string->data, NULL, 10);
}

int64_t ARC_String_ToInt64_t(ARC_String *string){
    return (int64_t) strtol(string->data, NULL, 10);
}

double ARC_String_ToDouble(ARC_String *string){
    return strtod(string->data, NULL);
}


uint64_t ARC_String_Find(ARC_String *string, ARC_String *substring){
    if(!string || !substring){
        ARC_DEBUG_LOG_ERROR("ARC_String_Find(string, substring), string or substring was null");
        arc_errno = ARC_ERRNO_NULL;
        return ~(uint64_t)0;
    }

    if(substring->length > string->length){
        return ~(uint64_t)0;
    }

    uint64_t max = string->length - (substring->length - 1);
    for(uint64_t i = 0; max; i++, max--){
        if(!strncmp(string->data + i, substring->data, substring->length)){
            return i;
        }
    }

    return ~(uint64_t)0;
}

uint64_t ARC_String_FindCString(ARC_String *string, const char *cstring, uint64_t length){
    if(!string || !cstring){
        arc_errno = ARC_ERRNO_NULL;
        ARC_DEBUG_LOG_ERROR("ARC_String_FindCString(string, cstring, length), string or cstring was null");
        return ~(uint64_t)0;
    }

    if(string->length < length){
        return ~(uint64_t)0;
    }

    uint64_t max = string->length - (length - 1);
    for(uint64_t i = 0; i < max; i++){
        if(!strncmp(string->data + i, cstring, length)){
            return i + 1;
        }
    }

    return ~(uint64_t)0;
}

uint64_t ARC_String_FindCStringWithStrlen(ARC_String *string, const char *cstring){
    return ARC_String_FindCString(string, cstring, strlen(cstring));
}

uint64_t ARC_String_FindBack(ARC_String *string, ARC_String *substring){
    if(!string || !substring){
        arc_errno = ARC_ERRNO_NULL;
        ARC_DEBUG_LOG_ERROR("ARC_String_FindBack(string, substring), string or substring was null");
        return ~(uint64_t)0;
    }

    if(substring->length > string->length){
        return ~(uint64_t)0;
    }

    uint64_t max = string->length - (substring->length - 1);
    for(; max; max--){
        if(!strncmp(string->data + (max - 1), substring->data, substring->length)){
            return max;
        }
    }

    return ~(uint64_t)0;
}

uint64_t ARC_String_FindBackCString(ARC_String *string, const char *cstring, uint64_t length){
    if(!string || !cstring){
        arc_errno = ARC_ERRNO_NULL;
        ARC_DEBUG_LOG_ERROR("ARC_String_FindBack(string, substring), string or substring was null");
        return ~(uint64_t)0;
    }

    if(length > string->length){
        return ~(uint64_t)0;
    }

    uint64_t max = string->length - (length - 1);
    for(; max; max--){
        if(!strncmp(string->data + (max - 1), cstring, length)){
            return max;
        }
    }

    return ~(uint64_t)0;
}

uint64_t ARC_String_FindBackCStringWithStrlen(ARC_String *string, const char *cstring){
    return ARC_String_FindBackCString(string, cstring, strlen(cstring));
}

void ARC_String_StripEnds(ARC_String **stripped, ARC_String *original, char charToStrip){
    if(!original){
        arc_errno = ARC_ERRNO_NULL;
        *stripped = NULL;
        return;
    }

    if(!original->length){
        arc_errno = ARC_ERRNO_DATA;
        *stripped = NULL;
        return;
    }

    uint64_t length = original->length - 1;
    for(; length; length--){
        if(strncmp(original->data + (length - 1), &charToStrip, 1)){
            break;
        }
    }

    if(!length){
        arc_errno = ARC_ERRNO_DATA;
        *stripped = NULL;
        return;
    }

    uint64_t start = 0;
    for(; start <= length; start++){
        if(strncmp(original->data + start, &charToStrip, 1)){
            break;
        }
    }

    if(start == length){
        arc_errno = ARC_ERRNO_DATA;
        *stripped = NULL;
        return;
    }

    length -= start;
    ARC_String_Create(stripped, original->data + start, length);
}

void ARC_String_StripWhitespace(ARC_String **stripped, ARC_String *original){
    if(!original){
        arc_errno = ARC_ERRNO_NULL;
        *stripped = NULL;
        return;
    }

    if(!original->length){
        arc_errno = ARC_ERRNO_DATA;
        *stripped = NULL;
        return;
    }

    uint64_t length = 0;
    for(uint64_t i = 0; i < original->length; i++){
        if(original->data[i] == ' '){
            continue;
        }

        if(original->data[i] == '\n'){
            continue;
        }

        if(original->data[i] == '\t'){
            continue;
        }

        if(original->data[i] == '\r'){
            continue;
        }

        length++;
    }

    if(!length){
        arc_errno = ARC_ERRNO_DATA;
        *stripped = NULL;
        return;
    }

    length++;
    char data[length];
    for(uint32_t i = 0; i < length; i++){
        data[i] = 0;
    }

    uint64_t start = 0;
    for(uint64_t i = 0; i < length; i++){
        if(original->data[i] == ' '){
            continue;
        }

        if(original->data[i] == '\n'){
            continue;
        }

        if(original->data[i] == '\t'){
            continue;
        }

        if(original->data[i] == '\r'){
            continue;
        }

        data[start] = original->data[i];
        start++;
    }

    ARC_String_Create(stripped, data, length);
}

void ARC_String_StripEndsWhitespace(ARC_String **stripped, ARC_String *original){
    uint64_t index;
    for(uint64_t i = 0; i < original->length; i++){
        if(original->data[i] == ' '){
            continue;
        }

        if(original->data[i] == '\n'){
            continue;
        }

        if(original->data[i] == '\t'){
            continue;
        }

        if(original->data[i] == '\r'){
            continue;
        }

        index = i;
        break;
    }

    uint64_t endIndex;
    for(uint64_t i = original->length;; i--){
        if(original->data[i - 1] == ' '){
            continue;
        }

        if(original->data[i - 1] == '\n'){
            continue;
        }

        if(original->data[i - 1] == '\t'){
            continue;
        }

        if(original->data[i - 1] == '\r'){
            continue;
        }

        endIndex = i;
        break;
    }

    ARC_String_CopySubstring(stripped, original, index, endIndex - index);
}

void ARC_String_Merge(ARC_String **combined, ARC_String *first, ARC_String *second){
    char data[first->length + second->length];
    for(uint32_t i = 0; i < first->length; i++){
        data[i] = first->data[i];
    }

    for(uint32_t i = 0; i < second->length; i++){
        data[i + first->length] = second->data[i];
    }

    ARC_String_Create(combined, data, first->length + second->length);
}


void ARC_String_RemoveSection(ARC_String **newString, ARC_String *original, uint64_t removeIndex, uint64_t removeLength){
    if(removeIndex == 0 && removeIndex + removeLength >= original->length){
        ARC_String_Copy(newString, original);
        return;
    }

    if(removeIndex == 0){
        ARC_String_CopySubstring(newString, original, removeLength, original->length - removeLength);
        return;
    }

    if(removeIndex + removeLength >= original->length){
        ARC_String_CopySubstring(newString, original, 0, removeIndex);
        return;
    }

    ARC_String *first, *second;
    ARC_String_CopySubstring(&first , original, 0                         , removeIndex                                    );
    ARC_String_CopySubstring(&second, original, removeIndex + removeLength, original->length - (removeIndex + removeLength));

    ARC_String_Merge(newString, first, second);

    ARC_String_Destroy(first );
    ARC_String_Destroy(second);
}

void ARC_String_ReplaceMatching(ARC_String **string, ARC_String *pattern, ARC_String *replacement){
    ARC_String *copyReplaced;
    ARC_String_CopyReplaceMatching(&copyReplaced, *string, pattern, replacement);

    if(arc_errno != 0 || copyReplaced == NULL){
        return;
    }

    ARC_String_Destroy(*string);
    *string = copyReplaced;
}

void ARC_String_CopyReplaceMatching(ARC_String **newString, ARC_String *original, ARC_String *pattern, ARC_String *replacement){
    //TODO: probs want to check if the replacement goes over a uint64_t size
    if(original == NULL || pattern == NULL || replacement == NULL){
        arc_errno = ARC_ERRNO_NULL;
        ARC_DEBUG_LOG_ERROR("ARC_String_CopyReplaceMatching(newString, original, pattern, replacement), original, pattern, or replacement was null");
        return;
    }

    uint64_t numberOfMatches = 0;
    for(uint64_t originalIndex = 0; originalIndex < original->length; originalIndex++){
        if(ARC_String_SubstringEquals(original, originalIndex, pattern)){
            numberOfMatches++;
            originalIndex += pattern->length - 1;
        }
    }

    //no matches were found, but that isn't an error, so copy and return
    if(numberOfMatches == 0){
        ARC_String_Copy(newString, original);
        return;
    }

    (*newString) = (ARC_String *)malloc(sizeof(ARC_String));
    (*newString)->length = original->length + (replacement->length - pattern->length);
    (*newString)->data   = (char *)malloc(sizeof(char *) * original->length + 1);

    for(uint64_t originalIndex = 0, newIndex = 0; originalIndex < original->length; originalIndex++, newIndex++){
        if(ARC_String_SubstringEquals(original, originalIndex, pattern)){
            for(uint64_t replacementIndex = 0; replacementIndex < replacement->length; replacementIndex++){
                (*newString)->data[newIndex + replacementIndex] = replacement->data[replacementIndex];
            }

            originalIndex += pattern->length - 1;
            newIndex += replacement->length - 1;
            continue;
        }

        (*newString)->data[newIndex] = original->data[originalIndex];
    }

    (*newString)->data[(*newString)->length] = '\0';
}

void ARC_String_ReplaceMatchingCString(ARC_String **string, char *patternCString, uint64_t patternLength, char *replacementCString, uint64_t replacementLength){
    //TODO: probs want to check if the replacement goes over a uint64_t size
    if(*string == NULL || patternCString == NULL || replacementCString == NULL){
        arc_errno = ARC_ERRNO_NULL;
        ARC_DEBUG_LOG_ERROR("ARC_String_ReplaceMatchingCString(string, patternCString, patternLength, replacementCString, replacementLength), *string, patternCString, or replacementCString was null");
        return;
    }

    ARC_String *original = *string;

    uint64_t numberOfMatches = 0;
    for(uint64_t originalIndex = 0; originalIndex < original->length; originalIndex++){
        if(ARC_String_SubstringEqualsCString(original, originalIndex, patternCString, patternLength)){
            numberOfMatches++;
            originalIndex += patternLength - 1;
        }
    }

    //no matches were found, but that isn't an error, so nothing to do, return
    if(numberOfMatches == 0){
        return;
    }

    (*string) = (ARC_String *)malloc(sizeof(ARC_String));
    (*string)->length = original->length + (replacementLength - patternLength);
    (*string)->data   = (char *)malloc(sizeof(char *) * original->length + 1);

    for(uint64_t originalIndex = 0, newIndex = 0; originalIndex < original->length; originalIndex++, newIndex++){
        if(ARC_String_SubstringEqualsCString(original, originalIndex, patternCString, patternLength)){
            for(uint64_t replacementIndex = 0; replacementIndex < replacementLength; replacementIndex++){
                (*string)->data[newIndex + replacementIndex] = replacementCString[replacementIndex];
            }

            originalIndex += patternLength - 1;
            newIndex += replacementLength - 1;
            continue;
        }

        (*string)->data[newIndex] = original->data[originalIndex];
    }

    (*string)->data[(*string)->length] = '\0';

    //cleanup
    ARC_String_Destroy(original);
}

void ARC_String_ReplaceMatchingCStringWithStrlen(ARC_String **string, char *patternCString, char *replacementCString){
    ARC_String_ReplaceMatchingCString(string, patternCString, strlen(patternCString), replacementCString, strlen(replacementCString));
}

