#include "arc/graphics/config.h"

#include <SDL_image.h>
#include <stdio.h>
#include "renderer.h"
#include "sprite.h"
#include "spritesheet.h"
#include "arc/std/array.h"
#include "arc/std/string.h"
#include "arc/std/errno.h"
#include "arc/graphics/sprite.h"
#include "arc/graphics/spritesheet.h"
#include "arc/math/config.h"
#include "arc/math/point.h"
#include "arc/math/rectangle.h"

// #define ARC_DEFAULT_CONFIG
#include "arc/std/defaults/config.h"

SDL_Renderer *global_renderer;

uint8_t ARC_SDL_Texture_Read(ARC_Config *config, ARC_String *string, void **value);
uint8_t ARC_Spritesheet_Read(ARC_Config *config, ARC_String *string, void **value);
uint8_t ARC_Sprite_Read     (ARC_Config *config, ARC_String *string, void **value);

void ARC_SDL_Texture_Delete(ARC_Config *config, ARC_String *string, void *value);
void ARC_Spritesheet_Delete(ARC_Config *config, ARC_String *string, void *value);
void ARC_Sprite_Delete     (ARC_Config *config, ARC_String *string, void *value);

void ARC_GraphicsConfig_Init(ARC_Config *config, ARC_Renderer *renderer){
    global_renderer = (SDL_Renderer *)renderer;
    ARC_Config_AddKeyCString(config, (char *)"SDL_Texture"    , 11, ARC_SDL_Texture_Read, ARC_SDL_Texture_Delete);
    ARC_Config_AddKeyCString(config, (char *)"ARC_Spritesheet", 15, ARC_Spritesheet_Read, ARC_Spritesheet_Delete);
    ARC_Config_AddKeyCString(config, (char *)"ARC_Sprite"     , 10, ARC_Sprite_Read     , ARC_Sprite_Delete     );
}

uint64_t ARC_GraphicsConfig_GetIndexAndErrorCheck(ARC_String *string, char *search, uint64_t searchLength){
    uint64_t separator = ARC_String_FindCString(string, ",", 1);

    if(separator == ~(uint64_t)0){
        arc_errno = ARC_ERRNO_DATA;
    }

    return separator;
}

int32_t ARC_SDL_Texture_Load(const char *path, SDL_Texture **texture){
    IMG_Init(IMG_INIT_PNG);
    SDL_Surface *surface = IMG_Load(path);
    if(!surface){
        printf("Error: reading png '%s'\nSDL_Image Error: %s", path, IMG_GetError());
        return 1; // GE_SDL_ERRNO_
    }

    SDL_BlendMode tempMode;
    SDL_GetSurfaceBlendMode(surface, &tempMode);
    *texture = SDL_CreateTextureFromSurface(global_renderer, surface);
    SDL_GetTextureBlendMode(*texture, &tempMode);

    SDL_FreeSurface(surface);
    IMG_Quit();

    return 0;
}

uint8_t ARC_SDL_Texture_Read(ARC_Config* config, ARC_String *string, void **value){
    ARC_Config_Get(config, string, value);
    if(*value){
        return 1;
    }

    ARC_String *tempStr, *textureStr;
    ARC_String_StripEndsWhitespace(&tempStr, string);

    ARC_String_CopySubstring(&textureStr, tempStr, 1, tempStr->length - 2);
    ARC_String_Destroy(tempStr);

    ARC_SDL_Texture_Load(textureStr->data, (SDL_Texture **)value);

    ARC_String_Destroy(textureStr);
    return 0;
}

void ARC_Spritesheet_ReadTexture(ARC_Config *config, ARC_String *string, uint32_t *size, void **value){
    SDL_Texture *texture;

    ARC_String *tempStr, *textureStr;
    ARC_String_StripEndsWhitespace(&tempStr, string);

    //check for reference
    ARC_Config_Get(config, tempStr, (void **)&texture);
    if(!texture && (tempStr->data[0] != '"' || tempStr->data[string->length - 1] != '"')){
        arc_errno = ARC_ERRNO_DATA;
    }

    ARC_String_CopySubstring(&textureStr, tempStr, 1, tempStr->length - 2);
    ARC_String_Destroy(tempStr);

    //try reading in the texture
    if(!texture){
        ARC_SDL_Texture_Read(config, string, (void **)&texture);
        if(arc_errno){
            *value = NULL;
        }
    }

    ARC_String_Destroy(textureStr);

    *value = malloc(sizeof(ARC_Spritesheet));
    ((ARC_Spritesheet *) *value)->texture = texture;
    ((ARC_Spritesheet *) *value)->size = size;
}

uint8_t ARC_Spritesheet_Read(ARC_Config* config, ARC_String *string, void **value){
    if(string->data[0] != '{' || string->data[string->length - 1] != '}'){
       ARC_Spritesheet_ReadTexture(config, string, NULL, value);
       return 0;
    }

    uint64_t split = ARC_GraphicsConfig_GetIndexAndErrorCheck(string, ",", 1);
    if(arc_errno){
        return 0;
    }

    ARC_String *temp, *textureStr, *sizeStr;
    ARC_String_CopySubstring(&temp, string, 1, split - 2);
    ARC_String_StripEndsWhitespace(&textureStr, temp);
    ARC_String_Destroy(temp);

    ARC_String_CopySubstring(&temp, string, split + 1, string->length - (split + 2));
    ARC_String_StripEndsWhitespace(&sizeStr, temp);
    ARC_String_Destroy(temp);

    uint32_t *size;
    ARC_Config_Get(config, string, (void **)&size);
    if(!size){
        ARC_ConfigKey_Read_Uint32_t(config, sizeStr, (void **)&size);
        if(arc_errno){
            ARC_String_Destroy(sizeStr);
            ARC_String_Destroy(textureStr);
            return ARC_ERRNO_DATA;
        }
    }

    ARC_Spritesheet_ReadTexture(config, textureStr, size, value);

    ARC_String_Destroy(sizeStr);
    ARC_String_Destroy(textureStr);

    return 0;
}

uint8_t ARC_Sprite_Read(ARC_Config* config, ARC_String *string, void **value){
    ARC_Config_Get(config, string, value);
    if(*value){
        return 1;
    }

    if(string->data[0] != '{' || string->data[string->length - 1] != '}'){
       ARC_Spritesheet_ReadTexture(config, string, NULL, value);
       return 0;
    }

    uint64_t split = ARC_GraphicsConfig_GetIndexAndErrorCheck(string, ",", 1);
    if(arc_errno){
        return 0;
    }

    ARC_String *temp, *spritesheetStr, *framesStr;
    ARC_String_CopySubstring(&temp, string, 1, split - 2);
    ARC_String_StripEndsWhitespace(&spritesheetStr, temp);
    ARC_String_Destroy(temp);

    ARC_String_CopySubstring(&temp, string, split + 1, string->length - (split + 2));
    ARC_String_StripEndsWhitespace(&framesStr, temp);
    ARC_String_Destroy(temp);

    //spritesheet
    ARC_Spritesheet *spritesheet;
    ARC_Config_Get(config, spritesheetStr, (void **)&spritesheet);

    if(!spritesheet){
        ARC_Spritesheet_Read(config, spritesheetStr, (void **)&spritesheet);
        if(arc_errno){
            ARC_String_Destroy(spritesheetStr);
            ARC_String_Destroy(framesStr     );
            return 0;
        }
    }

    //bounds
    ARC_Array *frames;
    ARC_Config_Get(config, framesStr, (void **)&frames);

    if(!frames){
        ARC_RectArray_Read(config, framesStr, (void **)&frames);
        if(arc_errno){
            ARC_String_Destroy(spritesheetStr);
            ARC_String_Destroy(framesStr     );
            return 0;
        }
    }

    ARC_String_Destroy(spritesheetStr);
    ARC_String_Destroy(framesStr     );

    // Scale frames to match spritesheet size
    // TODO: possible bug for sheets that use same frames
    if(spritesheet->size){
        for(uint32_t i = 0; i < frames->size; i++){
            ((ARC_Rect *)frames->data)[i].x *= *spritesheet->size;
            ((ARC_Rect *)frames->data)[i].y *= *spritesheet->size;
            ((ARC_Rect *)frames->data)[i].w *= *spritesheet->size;
            ((ARC_Rect *)frames->data)[i].h *= *spritesheet->size;
        }
    }
    //sprite
    ARC_Sprite_Create((ARC_Sprite **)value, spritesheet, frames);

    return 0;
}

void ARC_SDL_Texture_Delete(ARC_Config* config, ARC_String *string, void *value){
    SDL_DestroyTexture((SDL_Texture *) value);
}

void ARC_Spritesheet_Delete(ARC_Config* config, ARC_String *string, void *value){
    ARC_Spritesheet *sheetValue = (ARC_Spritesheet *)value;

    //check if read in as a Textrue reference
    void *temp;
    ARC_Config_Get(config, string, &temp);
    if(temp){
        //TODO: test to see if this breaks references
        free(sheetValue);
        return;
    }

    uint64_t split = ARC_GraphicsConfig_GetIndexAndErrorCheck(string, ",", 1);
    if(arc_errno){
        //TODO: test to make sure no edge cases
        // free(sheetValue);
        ARC_SDL_Texture_Delete(config, string, value);
        arc_errno = 0;
        return;
    }

    if(split == ~0){

    }

    //check if texture and size are references
    ARC_String *tempStr, *textureStr, *sizeStr;
    ARC_String_CopySubstring(&tempStr, string, 1, split - 1);
    ARC_String_StripEndsWhitespace(&textureStr, tempStr);
    ARC_String_Destroy(tempStr);

    ARC_String_CopySubstring(&tempStr, string, split + 1, string->length - (split + 1));
    ARC_String_StripEndsWhitespace(&sizeStr, tempStr);
    ARC_String_Destroy(tempStr);

    ARC_Config_Get(config, sizeStr, (void **)&temp);
    ARC_String_Destroy(sizeStr);
    if(temp){
        free(sheetValue->size);
    }

    ARC_Config_Get(config, textureStr, (void **)&temp);
    ARC_String_Destroy(textureStr);
    if(temp){
        free(sheetValue->size);
    }

    free(sheetValue);
}

void ARC_Sprite_Delete(ARC_Config* config, ARC_String *string, void *value){
    ARC_Sprite *spriteValue = (ARC_Sprite *)value;

    //check if read in as a Textrue reference
    void *temp;
    uint64_t split = ARC_GraphicsConfig_GetIndexAndErrorCheck(string, ",", 1);
    if(arc_errno){
        free(spriteValue);
        return;
    }

    //check if texture and size are references
    ARC_String *tempStr, *spritesheetStr, *framesStr;
    ARC_String_CopySubstring(&tempStr, string, 1, split - 1);
    ARC_String_StripEndsWhitespace(&spritesheetStr, tempStr);
    ARC_String_Destroy(tempStr);

    ARC_String_CopySubstring(&tempStr, string, split + 1, string->length - (split + 1));
    ARC_String_StripEndsWhitespace(&framesStr, tempStr);
    ARC_String_Destroy(tempStr);

    ARC_Config_Get(config, spritesheetStr, (void **)&temp);
    ARC_String_Destroy(spritesheetStr);
    if(temp){
        free(spriteValue->spritesheet);
    }

    ARC_Config_Get(config, framesStr, (void **)&temp);
    ARC_String_Destroy(framesStr);
    if(temp){
        free(spriteValue->frames);
    }

    free(spriteValue);
}