#include "pxl8_anim.h"

#include <stdlib.h>
#include <string.h>

#include "pxl8_ase.h"
#include "pxl8_atlas.h"
#include "pxl8_gfx.h"
#include "pxl8_log.h"

#define PXL8_ANIM_MAX_STATES 32

typedef struct pxl8_anim_state {
    char* name;
    pxl8_anim* anim;
} pxl8_anim_state;

typedef struct pxl8_anim_state_machine {
    pxl8_anim_state states[PXL8_ANIM_MAX_STATES];
    u16 state_count;
    u16 current_state;
} pxl8_anim_state_machine;

static inline pxl8_anim* pxl8_anim_get_active(pxl8_anim* anim) {
    if (anim->state_machine &&
        anim->state_machine->current_state < anim->state_machine->state_count) {
        pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
        if (state_anim) return state_anim;
    }
    return anim;
}

pxl8_anim* pxl8_anim_create(const u32* frame_ids, const u16* frame_durations, u16 frame_count) {
    if (!frame_ids || frame_count == 0) {
        pxl8_error("Invalid animation parameters");
        return NULL;
    }

    pxl8_anim* anim = (pxl8_anim*)calloc(1, sizeof(pxl8_anim));
    if (!anim) {
        pxl8_error("Failed to allocate animation");
        return NULL;
    }

    anim->frame_ids = (u32*)malloc(frame_count * sizeof(u32));
    if (!anim->frame_ids) {
        pxl8_error("Failed to allocate frame IDs");
        free(anim);
        return NULL;
    }

    memcpy(anim->frame_ids, frame_ids, frame_count * sizeof(u32));

    if (frame_durations) {
        anim->frame_durations = (u16*)malloc(frame_count * sizeof(u16));
        if (!anim->frame_durations) {
            pxl8_error("Failed to allocate frame durations");
            free(anim->frame_ids);
            free(anim);
            return NULL;
        }
        memcpy(anim->frame_durations, frame_durations, frame_count * sizeof(u16));
    } else {
        anim->frame_durations = (u16*)calloc(frame_count, sizeof(u16));
        if (!anim->frame_durations) {
            pxl8_error("Failed to allocate frame durations");
            free(anim->frame_ids);
            free(anim);
            return NULL;
        }
        for (u16 i = 0; i < frame_count; i++) {
            anim->frame_durations[i] = 100;
        }
    }

    anim->frame_count = frame_count;
    anim->current_frame = 0;
    anim->time_accumulator = 0.0f;
    anim->loop = true;
    anim->playing = true;
    anim->reverse = false;
    anim->speed = 1.0f;
    anim->state_machine = NULL;
    anim->on_complete = NULL;
    anim->on_frame_change = NULL;
    anim->userdata = NULL;

    return anim;
}

pxl8_anim* pxl8_anim_create_from_ase(pxl8_gfx* gfx, const char* path) {
    if (!gfx || !path) {
        pxl8_error("Invalid parameters for ASE animation creation");
        return NULL;
    }

    pxl8_ase_file ase_file;
    pxl8_result result = pxl8_ase_load(path, &ase_file);
    if (result != PXL8_OK) {
        pxl8_error("Failed to load ASE file: %s", path);
        return NULL;
    }

    if (ase_file.frame_count == 0) {
        pxl8_error("ASE file has no frames: %s", path);
        pxl8_ase_destroy(&ase_file);
        return NULL;
    }

    u32* frame_ids = (u32*)malloc(ase_file.frame_count * sizeof(u32));
    u16* frame_durations = (u16*)malloc(ase_file.frame_count * sizeof(u16));
    if (!frame_ids || !frame_durations) {
        pxl8_error("Failed to allocate frame arrays");
        free(frame_ids);
        free(frame_durations);
        pxl8_ase_destroy(&ase_file);
        return NULL;
    }

    for (u32 i = 0; i < ase_file.frame_count; i++) {
        pxl8_ase_frame* frame = &ase_file.frames[i];
        result = pxl8_gfx_create_texture(gfx, frame->pixels, frame->width, frame->height);
        if (result != PXL8_OK) {
            pxl8_error("Failed to create texture for frame %u", i);
            free(frame_ids);
            free(frame_durations);
            pxl8_ase_destroy(&ase_file);
            return NULL;
        }
        frame_ids[i] = i;
        frame_durations[i] = frame->duration;
    }

    pxl8_anim* anim = pxl8_anim_create(frame_ids, frame_durations, ase_file.frame_count);

    free(frame_ids);
    free(frame_durations);
    pxl8_ase_destroy(&ase_file);

    return anim;
}

void pxl8_anim_destroy(pxl8_anim* anim) {
    if (!anim) return;

    if (anim->state_machine) {
        for (u16 i = 0; i < anim->state_machine->state_count; i++) {
            free(anim->state_machine->states[i].name);
            pxl8_anim_destroy(anim->state_machine->states[i].anim);
        }
        free(anim->state_machine);
    }

    free(anim->frame_ids);
    free(anim->frame_durations);
    free(anim);
}

pxl8_result pxl8_anim_add_state(pxl8_anim* anim, const char* name, pxl8_anim* state_anim) {
    if (!anim || !name || !state_anim) {
        return PXL8_ERROR_NULL_POINTER;
    }

    if (!anim->state_machine) {
        anim->state_machine = (pxl8_anim_state_machine*)calloc(1, sizeof(pxl8_anim_state_machine));
        if (!anim->state_machine) {
            return PXL8_ERROR_OUT_OF_MEMORY;
        }
    }

    if (anim->state_machine->state_count >= PXL8_ANIM_MAX_STATES) {
        pxl8_error("Cannot add more states, maximum %d reached", PXL8_ANIM_MAX_STATES);
        return PXL8_ERROR_OUT_OF_MEMORY;
    }

    u16 idx = anim->state_machine->state_count;
    anim->state_machine->states[idx].name = strdup(name);
    if (!anim->state_machine->states[idx].name) {
        return PXL8_ERROR_OUT_OF_MEMORY;
    }

    anim->state_machine->states[idx].anim = state_anim;
    anim->state_machine->state_count++;

    return PXL8_OK;
}

u16 pxl8_anim_get_current_frame(const pxl8_anim* anim) {
    if (!anim) return 0;

    if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
        pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
        if (state_anim) {
            return state_anim->current_frame;
        }
    }

    return anim->current_frame;
}

u32 pxl8_anim_get_current_frame_id(const pxl8_anim* anim) {
    if (!anim || !anim->frame_ids) return 0;

    if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
        pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
        if (state_anim && state_anim->frame_ids) {
            return state_anim->frame_ids[state_anim->current_frame];
        }
    }

    return anim->frame_ids[anim->current_frame];
}

const char* pxl8_anim_get_state(const pxl8_anim* anim) {
    if (!anim || !anim->state_machine) return NULL;

    if (anim->state_machine->current_state < anim->state_machine->state_count) {
        return anim->state_machine->states[anim->state_machine->current_state].name;
    }

    return NULL;
}

bool pxl8_anim_has_state_machine(const pxl8_anim* anim) {
    return anim && anim->state_machine && anim->state_machine->state_count > 0;
}

bool pxl8_anim_is_complete(const pxl8_anim* anim) {
    if (!anim) return true;

    if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
        pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
        if (state_anim) {
            return pxl8_anim_is_complete(state_anim);
        }
    }

    if (anim->loop) return false;

    if (anim->reverse) {
        return anim->current_frame == 0;
    } else {
        return anim->current_frame >= anim->frame_count - 1;
    }
}

bool pxl8_anim_is_playing(const pxl8_anim* anim) {
    if (!anim) return false;

    if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
        pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
        if (state_anim) {
            return state_anim->playing;
        }
    }

    return anim->playing;
}

void pxl8_anim_pause(pxl8_anim* anim) {
    if (!anim) return;

    if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
        pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
        if (state_anim) {
            state_anim->playing = false;
            return;
        }
    }

    anim->playing = false;
}

void pxl8_anim_play(pxl8_anim* anim) {
    if (!anim) return;

    if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
        pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
        if (state_anim) {
            state_anim->playing = true;
            return;
        }
    }

    anim->playing = true;
}

void pxl8_anim_render_sprite(const pxl8_anim* anim, pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, bool flip_x, bool flip_y) {
    if (!anim || !gfx) return;

    u32 sprite_id = pxl8_anim_get_current_frame_id(anim);
    pxl8_2d_sprite(gfx, sprite_id, x, y, w, h, flip_x, flip_y);
}

void pxl8_anim_reset(pxl8_anim* anim) {
    if (!anim) return;

    if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
        pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
        if (state_anim) {
            pxl8_anim_reset(state_anim);
            return;
        }
    }

    anim->current_frame = anim->reverse ? anim->frame_count - 1 : 0;
    anim->time_accumulator = 0.0f;
}

void pxl8_anim_set_frame(pxl8_anim* anim, u16 frame) {
    if (!anim) return;
    pxl8_anim* target = pxl8_anim_get_active(anim);
    if (frame < target->frame_count) {
        target->current_frame = frame;
        target->time_accumulator = 0.0f;
    }
}

void pxl8_anim_set_loop(pxl8_anim* anim, bool loop) {
    if (!anim) return;
    pxl8_anim_get_active(anim)->loop = loop;
}

void pxl8_anim_set_reverse(pxl8_anim* anim, bool reverse) {
    if (!anim) return;
    pxl8_anim_get_active(anim)->reverse = reverse;
}

void pxl8_anim_set_speed(pxl8_anim* anim, f32 speed) {
    if (!anim) return;
    pxl8_anim_get_active(anim)->speed = speed;
}

pxl8_result pxl8_anim_set_state(pxl8_anim* anim, const char* name) {
    if (!anim || !name) {
        return PXL8_ERROR_NULL_POINTER;
    }

    if (!anim->state_machine) {
        return PXL8_ERROR_INVALID_ARGUMENT;
    }

    for (u16 i = 0; i < anim->state_machine->state_count; i++) {
        if (strcmp(anim->state_machine->states[i].name, name) == 0) {
            if (anim->state_machine->current_state != i) {
                anim->state_machine->current_state = i;
                pxl8_anim_reset(anim->state_machine->states[i].anim);
            }
            return PXL8_OK;
        }
    }

    pxl8_error("Animation state not found: %s", name);
    return PXL8_ERROR_INVALID_ARGUMENT;
}

void pxl8_anim_stop(pxl8_anim* anim) {
    if (!anim) return;

    if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
        pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
        if (state_anim) {
            pxl8_anim_stop(state_anim);
            return;
        }
    }

    anim->playing = false;
    pxl8_anim_reset(anim);
}

void pxl8_anim_update(pxl8_anim* anim, f32 dt) {
    if (!anim || !anim->playing) return;

    if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
        pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
        if (state_anim) {
            pxl8_anim_update(state_anim, dt);
            return;
        }
    }

    if (anim->frame_count == 0 || !anim->frame_durations) return;

    anim->time_accumulator += dt * anim->speed * 1000.0f;

    u16 current_duration = anim->frame_durations[anim->current_frame];
    if (current_duration == 0) return;

    while (anim->time_accumulator >= current_duration) {
        anim->time_accumulator -= current_duration;

        u16 old_frame = anim->current_frame;

        if (anim->reverse) {
            if (anim->current_frame > 0) {
                anim->current_frame--;
            } else {
                if (anim->loop) {
                    anim->current_frame = anim->frame_count - 1;
                } else {
                    anim->playing = false;
                    if (anim->on_complete) {
                        anim->on_complete(anim->userdata);
                    }
                    break;
                }
            }
        } else {
            if (anim->current_frame < anim->frame_count - 1) {
                anim->current_frame++;
            } else {
                if (anim->loop) {
                    anim->current_frame = 0;
                } else {
                    anim->playing = false;
                    if (anim->on_complete) {
                        anim->on_complete(anim->userdata);
                    }
                    break;
                }
            }
        }

        if (old_frame != anim->current_frame && anim->on_frame_change) {
            anim->on_frame_change(anim->current_frame, anim->userdata);
        }

        current_duration = anim->frame_durations[anim->current_frame];
        if (current_duration == 0) break;
    }
}
