/*
 * lib/apck_map.h
 * https://gitlab.com/bztsrc/assetpack
 *
 * Copyright (C) 2024 bzt, MIT license
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL ANY
 * DEVELOPER OR DISTRIBUTOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
 * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * @brief Map functions
 */

#ifndef APCK_MAP_H
#define APCK_MAP_H

enum { APCK_ORTHOGONAL = 'o', APCK_ISOMETRIC = 'i', APCK_VERTHEX = 'v', APCK_HORIZHEX = 'h' };

typedef struct {
    uint8_t *v;
    uint32_t x, y;
} apck_map_list_t;

typedef struct {
    int type, disptype;
    int offX, offY;
    int parX, parY;
    int movX, movY;
    uint32_t size;
    uint8_t *data;
} apck_map_layer_t;

typedef struct {
    int type, tilew, tileh, w, h, l, proplen;
    uint8_t *properties;
    apck_map_layer_t *layers;
} apck_map_t;

apck_map_t *apck_map_new(int type, int tilew, int tileh, int w, int h, int proplen, uint8_t *properties);
apck_map_layer_t *apck_map_layer(apck_map_t *map, int type, uint32_t datasize, void *data);
int apck_map_free(apck_map_t *map);
apck_map_t *apck_map_decode(uint8_t *str, uint8_t *buf, uint64_t size);

#ifdef APCK_IMPLEMENTATION

/**
 * Create a new map
 */
apck_map_t *apck_map_new(int type, int tilew, int tileh, int w, int h, int proplen, uint8_t *properties)
{
    apck_map_t *ret;

    if(tilew < 1 || tileh < 1 || w < 1 || h < 1 || !(ret = (apck_map_t*)malloc(sizeof(apck_map_t))))
        return NULL;
    memset(ret, 0, sizeof(apck_map_t));
    ret->type = type;
    ret->tilew = tilew;
    ret->tileh = tileh;
    ret->w = w;
    ret->h = h;
    if(proplen && properties && (ret->properties = (uint8_t*)malloc(proplen))) {
        ret->proplen = proplen;
        memcpy(ret->properties, properties, proplen);
    }
    return ret;
}

/**
 * Add a new layer to map
 */
apck_map_layer_t *apck_map_layer(apck_map_t *map, int type, uint32_t datasize, void *data)
{
    int i;
    uint32_t l = 0;

    if(!map) return NULL;

    /* get the effective layer data size */
    if(type == APCK_SPRITE) l = datasize * sizeof(apck_map_list_t); else
    if(type == APCK_MAP) l = map->w * map->h * sizeof(uint32_t); else {
        if(!data) return NULL;
        if(type >= APCK_OBJECT) l = datasize;
        else l = strlen((char*)data) + 1;
    }

    /* allocate a new layer */
    i = map->l++;
    if(!(map->layers = (apck_map_layer_t*)realloc(map->layers, map->l * sizeof(apck_map_layer_t)))) {
        map->l = 0;
        return NULL;
    }
    memset(&map->layers[i], 0, sizeof(apck_map_layer_t));
    map->layers[i].type = type;
    map->layers[i].parX = map->layers[i].parY = map->layers[i].movX = map->layers[i].movY = 100;
    if(l) {
        /* allocate layer data */
        if(!(map->layers[i].data = (uint8_t*)malloc(l))) {
            map->l--;
            return NULL;
        }
        memset(map->layers[i].data, 0, l);
        map->layers[i].size = l;
        /* fill up data */
        if(type != APCK_MAP && type != APCK_SPRITE && data)
            memcpy(map->layers[i].data, data, l);
    }
    return &map->layers[i];
}

/**
 * Free a map
 */
int apck_map_free(apck_map_t *map)
{
    int i;

    if(!map) return APCK_ERR_BADINP;

    if(map->layers) {
        for(i = 0; i < map->l; i++)
            if(map->layers[i].data)
                free(map->layers[i].data);
        free(map->layers);
    }
    if(map->properties)
        free(map->properties);
    free(map);
    return APCK_OK;
}

/**
 * Deserialize a map
 */
apck_map_t *apck_map_decode(uint8_t *str, uint8_t *buf, uint64_t size)
{
    apck_map_t *map = NULL;
    apck_map_layer_t *layer;
    apck_map_list_t *list;
    uint64_t i, j, k, n, s, tw, th, w, h, l, parX, parY, offX, offY, movX, movY, type, disptype;
    uint32_t *data;
    uint8_t *ptr = buf + 4;

    if(!str || !buf || !size || memcmp(buf, "MAP", 3)) return NULL;

    /* parse header */
    ptr = apck_uleb128(ptr, &tw);
    ptr = apck_uleb128(ptr, &th);
    ptr = apck_uleb128(ptr, &w);
    ptr = apck_uleb128(ptr, &h);
    ptr = apck_uleb128(ptr, &l);
    ptr = apck_uleb128(ptr, &n);
    if(!(map = apck_map_new(buf[3], tw, th, w, h, n, ptr)))
        return NULL;
    ptr += n;
    /* for each layer */
    for(j = 0; j < l; j++) {
        /* parse layer header */
        ptr = apck_uleb128(ptr, &type);
        ptr = apck_uleb128(ptr, &disptype);
        ptr = apck_uleb128(ptr, &offX);
        ptr = apck_uleb128(ptr, &offY);
        ptr = apck_uleb128(ptr, &parX);
        ptr = apck_uleb128(ptr, &parY);
        ptr = apck_uleb128(ptr, &movX);
        ptr = apck_uleb128(ptr, &movY);
        if(type != APCK_MAP)
            ptr = apck_uleb128(ptr, &s);
        else
            s = 0;
        /* add new layer */
        if((layer = apck_map_layer(map, type, s,
          type >= APCK_OBJECT ? ptr : (type != APCK_MAP && type != APCK_SPRITE && str && s ? str + s : NULL)))) {
            layer->disptype = disptype;
            layer->offX = offX; layer->offY = offY;
            layer->parX = parX; layer->parY = parY;
            layer->movX = movX - 100; layer->movY = movY - 100;
            /* for tiles RLE decode layer data */
            if(type == APCK_MAP) {
                data = (uint32_t*)layer->data;
                for(i = 0, n = w * h; i < n;) {
                    s = ((*ptr++) & 0x7F) + 1;
                    if(ptr[-1] & 0x80) {
                        ptr = apck_uleb128(ptr, &k);
                        while(s-- && i < n) { data[i++] = k; }
                    } else
                        while(s-- && i < n) { ptr = apck_uleb128(ptr, &k); data[i++] = k; }
                }
            } else
            /* for sprites, there's a list with x,y coordinates and sprite ids */
            if(type == APCK_SPRITE) {
                list = (apck_map_list_t*)layer->data;
                for(i = 0; i < s; i++) {
                    ptr = apck_uleb128(ptr, &k); list[i].v = str + k;
                    ptr = apck_uleb128(ptr, &k); list[i].x = k;
                    ptr = apck_uleb128(ptr, &k); list[i].y = k;
                }
            } else
            /* for objects, scripts, etc. skip the data bytes that apck_map_layer have already added to the layer */
            if(type >= APCK_OBJECT)
                ptr += s;
        } else
            break;
    }
    return map;
}

#endif /* APCK_IMPLEMENTATION */

#endif /* APIC_MAP_H */
