#include "pxl8_ase.h"

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

#define MINIZ_NO_STDIO
#define MINIZ_NO_TIME
#define MINIZ_NO_ARCHIVE_APIS
#define MINIZ_NO_ARCHIVE_WRITING_APIS
#define MINIZ_NO_DEFLATE_APIS
#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES

#include <miniz.h>

#include "pxl8_io.h"
#include "pxl8_log.h"

static pxl8_result parse_ase_header(pxl8_stream* stream, pxl8_ase_header* header) {
    header->file_size = pxl8_read_u32(stream);
    header->magic = pxl8_read_u16(stream);
    header->frames = pxl8_read_u16(stream);
    header->width = pxl8_read_u16(stream);
    header->height = pxl8_read_u16(stream);
    header->color_depth = pxl8_read_u16(stream);
    header->flags = pxl8_read_u32(stream);
    header->speed = pxl8_read_u16(stream);
    pxl8_skip_bytes(stream, 4);
    header->transparent_index = pxl8_read_u32(stream);
    header->n_colors = pxl8_read_u8(stream);
    header->pixel_width = pxl8_read_u8(stream);
    header->pixel_height = pxl8_read_u8(stream);
    header->grid_x = pxl8_read_i16(stream);
    header->grid_y = pxl8_read_i16(stream);
    header->grid_width = pxl8_read_u16(stream);
    header->grid_height = pxl8_read_u16(stream);

    if (header->magic != PXL8_ASE_MAGIC) {
        pxl8_error("Invalid ASE file magic: 0x%04X", header->magic);
        return PXL8_ERROR_ASE_INVALID_MAGIC;
    }

    return PXL8_OK;
}

static pxl8_result parse_old_palette_chunk(pxl8_stream* stream, pxl8_ase_palette* palette) {
    u16 packet_count = pxl8_read_u16(stream);

    u32 total_colors = 0;
    u32 temp_pos = pxl8_stream_position(stream);
    for (u16 packet = 0; packet < packet_count; packet++) {
        u8 skip_colors = pxl8_read_u8(stream);
        u8 colors_in_packet = pxl8_read_u8(stream);
        u32 actual_colors = (colors_in_packet == 0) ? 256 : colors_in_packet;
        total_colors += skip_colors + actual_colors;
        pxl8_skip_bytes(stream, actual_colors * 3);
    }

    palette->entry_count = total_colors;
    palette->first_color = 0;
    palette->last_color = total_colors - 1;
    palette->colors = (u32*)malloc(total_colors * sizeof(u32));
    if (!palette->colors) {
        return PXL8_ERROR_OUT_OF_MEMORY;
    }

    pxl8_stream_seek(stream, temp_pos);

    u32 color_index = 0;
    for (u16 packet = 0; packet < packet_count; packet++) {
        u8 skip_colors = pxl8_read_u8(stream);
        u8 colors_in_packet = pxl8_read_u8(stream);
        u32 actual_colors = (colors_in_packet == 0) ? 256 : colors_in_packet;

        for (u32 skip = 0; skip < skip_colors && color_index < total_colors; skip++) {
            palette->colors[color_index++] = 0xFF000000;
        }

        for (u32 color = 0; color < actual_colors && color_index < total_colors; color++) {
            u8 r = pxl8_read_u8(stream);
            u8 g = pxl8_read_u8(stream);
            u8 b = pxl8_read_u8(stream);
            palette->colors[color_index++] = 0xFF000000 | ((u32)b << 16) | ((u32)g << 8) | r;
        }
    }

    return PXL8_OK;
}

static pxl8_result parse_layer_chunk(pxl8_stream* stream, pxl8_ase_layer* layer) {
    layer->flags = pxl8_read_u16(stream);
    layer->layer_type = pxl8_read_u16(stream);
    layer->child_level = pxl8_read_u16(stream);
    pxl8_skip_bytes(stream, 4);
    layer->blend_mode = pxl8_read_u16(stream);
    layer->opacity = pxl8_read_u8(stream);
    pxl8_skip_bytes(stream, 3);

    u16 name_len = pxl8_read_u16(stream);
    if (name_len > 0) {
        layer->name = (char*)malloc(name_len + 1);
        if (!layer->name) return PXL8_ERROR_OUT_OF_MEMORY;
        pxl8_read_bytes(stream, layer->name, name_len);
        layer->name[name_len] = '\0';
    } else {
        layer->name = NULL;
    }

    return PXL8_OK;
}

static pxl8_result parse_palette_chunk(pxl8_stream* stream, pxl8_ase_palette* palette) {
    pxl8_skip_bytes(stream, 4);
    palette->first_color = pxl8_read_u32(stream);
    palette->last_color = pxl8_read_u32(stream);
    pxl8_skip_bytes(stream, 8);

    u32 color_count = palette->last_color - palette->first_color + 1;

    if (color_count == 0 || color_count > 65536) {
        return PXL8_ERROR_ASE_MALFORMED_CHUNK;
    }

    palette->colors = (u32*)malloc(color_count * sizeof(u32));
    if (!palette->colors) {
        return PXL8_ERROR_OUT_OF_MEMORY;
    }

    palette->entry_count = color_count;

    for (u32 i = 0; i < color_count; i++) {
        u16 flags = pxl8_read_u16(stream);
        u8 r = pxl8_read_u8(stream);
        u8 g = pxl8_read_u8(stream);
        u8 b = pxl8_read_u8(stream);
        u8 a = pxl8_read_u8(stream);

        palette->colors[i] = ((u32)a << 24) | ((u32)b << 16) | ((u32)g << 8) | r;

        if (flags & 1) {
            u16 name_len = pxl8_read_u16(stream);
            pxl8_skip_bytes(stream, name_len);
        }
    }

    return PXL8_OK;
}

static pxl8_result parse_user_data_chunk(pxl8_stream* stream, pxl8_ase_user_data* user_data) {
    u32 flags = pxl8_read_u32(stream);

    user_data->has_text = (flags & 1) != 0;
    user_data->has_color = (flags & 2) != 0;

    if (user_data->has_text) {
        u16 text_len = pxl8_read_u16(stream);
        if (text_len > 0) {
            user_data->text = (char*)malloc(text_len + 1);
            if (!user_data->text) return PXL8_ERROR_OUT_OF_MEMORY;
            pxl8_read_bytes(stream, user_data->text, text_len);
            user_data->text[text_len] = '\0';
        }
    }

    if (user_data->has_color) {
        u8 r = pxl8_read_u8(stream);
        u8 g = pxl8_read_u8(stream);
        u8 b = pxl8_read_u8(stream);
        u8 a = pxl8_read_u8(stream);
        user_data->color = ((u32)a << 24) | ((u32)b << 16) | ((u32)g << 8) | r;
    }

    if (flags & 4) {
        pxl8_skip_bytes(stream, 4);
        u32 num_properties = pxl8_read_u32(stream);

        if (num_properties > 0) {
            user_data->properties = (pxl8_ase_property*)calloc(num_properties, sizeof(pxl8_ase_property));
            if (!user_data->properties) return PXL8_ERROR_OUT_OF_MEMORY;
            user_data->property_count = num_properties;

            for (u32 i = 0; i < num_properties; i++) {
                u16 name_len = pxl8_read_u16(stream);
                if (name_len > 0) {
                    user_data->properties[i].name = (char*)malloc(name_len + 1);
                    if (!user_data->properties[i].name) return PXL8_ERROR_OUT_OF_MEMORY;
                    pxl8_read_bytes(stream, user_data->properties[i].name, name_len);
                    user_data->properties[i].name[name_len] = '\0';
                }

                u16 type = pxl8_read_u16(stream);
                user_data->properties[i].type = type;

                switch (type) {
                    case 1:
                        user_data->properties[i].bool_val = pxl8_read_u8(stream) != 0;
                        break;
                    case 2:
                    case 3:
                    case 4:
                    case 5:
                        user_data->properties[i].int_val = pxl8_read_i32(stream);
                        break;
                    case 6:
                    case 7:
                        user_data->properties[i].float_val = pxl8_read_f32(stream);
                        break;
                    case 8: {
                        u16 str_len = pxl8_read_u16(stream);
                        if (str_len > 0) {
                            user_data->properties[i].string_val = (char*)malloc(str_len + 1);
                            if (!user_data->properties[i].string_val) return PXL8_ERROR_OUT_OF_MEMORY;
                            pxl8_read_bytes(stream, user_data->properties[i].string_val, str_len);
                            user_data->properties[i].string_val[str_len] = '\0';
                        }
                        break;
                    }
                    default:
                        pxl8_skip_bytes(stream, 4);
                        break;
                }
            }
        }
    }

    return PXL8_OK;
}

static pxl8_result parse_tileset_chunk(pxl8_stream* stream, pxl8_ase_tileset* tileset) {
    tileset->id = pxl8_read_u32(stream);
    tileset->flags = pxl8_read_u32(stream);
    tileset->tile_count = pxl8_read_u32(stream);
    tileset->tile_width = pxl8_read_u16(stream);
    tileset->tile_height = pxl8_read_u16(stream);
    tileset->base_index = pxl8_read_i16(stream);
    pxl8_skip_bytes(stream, 14);

    u16 name_len = pxl8_read_u16(stream);
    if (name_len > 0) {
        tileset->name = (char*)malloc(name_len + 1);
        if (!tileset->name) return PXL8_ERROR_OUT_OF_MEMORY;
        pxl8_read_bytes(stream, tileset->name, name_len);
        tileset->name[name_len] = '\0';
    }

    if (tileset->flags & 1) {
        pxl8_skip_bytes(stream, 8);  // external_file_id + tileset_id
    }

    if (tileset->flags & 2) {
        u32 compressed_size = pxl8_read_u32(stream);
        tileset->pixels_size = tileset->tile_width * tileset->tile_height * tileset->tile_count;
        tileset->pixels = (u8*)malloc(tileset->pixels_size);
        if (!tileset->pixels) return PXL8_ERROR_OUT_OF_MEMORY;

        const u8* compressed_data = pxl8_read_ptr(stream, compressed_size);
        mz_ulong dest_len = tileset->pixels_size;
        i32 result = mz_uncompress(tileset->pixels, &dest_len, compressed_data, compressed_size);
        if (result != MZ_OK) {
            pxl8_error("Failed to decompress tileset data: miniz error %d", result);
            free(tileset->pixels);
            tileset->pixels = NULL;
            return PXL8_ERROR_ASE_MALFORMED_CHUNK;
        }
    }

    tileset->tile_user_data = (pxl8_ase_user_data*)calloc(tileset->tile_count, sizeof(pxl8_ase_user_data));
    if (!tileset->tile_user_data) return PXL8_ERROR_OUT_OF_MEMORY;

    return PXL8_OK;
}

static pxl8_result parse_cel_chunk(pxl8_stream* stream, u32 chunk_size, pxl8_ase_cel* cel) {
    if (chunk_size < 9) return PXL8_ERROR_ASE_MALFORMED_CHUNK;

    cel->layer_index = pxl8_read_u16(stream);
    cel->x = pxl8_read_i16(stream);
    cel->y = pxl8_read_i16(stream);
    cel->opacity = pxl8_read_u8(stream);
    cel->cel_type = pxl8_read_u16(stream);
    pxl8_skip_bytes(stream, 7);

    if (cel->cel_type == 2) {
        if (chunk_size < 20) return PXL8_ERROR_ASE_MALFORMED_CHUNK;

        cel->image.width = pxl8_read_u16(stream);
        cel->image.height = pxl8_read_u16(stream);

        u32 pixels_size = cel->image.width * cel->image.height;
        u32 compressed_size = chunk_size - 20;

        cel->image.pixels = (u8*)malloc(pixels_size);
        if (!cel->image.pixels) return PXL8_ERROR_OUT_OF_MEMORY;

        const u8* compressed_data = pxl8_read_ptr(stream, compressed_size);
        mz_ulong dest_len = pixels_size;
        i32 result = mz_uncompress(cel->image.pixels, &dest_len, compressed_data, compressed_size);
        if (result != MZ_OK) {
            pxl8_error("Failed to decompress cel data: miniz error %d", result);
            free(cel->image.pixels);
            cel->image.pixels = NULL;
            return PXL8_ERROR_ASE_MALFORMED_CHUNK;
        }

        if (dest_len != pixels_size) {
            pxl8_warn("Decompressed size mismatch: expected %u, got %lu", pixels_size, dest_len);
        }
    } else if (cel->cel_type == 3) {
        if (chunk_size < 36) return PXL8_ERROR_ASE_MALFORMED_CHUNK;

        cel->tilemap.width = pxl8_read_u16(stream);
        cel->tilemap.height = pxl8_read_u16(stream);
        cel->tilemap.bits_per_tile = pxl8_read_u16(stream);
        cel->tilemap.tile_id_mask = pxl8_read_u32(stream);
        cel->tilemap.x_flip_mask = pxl8_read_u32(stream);
        cel->tilemap.y_flip_mask = pxl8_read_u32(stream);
        cel->tilemap.diag_flip_mask = pxl8_read_u32(stream);
        pxl8_skip_bytes(stream, 10);

        u32 tile_count = cel->tilemap.width * cel->tilemap.height;
        u32 bytes_per_tile = (cel->tilemap.bits_per_tile + 7) / 8;
        u32 uncompressed_size = tile_count * bytes_per_tile;
        u32 compressed_size = chunk_size - 36;

        u8* temp_buffer = (u8*)malloc(uncompressed_size);
        if (!temp_buffer) return PXL8_ERROR_OUT_OF_MEMORY;

        const u8* compressed_data = pxl8_read_ptr(stream, compressed_size);
        mz_ulong dest_len = uncompressed_size;
        i32 result = mz_uncompress(temp_buffer, &dest_len, compressed_data, compressed_size);
        if (result != MZ_OK) {
            pxl8_error("Failed to decompress tilemap data: miniz error %d", result);
            free(temp_buffer);
            return PXL8_ERROR_ASE_MALFORMED_CHUNK;
        }

        cel->tilemap.tiles = (u32*)calloc(tile_count, sizeof(u32));
        if (!cel->tilemap.tiles) {
            free(temp_buffer);
            return PXL8_ERROR_OUT_OF_MEMORY;
        }

        for (u32 i = 0; i < tile_count; i++) {
            if (cel->tilemap.bits_per_tile == 32) {
                cel->tilemap.tiles[i] = ((u32)temp_buffer[i * 4 + 0]) |
                                        ((u32)temp_buffer[i * 4 + 1] << 8) |
                                        ((u32)temp_buffer[i * 4 + 2] << 16) |
                                        ((u32)temp_buffer[i * 4 + 3] << 24);
            } else if (cel->tilemap.bits_per_tile == 16) {
                cel->tilemap.tiles[i] = ((u32)temp_buffer[i * 2 + 0]) |
                                        ((u32)temp_buffer[i * 2 + 1] << 8);
            } else if (cel->tilemap.bits_per_tile == 8) {
                cel->tilemap.tiles[i] = temp_buffer[i];
            }
        }

        free(temp_buffer);
    }

    return PXL8_OK;
}

pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
    if (!filepath || !ase_file) {
        return PXL8_ERROR_NULL_POINTER;
    }

    memset(ase_file, 0, sizeof(pxl8_ase_file));

    u8* file_data;
    size_t file_size;
    pxl8_result result = pxl8_io_read_binary_file(filepath, &file_data, &file_size);
    if (result != PXL8_OK) {
        return result;
    }

    if (file_size < 128) {
        pxl8_io_free_binary_data(file_data);
        return PXL8_ERROR_ASE_TRUNCATED_FILE;
    }

    pxl8_stream stream = pxl8_stream_create(file_data, (u32)file_size);

    result = parse_ase_header(&stream, &ase_file->header);
    if (result != PXL8_OK) {
        pxl8_io_free_binary_data(file_data);
        return result;
    }

    ase_file->frame_count = ase_file->header.frames;
    ase_file->frames = (pxl8_ase_frame*)calloc(ase_file->frame_count, sizeof(pxl8_ase_frame));
    if (!ase_file->frames) {
        pxl8_io_free_binary_data(file_data);
        return PXL8_ERROR_OUT_OF_MEMORY;
    }

    pxl8_stream_seek(&stream, 128);

    for (u16 frame_idx = 0; frame_idx < ase_file->header.frames; frame_idx++) {
        u32 frame_start = pxl8_stream_position(&stream);

        pxl8_ase_frame_header frame_header;
        frame_header.frame_bytes = pxl8_read_u32(&stream);
        frame_header.magic = pxl8_read_u16(&stream);
        u16 old_chunks = pxl8_read_u16(&stream);
        frame_header.duration = pxl8_read_u16(&stream);
        pxl8_skip_bytes(&stream, 2);

        if (frame_header.magic != PXL8_ASE_FRAME_MAGIC) {
            pxl8_error("Invalid frame magic: 0x%04X", frame_header.magic);
            result = PXL8_ERROR_ASE_INVALID_FRAME_MAGIC;
            break;
        }

        if (old_chunks == 0xFFFF || old_chunks == 0xFFFE) {
            frame_header.chunks = pxl8_read_u32(&stream);
        } else {
            frame_header.chunks = old_chunks;
            pxl8_skip_bytes(&stream, 4);
        }

        pxl8_ase_frame* frame = &ase_file->frames[frame_idx];
        frame->frame_id = frame_idx;
        frame->width = ase_file->header.width;
        frame->height = ase_file->header.height;
        frame->duration = frame_header.duration;

        u32 pixel_count = frame->width * frame->height;
        frame->pixels = (u8*)calloc(pixel_count, sizeof(u8));
        if (!frame->pixels) {
            result = PXL8_ERROR_OUT_OF_MEMORY;
            break;
        }

        u32 last_tileset_idx = 0xFFFFFFFF;
        u32 tileset_tile_idx = 0;

        for (u16 chunk_idx = 0; chunk_idx < frame_header.chunks; chunk_idx++) {
            u32 chunk_start = pxl8_stream_position(&stream);

            pxl8_ase_chunk_header chunk_header;
            chunk_header.chunk_size = pxl8_read_u32(&stream);
            chunk_header.chunk_type = pxl8_read_u16(&stream);

            if (chunk_header.chunk_size < 6 || chunk_header.chunk_size > frame_header.frame_bytes) {
                pxl8_error("Invalid chunk size %u in ASE file (frame_bytes=%u)",
                          chunk_header.chunk_size, frame_header.frame_bytes);
                result = PXL8_ERROR_ASE_MALFORMED_CHUNK;
                break;
            }

            switch (chunk_header.chunk_type) {
                case PXL8_ASE_CHUNK_OLD_PALETTE:
                    if (!ase_file->palette.colors) {
                        result = parse_old_palette_chunk(&stream, &ase_file->palette);
                    }
                    break;

                case PXL8_ASE_CHUNK_LAYER: {
                    pxl8_ase_layer* new_layers =
                        (pxl8_ase_layer*)realloc(ase_file->layers,
                            (ase_file->layer_count + 1) * sizeof(pxl8_ase_layer));
                    if (!new_layers) {
                        result = PXL8_ERROR_OUT_OF_MEMORY;
                        break;
                    }
                    ase_file->layers = new_layers;

                    result = parse_layer_chunk(&stream, &ase_file->layers[ase_file->layer_count]);
                    if (result == PXL8_OK) {
                        ase_file->layer_count++;
                    }
                    break;
                }

                case PXL8_ASE_CHUNK_CEL: {
                    pxl8_ase_cel cel = {0};
                    result = parse_cel_chunk(&stream, chunk_header.chunk_size - 6, &cel);
                    if (result == PXL8_OK) {
                        pxl8_ase_cel* new_cels = (pxl8_ase_cel*)realloc(frame->cels, (frame->cel_count + 1) * sizeof(pxl8_ase_cel));
                        if (!new_cels) {
                            result = PXL8_ERROR_OUT_OF_MEMORY;
                            break;
                        }
                        frame->cels = new_cels;
                        frame->cels[frame->cel_count] = cel;
                        frame->cel_count++;

                        if (cel.cel_type == 2 && cel.image.pixels) {
                            u32 copy_width = (cel.image.width < frame->width) ? cel.image.width : frame->width;
                            u32 copy_height = (cel.image.height < frame->height) ? cel.image.height : frame->height;

                            for (u32 y = 0; y < copy_height; y++) {
                                u32 src_offset = y * cel.image.width;
                                u32 dst_offset = (y + cel.y) * frame->width + cel.x;
                                if (dst_offset + copy_width <= pixel_count) {
                                    for (u32 x = 0; x < copy_width; x++) {
                                        u8 src_pixel = cel.image.pixels[src_offset + x];
                                        bool is_transparent = false;

                                        if (src_pixel < ase_file->palette.entry_count && ase_file->palette.colors) {
                                            u32 color = ase_file->palette.colors[src_pixel];
                                            is_transparent = ((color >> 24) & 0xFF) == 0;
                                        }

                                        if (!is_transparent) {
                                            frame->pixels[dst_offset + x] = src_pixel;
                                        }
                                    }
                                }
                            }
                        }
                    }
                    break;
                }

                case PXL8_ASE_CHUNK_PALETTE:
                    if (ase_file->palette.colors) {
                        free(ase_file->palette.colors);
                        ase_file->palette.colors = NULL;
                    }
                    result = parse_palette_chunk(&stream, &ase_file->palette);
                    break;

                case PXL8_ASE_CHUNK_TILESET: {
                    pxl8_ase_tileset* new_tilesets = (pxl8_ase_tileset*)realloc(ase_file->tilesets,
                        (ase_file->tileset_count + 1) * sizeof(pxl8_ase_tileset));
                    if (!new_tilesets) {
                        result = PXL8_ERROR_OUT_OF_MEMORY;
                        break;
                    }
                    ase_file->tilesets = new_tilesets;

                    memset(&ase_file->tilesets[ase_file->tileset_count], 0, sizeof(pxl8_ase_tileset));
                    result = parse_tileset_chunk(&stream, &ase_file->tilesets[ase_file->tileset_count]);
                    if (result == PXL8_OK) {
                        last_tileset_idx = ase_file->tileset_count;
                        tileset_tile_idx = 0;
                        ase_file->tileset_count++;
                    }
                    break;
                }

                case PXL8_ASE_CHUNK_USER_DATA: {
                    if (last_tileset_idx != 0xFFFFFFFF) {
                        pxl8_ase_tileset* tileset = &ase_file->tilesets[last_tileset_idx];
                        if (tileset_tile_idx < tileset->tile_count) {
                            result = parse_user_data_chunk(&stream, &tileset->tile_user_data[tileset_tile_idx]);
                            tileset_tile_idx++;
                        }
                    }
                    break;
                }

                default:
                    break;
            }

            if (result != PXL8_OK) break;

            pxl8_stream_seek(&stream, chunk_start + chunk_header.chunk_size);
        }

        if (result != PXL8_OK) break;

        pxl8_stream_seek(&stream, frame_start + frame_header.frame_bytes);
    }

    pxl8_io_free_binary_data(file_data);

    if (result != PXL8_OK) {
        pxl8_ase_destroy(ase_file);
    }

    return result;
}

void pxl8_ase_destroy(pxl8_ase_file* ase_file) {
    if (!ase_file) return;

    if (ase_file->frames) {
        for (u32 i = 0; i < ase_file->frame_count; i++) {
            if (ase_file->frames[i].pixels) free(ase_file->frames[i].pixels);
            if (ase_file->frames[i].cels) {
                for (u32 j = 0; j < ase_file->frames[i].cel_count; j++) {
                    pxl8_ase_cel* cel = &ase_file->frames[i].cels[j];
                    if (cel->cel_type == 2 && cel->image.pixels) {
                        free(cel->image.pixels);
                    } else if (cel->cel_type == 3 && cel->tilemap.tiles) {
                        free(cel->tilemap.tiles);
                    }
                }
                free(ase_file->frames[i].cels);
            }
        }
        free(ase_file->frames);
    }

    if (ase_file->palette.colors) {
        free(ase_file->palette.colors);
    }

    if (ase_file->layers) {
        for (u32 i = 0; i < ase_file->layer_count; i++) {
            if (ase_file->layers[i].name) {
                free(ase_file->layers[i].name);
            }
        }
        free(ase_file->layers);
    }

    if (ase_file->tilesets) {
        for (u32 i = 0; i < ase_file->tileset_count; i++) {
            if (ase_file->tilesets[i].name) free(ase_file->tilesets[i].name);
            if (ase_file->tilesets[i].pixels) free(ase_file->tilesets[i].pixels);
            if (ase_file->tilesets[i].tile_user_data) {
                for (u32 j = 0; j < ase_file->tilesets[i].tile_count; j++) {
                    pxl8_ase_user_data* ud = &ase_file->tilesets[i].tile_user_data[j];
                    if (ud->text) free(ud->text);
                    if (ud->properties) {
                        for (u32 k = 0; k < ud->property_count; k++) {
                            if (ud->properties[k].name) free(ud->properties[k].name);
                            if (ud->properties[k].type == 8 && ud->properties[k].string_val) {
                                free(ud->properties[k].string_val);
                            }
                        }
                        free(ud->properties);
                    }
                }
                free(ase_file->tilesets[i].tile_user_data);
            }
        }
        free(ase_file->tilesets);
    }

    memset(ase_file, 0, sizeof(pxl8_ase_file));
}
