/*
 * tngp/map.c
 *
 * Copyright (C) 2022 bzt
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * @brief Display map on game area
 *
 */

#include "tngp.h"

/* NOTE: No UI related stuff allowed in this file, because it is used by the server too. Use game.c for that */

#define MAP_DISTANCE 3 /* distance in map tiles to be considered close */

/*
world_scene_t.maps:

TNG_TYPE_ORTHO, TNG_TYPE_ISO:
+--------+--------+--------+
| 0 NW   | 1 N    | 2 NE   |    NW, NE loaded from N's neightbors
+--------+--------+--------+
| 3 W    | 4 main | 5 E    |    N, W, E, S loaded from main's neightbors
+--------+--------+--------+
| 6 SW   | 7 S    | 8 SE   |    SW, SE loaded from S's neightbors
+--------+--------+--------+
TNG_TYPE_HEXV:
+--------+--------+--------+
| 0 NW   | x      | 2 NE   |     NW | NE
+--------+--------+--------+     \ / \ /
| 3 W    | 4 main | 5 E    |    W |   | E
+--------+--------+--------+     / \ / \
| 6 SW   | x      | 8 SE   |     SW | SE
+--------+--------+--------+
TNG_TYPE_HEXH:
+--------+--------+--------+
| 0 NW   | 1 N    | 2 NE   |        N
+--------+--------+--------+   NW \___/ NE
| x      | 4 main | x      |    __/   \__
+--------+--------+--------+      \___/
| 6 SW   | 7 S    | 8 SE   |   SW /   \ SE
+--------+--------+--------+        S
*/

int map_half, map_mm, map_halfw, map_halfh, map_halftw, map_halfth, map_w, map_h;

/**
 * Initialize maps
 */
void map_init()
{
    map_half = world.mapsize / 2;
    map_mm = world.mapsize * world.mapsize;
    map_halftw = world.tilew / 2;
    map_halfth = world.tileh / 2;
    map_halfw = world.mapsize * map_halftw;
    map_halfh = world.mapsize * map_halfth;
    map_w = world.mapsize * world.tilew;
    map_h = world.mapsize * world.tileh;
}

/**
 * Convert tile coordinates to map coordinates
 */
void map_tile2map(int srcx, int srcy, int *dstx, int *dsty)
{
    if(!dstx || !dsty) return;
    switch(world.type) {
        case TNG_TYPE_ORTHO:
            *dstx = world.tilew * srcx + map_halftw;
            *dsty = world.tileh * srcy + world.tileh - 1;
        break;
        case TNG_TYPE_ISO:
            *dstx = (srcx - srcy) * map_halftw + map_halfw;
            *dsty = (srcx + srcy) * map_halfth;
        break;
        /* TODO: hexagonal grid */
        case TNG_TYPE_HEXV:
        break;
        case TNG_TYPE_HEXH:
        break;
    }
}

/**
 * Convert map coordinates to tile coordinates
 */
void map_map2tile(int srcx, int srcy, int *dstx, int *dsty)
{
    if(!dstx || !dsty) return;
    switch(world.type) {
        case TNG_TYPE_ORTHO:
            *dstx = srcx / world.tilew;
            *dsty = srcy / world.tileh;
        break;
        case TNG_TYPE_ISO:
            srcx -= map_halfw;
            /* this is integer arithmetic madness: 31/32 = 0, -31/32 = 0 loses sign! */
            *dstx = (int)((float)srcx / (float)world.tilew + (float)srcy / (float)world.tileh);
            *dsty = (int)((float)srcy / (float)world.tileh - (float)srcx / (float)world.tilew);
        case TNG_TYPE_HEXV:
        break;
        case TNG_TYPE_HEXH:
        break;
    }
}

/**
 * Get distance between two entities in map tiles
 */
void map_dist(world_map_t **maps, world_entity_t *this, world_entity_t *ent, int *x, int *y)
{
    int tx, ty, ex, ey;

    if(!x || !y) return;
    *x = 0; *y = 0;
    if(!maps || !this || !ent) return;
    map_map2tile(this->x, this->y, &tx, &ty);
    map_map2tile(ent->x, ent->y, &ex, &ey);
    *x = ex - tx; *y = ey - ty;
    if(this->map == ent->map) return;   /* same map, neightbors doesn't matter */
    if(!maps[MAP_MAIN] || maps[MAP_MAIN]->id != this->map) { *x = *y = 2147483647; return; }
    /* add displacement if they are on neightboring maps */
    switch(world.type) {
        case TNG_TYPE_ORTHO:
        case TNG_TYPE_ISO:
            if(maps[MAP_NW] && ent->map == maps[MAP_NW]->id) { *x -= world.mapsize; *y -= world.mapsize; } else
            if(maps[MAP_N] && ent->map == maps[MAP_N]->id) { *y -= world.mapsize; } else
            if(maps[MAP_NE] && ent->map == maps[MAP_NE]->id) { *x += world.mapsize; *y -= world.mapsize; } else
            if(maps[MAP_W] && ent->map == maps[MAP_W]->id) { *x -= world.mapsize; } else
            if(maps[MAP_E] && ent->map == maps[MAP_E]->id) { *x += world.mapsize; } else
            if(maps[MAP_SW] && ent->map == maps[MAP_SW]->id) { *x -= world.mapsize; *y += world.mapsize; } else
            if(maps[MAP_S] && ent->map == maps[MAP_S]->id) { *y += world.mapsize; } else
            if(maps[MAP_SE] && ent->map == maps[MAP_SE]->id) { *x += world.mapsize; *y += world.mapsize; }
        break;
        case TNG_TYPE_HEXV:
            if(maps[MAP_NW] && ent->map == maps[MAP_NW]->id) { *x -= map_half; *y -= world.mapsize; } else
            if(maps[MAP_N] && ent->map == maps[MAP_N]->id) { *y -= world.mapsize; } else
            if(maps[MAP_NE] && ent->map == maps[MAP_NE]->id) { *x += map_half; *y -= world.mapsize; } else
            if(maps[MAP_W] && ent->map == maps[MAP_W]->id) { *x -= world.mapsize; } else
            if(maps[MAP_E] && ent->map == maps[MAP_E]->id) { *x += world.mapsize; } else
            if(maps[MAP_SW] && ent->map == maps[MAP_SW]->id) { *x -= map_half; *y += world.mapsize; } else
            if(maps[MAP_S] && ent->map == maps[MAP_S]->id) { *y += world.mapsize; } else
            if(maps[MAP_SE] && ent->map == maps[MAP_SE]->id) { *x += map_half; *y += world.mapsize; }
        break;
        case TNG_TYPE_HEXH:
            if(maps[MAP_NW] && ent->map == maps[MAP_NW]->id) { *x -= world.mapsize; *y -= map_half; } else
            if(maps[MAP_N] && ent->map == maps[MAP_N]->id) { *y -= world.mapsize; } else
            if(maps[MAP_NE] && ent->map == maps[MAP_NE]->id) { *x += world.mapsize; *y -= map_half; } else
            if(maps[MAP_W] && ent->map == maps[MAP_W]->id) { *x -= world.mapsize; } else
            if(maps[MAP_E] && ent->map == maps[MAP_E]->id) { *x += world.mapsize; } else
            if(maps[MAP_SW] && ent->map == maps[MAP_SW]->id) { *x -= world.mapsize; *y += map_half; } else
            if(maps[MAP_S] && ent->map == maps[MAP_S]->id) { *y += world.mapsize; } else
            if(maps[MAP_SE] && ent->map == maps[MAP_SE]->id) { *x += world.mapsize; *y += map_half; }
        break;
    }
}

/**
 * Load maps
 */
void map_load(world_map_t **maps, int neightbor, int map)
{
    int id;

    if(!maps || map < 0 || map >= world.nummap || (maps[neightbor] && maps[neightbor]->id == map)) return;
    if(neightbor == MAP_MAIN) map_unload(maps);
    if(world.hdr.magic[0] != '#') {
        id = SDL_SwapLE32(map);
        client_send(10, (uint8_t*)&id, 4);
    } else {
        if(maps[neightbor]) world_freemap(maps[neightbor]);
        maps[neightbor] = world_loadmap(map);
        if(maps[neightbor]) {
            if(neightbor == MAP_MAIN)
                switch(world.type) {
                    case TNG_TYPE_ORTHO: case TNG_TYPE_ISO:
                        map_load(maps, MAP_S, maps[MAP_MAIN]->neightbors[0]);
                        map_load(maps, MAP_W, maps[MAP_MAIN]->neightbors[1]);
                        map_load(maps, MAP_N, maps[MAP_MAIN]->neightbors[2]);
                        map_load(maps, MAP_E, maps[MAP_MAIN]->neightbors[3]);
                    break;
                    case TNG_TYPE_HEXV:
                        map_load(maps, MAP_SW, maps[MAP_MAIN]->neightbors[0]);
                        map_load(maps, MAP_W, maps[MAP_MAIN]->neightbors[1]);
                        map_load(maps, MAP_NW, maps[MAP_MAIN]->neightbors[2]);
                        map_load(maps, MAP_NE, maps[MAP_MAIN]->neightbors[3]);
                        map_load(maps, MAP_E, maps[MAP_MAIN]->neightbors[4]);
                        map_load(maps, MAP_SE, maps[MAP_MAIN]->neightbors[5]);
                    break;
                    case TNG_TYPE_HEXH:
                        map_load(maps, MAP_S, maps[MAP_MAIN]->neightbors[0]);
                        map_load(maps, MAP_SW, maps[MAP_MAIN]->neightbors[1]);
                        map_load(maps, MAP_NW, maps[MAP_MAIN]->neightbors[2]);
                        map_load(maps, MAP_N, maps[MAP_MAIN]->neightbors[3]);
                        map_load(maps, MAP_NE, maps[MAP_MAIN]->neightbors[4]);
                        map_load(maps, MAP_SE, maps[MAP_MAIN]->neightbors[5]);
                    break;
                }
            /* ortho and iso need 8, not 6, but got only 4, so load that additional 4 from the North and South neightbors */
            if(world.type == TNG_TYPE_ORTHO || world.type == TNG_TYPE_ISO) {
                switch(neightbor) {
                    case MAP_S:
                        map_load(maps, MAP_SW, maps[MAP_S]->neightbors[1]);
                        map_load(maps, MAP_SE, maps[MAP_S]->neightbors[3]);
                    break;
                    case MAP_N:
                        map_load(maps, MAP_NW, maps[MAP_N]->neightbors[1]);
                        map_load(maps, MAP_NE, maps[MAP_N]->neightbors[3]);
                    break;
                }
            }
        } else
        if(verbose) printf("tngp: unable to load map id %u pos %u\r\n", map, neightbor);
    }
}

/**
 * Unload maps
 */
void map_unload(world_map_t **maps)
{
    int i;

    for(i = 0; i < 9; i++) {
        world_freemap(maps[i]);
        maps[i] = NULL;
    }
}

/**
 * Scroll maps
 * dx, dy: only their signs matters
 */
void map_scroll(world_map_t **maps, int dx, int dy)
{
    if(!dx && !dy) return;
    /* TODO: hexagonal scroll */
    /* move maps */
    if(dy < 0 && dx < 0) {
        world_freemap(maps[MAP_SW]); maps[MAP_SW] = NULL;
        world_freemap(maps[MAP_NE]); maps[MAP_NE] = NULL;
        world_freemap(maps[MAP_S]);
        world_freemap(maps[MAP_SE]);
        world_freemap(maps[MAP_E]);
        maps[MAP_E] = maps[MAP_N];
        maps[MAP_SE] = maps[MAP_MAIN];
        maps[MAP_MAIN] = maps[MAP_NW];
        maps[MAP_S] = maps[MAP_W];
        maps[MAP_NW] = NULL;
        maps[MAP_N] = NULL;
        maps[MAP_W] = NULL;
        if(maps[MAP_MAIN]) {
            map_load(maps, MAP_W, maps[MAP_MAIN]->neightbors[1]);
            map_load(maps, MAP_N, maps[MAP_MAIN]->neightbors[2]);
        }
    } else
    if(dy > 0 && dx > 0) {
        world_freemap(maps[MAP_SW]); maps[MAP_SW] = NULL;
        world_freemap(maps[MAP_NE]); maps[MAP_NE] = NULL;
        world_freemap(maps[MAP_NW]);
        world_freemap(maps[MAP_N]);
        world_freemap(maps[MAP_W]);
        maps[MAP_N] = maps[MAP_E];
        maps[MAP_NW] = maps[MAP_MAIN];
        maps[MAP_MAIN] = maps[MAP_SE];
        maps[MAP_W] = maps[MAP_S];
        maps[MAP_SE] = NULL;
        maps[MAP_S] = NULL;
        maps[MAP_E] = NULL;
        if(maps[MAP_MAIN]) {
            map_load(maps, MAP_S, maps[MAP_MAIN]->neightbors[0]);
            map_load(maps, MAP_E, maps[MAP_MAIN]->neightbors[3]);
        }
    } else
    if(dy < 0 && dx > 0) {
        world_freemap(maps[MAP_NW]); maps[MAP_NW] = NULL;
        world_freemap(maps[MAP_SE]); maps[MAP_SE] = NULL;
        world_freemap(maps[MAP_S]);
        world_freemap(maps[MAP_SW]);
        world_freemap(maps[MAP_W]);
        maps[MAP_W] = maps[MAP_N];
        maps[MAP_SW] = maps[MAP_MAIN];
        maps[MAP_MAIN] = maps[MAP_NE];
        maps[MAP_S] = maps[MAP_E];
        maps[MAP_NE] = NULL;
        maps[MAP_N] = NULL;
        maps[MAP_E] = NULL;
        if(maps[MAP_MAIN]) {
            map_load(maps, MAP_W, maps[MAP_MAIN]->neightbors[1]);
            map_load(maps, MAP_N, maps[MAP_MAIN]->neightbors[2]);
        }
    } else
    if(dy > 0 && dx < 0) {
        world_freemap(maps[MAP_NW]); maps[MAP_NW] = NULL;
        world_freemap(maps[MAP_SE]); maps[MAP_SE] = NULL;
        world_freemap(maps[MAP_NE]);
        world_freemap(maps[MAP_N]);
        world_freemap(maps[MAP_E]);
        maps[MAP_N] = maps[MAP_W];
        maps[MAP_NW] = maps[MAP_MAIN];
        maps[MAP_MAIN] = maps[MAP_SW];
        maps[MAP_E] = maps[MAP_S];
        maps[MAP_SW] = NULL;
        maps[MAP_S] = NULL;
        maps[MAP_W] = NULL;
        if(maps[MAP_MAIN]) {
            map_load(maps, MAP_S, maps[MAP_MAIN]->neightbors[0]);
            map_load(maps, MAP_W, maps[MAP_MAIN]->neightbors[1]);
        }
    } else
    if(dy > 0 && !dx) {
        world_freemap(maps[MAP_NW]);
        world_freemap(maps[MAP_N]);
        world_freemap(maps[MAP_NE]);
        maps[MAP_NW] = maps[MAP_W];
        maps[MAP_N] = maps[MAP_MAIN];
        maps[MAP_NE] = maps[MAP_E];
        maps[MAP_W] = maps[MAP_SW];
        maps[MAP_MAIN] = maps[MAP_S];
        maps[MAP_E] = maps[MAP_SE];
        maps[MAP_SW] = NULL;
        maps[MAP_S] = NULL;
        maps[MAP_SE] = NULL;
        if(maps[MAP_MAIN]) {
            map_load(maps, MAP_S, maps[MAP_MAIN]->neightbors[0]);
        }
    } else
    if(dy < 0 && !dx) {
        world_freemap(maps[MAP_SW]);
        world_freemap(maps[MAP_S]);
        world_freemap(maps[MAP_SE]);
        maps[MAP_SW] = maps[MAP_W];
        maps[MAP_S] = maps[MAP_MAIN];
        maps[MAP_SE] = maps[MAP_E];
        maps[MAP_W] = maps[MAP_NW];
        maps[MAP_MAIN] = maps[MAP_N];
        maps[MAP_E] = maps[MAP_NE];
        maps[MAP_NW] = NULL;
        maps[MAP_N] = NULL;
        maps[MAP_NE] = NULL;
        if(maps[MAP_MAIN]) {
            map_load(maps, MAP_N, maps[MAP_MAIN]->neightbors[2]);
        }
    } else
    if(!dy && dx < 0) {
        world_freemap(maps[MAP_NE]);
        world_freemap(maps[MAP_E]);
        world_freemap(maps[MAP_SE]);
        maps[MAP_NE] = maps[MAP_N];
        maps[MAP_E] = maps[MAP_MAIN];
        maps[MAP_SE] = maps[MAP_S];
        maps[MAP_N] = maps[MAP_NW];
        maps[MAP_MAIN] = maps[MAP_W];
        maps[MAP_S] = maps[MAP_SW];
        maps[MAP_NW] = NULL;
        maps[MAP_W] = NULL;
        maps[MAP_SW] = NULL;
        if(maps[MAP_MAIN]) {
            map_load(maps, MAP_W, maps[MAP_MAIN]->neightbors[1]);
        }
    } else
    if(!dy && dx > 0) {
        world_freemap(maps[MAP_NW]);
        world_freemap(maps[MAP_W]);
        world_freemap(maps[MAP_SW]);
        maps[MAP_NW] = maps[MAP_N];
        maps[MAP_W] = maps[MAP_MAIN];
        maps[MAP_SW] = maps[MAP_S];
        maps[MAP_N] = maps[MAP_NE];
        maps[MAP_MAIN] = maps[MAP_E];
        maps[MAP_S] = maps[MAP_SE];
        maps[MAP_NE] = NULL;
        maps[MAP_E] = NULL;
        maps[MAP_SE] = NULL;
        if(maps[MAP_MAIN]) {
            map_load(maps, MAP_E, maps[MAP_MAIN]->neightbors[3]);
        }
    }
}

/**
 * Return if map is a neightbor (server only)
 */
int map_srv_neighbor(int map1, int map2)
{
    int i;

    if(!world.mapngh || map1 < 0 || map1 >= world.nummap || map2 < 0 || map2 >= world.nummap) return -1;
    i = map1 * world.numngh;
    switch(world.type) {
        case TNG_TYPE_ORTHO:
        case TNG_TYPE_ISO:
            if(world.mapngh[i + 0] == map2) return MAP_S; else
            if(world.mapngh[i + 1] == map2) return MAP_W; else
            if(world.mapngh[i + 2] == map2) return MAP_N; else
            if(world.mapngh[i + 3] == map2) return MAP_E; else
            /* ortho and iso have 4 neightbors, not 6, so get the additional 4 from the North and South neightbors */
            if(world.mapngh[i + 0] != -1 && world.mapngh[world.mapngh[i + 0] * world.numngh + 1] == map2) return MAP_SW; else
            if(world.mapngh[i + 0] != -1 && world.mapngh[world.mapngh[i + 0] * world.numngh + 3] == map2) return MAP_SE; else
            if(world.mapngh[i + 2] != -1 && world.mapngh[world.mapngh[i + 2] * world.numngh + 1] == map2) return MAP_NW; else
            if(world.mapngh[i + 2] != -1 && world.mapngh[world.mapngh[i + 2] * world.numngh + 3] == map2) return MAP_NE;
        break;
        case TNG_TYPE_HEXV:
            if(world.mapngh[i + 0] == map2) return MAP_SW; else
            if(world.mapngh[i + 1] == map2) return MAP_W; else
            if(world.mapngh[i + 2] == map2) return MAP_NW; else
            if(world.mapngh[i + 3] == map2) return MAP_NE; else
            if(world.mapngh[i + 4] == map2) return MAP_E; else
            if(world.mapngh[i + 5] == map2) return MAP_SE;
        break;
        case TNG_TYPE_HEXH:
            if(world.mapngh[i + 0] == map2) return MAP_S; else
            if(world.mapngh[i + 1] == map2) return MAP_SW; else
            if(world.mapngh[i + 2] == map2) return MAP_NW; else
            if(world.mapngh[i + 3] == map2) return MAP_N; else
            if(world.mapngh[i + 4] == map2) return MAP_NE; else
            if(world.mapngh[i + 5] == map2) return MAP_SE;
        break;
    }
    return -1;
}

/**
 * Return true if distance is small between two entities (server only)
 */
int map_srv_dist(world_entity_t *this, world_entity_t *ent)
{
    int tx, ty, ex, ey, i;

    if(!this || !ent) return 0;
    map_map2tile(this->x, this->y, &tx, &ty);
    map_map2tile(ent->x, ent->y, &ex, &ey);
    ex -= tx; ey -= ty;
    /* add displacement if they are on neightboring maps */
    if(this->map != ent->map) {
        if(!world.mapngh) return 0;
        i = this->map * world.numngh;
        switch(world.type) {
            case TNG_TYPE_ORTHO:
            case TNG_TYPE_ISO:
                if(world.mapngh[i + 0] == ent->map) { ey += world.mapsize; } else   /* South */
                if(world.mapngh[i + 1] == ent->map) { ex -= world.mapsize; } else   /* West */
                if(world.mapngh[i + 2] == ent->map) { ey -= world.mapsize; } else   /* North */
                if(world.mapngh[i + 3] == ent->map) { ex += world.mapsize; } else   /* East */
                if(world.mapngh[i + 0] != -1 && world.mapngh[world.mapngh[i + 0] * world.numngh + 1] == ent->map)
                    { ey += world.mapsize; ex -= world.mapsize; } else
                if(world.mapngh[i + 0] != -1 && world.mapngh[world.mapngh[i + 0] * world.numngh + 3] == ent->map)
                    { ey += world.mapsize; ex += world.mapsize; } else
                if(world.mapngh[i + 2] != -1 && world.mapngh[world.mapngh[i + 2] * world.numngh + 1] == ent->map)
                    { ey -= world.mapsize; ex -= world.mapsize; } else
                if(world.mapngh[i + 2] != -1 && world.mapngh[world.mapngh[i + 2] * world.numngh + 3] == ent->map)
                    { ey -= world.mapsize; ex += world.mapsize; } else
                    return 0;
            break;
            case TNG_TYPE_HEXV:
                /* TODO: distance on hexagonal maps */
                if(world.mapngh[i + 0] == ent->map) {  } else   /* South-West */
                if(world.mapngh[i + 1] == ent->map) { ex -= world.mapsize; } else   /* West */
                if(world.mapngh[i + 2] == ent->map) {  } else   /* North-West */
                if(world.mapngh[i + 3] == ent->map) {  } else   /* North-East */
                if(world.mapngh[i + 4] == ent->map) { ex += world.mapsize; } else   /* East */
                if(world.mapngh[i + 5] == ent->map) {  } else   /* South-East */
                    return 0;
            break;
            case TNG_TYPE_HEXH:
                if(world.mapngh[i + 0] == ent->map) { ey += world.mapsize; } else   /* South */
                if(world.mapngh[i + 1] == ent->map) {  } else   /* South-West */
                if(world.mapngh[i + 2] == ent->map) {  } else   /* North-West */
                if(world.mapngh[i + 3] == ent->map) { ey -= world.mapsize; } else   /* North */
                if(world.mapngh[i + 4] == ent->map) {  } else   /* Noth-East */
                if(world.mapngh[i + 5] == ent->map) {  } else   /* South-East */
                    return 0;
                return 0;
            break;
        }
    }
    return ex*ex + ey*ey < MAP_DISTANCE*MAP_DISTANCE;
}
