#include "fallback.h"
#include "fileutils.h"
#include "../depends/cJSON.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

bool fallback_load_from_file(fallback *self, const char *filepath) {
    memset(self, 0, sizeof(*self));

    char *file_data;
    long file_size;
    if(file_get_content(filepath, &file_data, &file_size) != 0) {
        fprintf(stderr, "Error: fallback_load_from_file: failed to read %s\n", filepath);
        return false;
    }

    cJSON *json_root = cJSON_ParseWithLength(file_data, file_size);
    if(!json_root) {
        fprintf(stderr, "Error: fallback_load_from_file: failed to parse file %s as json\n", filepath);
        goto error;
    }
    free(file_data);
    file_data = NULL;

    if(!cJSON_IsArray(json_root)) {
        fprintf(stderr, "File %s contains malformed json. Expected json root element to be an array\n", filepath);
        goto error;
    }

    buffer_init(&self->fallbacks);

    const cJSON *fallback_item_json = NULL;
    cJSON_ArrayForEach(fallback_item_json, json_root) {
        if(!cJSON_IsObject(fallback_item_json))
            continue;

        const cJSON *source_json = cJSON_GetObjectItemCaseSensitive(fallback_item_json, "source");
        if(!cJSON_IsString(source_json))
            continue;

        const cJSON *fallbacks_json = cJSON_GetObjectItemCaseSensitive(fallback_item_json, "fallbacks");
        if(!cJSON_IsArray(fallbacks_json))
            continue;
        
        char *source_str = strdup(source_json->valuestring);
        if(!source_str) {
            fprintf(stderr, "Error: failed to clone string: %s\n", source_str);
            abort();
        }

        fallback_item item;
        item.source = source_str;
        item.source_to_use = NULL;
        buffer_init(&item.fallbacks);

        const cJSON *fallback_str_json = NULL;
        cJSON_ArrayForEach(fallback_str_json, fallbacks_json) {
            if(!cJSON_IsString(fallback_str_json))
                continue;

            char *fallback = strdup(fallback_str_json->valuestring);
            if(!fallback) {
                fprintf(stderr, "Error: failed to clone string: %s\n", fallback);
                abort();
            }

            buffer_append(&item.fallbacks, &fallback, sizeof(fallback));
        }

        buffer_append(&self->fallbacks, &item, sizeof(item));
    }

    cJSON_Delete(json_root);
    free(file_data);
    return true;

    error:
    cJSON_Delete(json_root);
    free(file_data);
    return false;
}

void fallback_deinit(fallback *self) {
    fallback_item *items_it = buffer_begin(&self->fallbacks);
    fallback_item *items_end = buffer_end(&self->fallbacks);
    for(; items_it != items_end; ++items_it) {
        if(items_it->source) {
            free(items_it->source);
            items_it->source = NULL;
        }

        char **fallbacks_it = buffer_begin(&items_it->fallbacks);
        char **fallbacks_end = buffer_end(&items_it->fallbacks);
        for(; fallbacks_it != fallbacks_end; ++fallbacks_it) {
            if(*fallbacks_it) {
                free(*fallbacks_it);
                *fallbacks_it = NULL;
            }
        }
        buffer_deinit(&items_it->fallbacks);
    }
    buffer_deinit(&self->fallbacks);
}

static void url_extract_domain(const char **url, int *len) {
    if(*len >= 7 && strncmp("http://", *url, 7) == 0) {
        *url += 7;
        *len -= 7;
    } else if(*len >= 8 && strncmp("https://", *url, 8) == 0) {
        *url += 8;
        *len -= 8;
    }

    const char *end = strchr(*url, '/');
    if(end)
        *len = end - *url;
}

fallback_item* fallback_get_from_url(fallback *self, const char *url) {
    int url_len = strlen(url);
    url_extract_domain(&url, &url_len);

    fallback_item *items_it = buffer_begin(&self->fallbacks);
    fallback_item *items_end = buffer_end(&self->fallbacks);
    for(; items_it != items_end; ++items_it) {
        int source_len = strlen(items_it->source);
        const char *source = items_it->source;
        url_extract_domain(&source, &source_len);

        if(url_len == source_len && memcmp(url, source, url_len) == 0)
            return items_it;

        if(!items_it->source_to_use)
            continue;

        int source_to_use_len = strlen(items_it->source_to_use);
        const char *source_to_use = items_it->source_to_use;
        url_extract_domain(&source_to_use, &source_to_use_len);

        if(url_len == source_to_use_len && memcmp(url, source_to_use, url_len) == 0)
            return items_it;
    }

    return NULL;
}

void fallback_clear_sources_to_use(fallback *self) {
    fallback_item *items_it = buffer_begin(&self->fallbacks);
    fallback_item *items_end = buffer_end(&self->fallbacks);
    for(; items_it != items_end; ++items_it) {
        items_it->source_to_use = NULL;
    }
}

static const char* get_url_part_after_domain(const char *url) {
    int len = strlen(url);
    if(len >= 7 && strncmp(url, "http://", 7) == 0) {
        url += 7;
        len -= 7;
    } else if(len >= 8 && strncmp(url, "https://", 8) == 0) {
        url += 8;
        len -= 8;
    }

    const char *after_domain = strchr(url, '/');
    if(after_domain)
        return after_domain;
    else
        return url + len;
}

void fallback_replace_url_with_fallback_url(const char *url, const char *fallback_url, char *new_url, size_t new_url_len) {
    const char *url_part_after_domain = get_url_part_after_domain(url);
    snprintf(new_url, new_url_len, "%s%s", fallback_url, url_part_after_domain);
}

void fallback_replace_active_fallback_url_with_source_url(fallback *self, const char *url, char *new_url, size_t new_url_len) {
    fallback_item *fall_item = fallback_get_from_url(self, url);
    if(!fall_item || !fall_item->source_to_use) {
        snprintf(new_url, new_url_len, "%s", url);
        return;
    }
    fallback_replace_url_with_fallback_url(url, fall_item->source, new_url, new_url_len);
}

void fallback_item_replace_url_with_fallback_url(fallback_item *self, const char *url, char *new_url, size_t new_url_len) {
    if(!self->source_to_use) {
        snprintf(new_url, new_url_len, "%s", url);
        return;
    }
    fallback_replace_url_with_fallback_url(url, self->source_to_use, new_url, new_url_len);
}