/*
 * tngp/game.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 Main game
 *
 */

#include "tngp.h"

extern int hud_navsiz;
int game_fade = 0, game_a = 0;
uint32_t game_fadeb = 0;
int game_dx = 0, game_dy = 0, game_navx = 0, game_navy = 0;
world_scene_t game_scene = { 0 };

/**
 * Initialize game tab
 */
void game_init()
{
    game_free();
    media_open();
    memset(&game_scene, 0, sizeof(game_scene));
    if(world.hdr.magic[0] == '#')
        script_init();
    game_scene.list = (int*)main_alloc(9 * world.mapsize * world.mapsize * sizeof(int));
    game_a = 255;
    game_navx = game_navy = 0;
}

/**
 * Free resources
 */
void game_free()
{
    if(game_scene.list) { free(game_scene.list); game_scene.list = NULL; }
    game_freemap();
    map_unload(game_scene.maps);
    script_free();
    hud_free();
    media_close();
}

/**
 * Exit game tab
 */
void game_exit()
{
    if(world.hdr.magic[0] != '#') {
        client_send(9, NULL, 0);
        client_close();
    }
    game_fadeout();
}

/**
 * Fade out game
 */
void game_fadeout()
{
    media_fadeout(500);
    game_a = 255; game_fade = -1; game_fadeb = main_tick;
}

/**
 * Fade in game
 */
void game_fadein()
{
    media_open();
    game_a = 0; game_fade = 1; game_fadeb = main_tick;
}

/**
 * Load a map
 */
void game_loadmap(int map_id, int x, int y)
{
    if(game_scene.maps[MAP_MAIN] && game_scene.maps[MAP_MAIN]->id == map_id) {
        game_scene.tx = x; game_scene.ty = y;
    } else {
        game_scene.tx = game_scene.cx = x; game_scene.ty = game_scene.cy = y;
        map_load(game_scene.maps, MAP_MAIN, map_id);
        game_setmap();
    }
}

/**
 * Set the main map
 */
void game_setmap()
{
    int i;

    game_freemap();
    if(game_scene.maps[MAP_MAIN]) {
        if(game_scene.maps[MAP_MAIN]->numprx > 0 && game_scene.maps[MAP_MAIN]->prx) {
            game_scene.numprx = game_scene.maps[MAP_MAIN]->numprx;
            game_scene.prx = (SDL_Texture**)main_alloc(game_scene.numprx * sizeof(SDL_Texture*));
            game_scene.prxdst = (SDL_Point*)main_alloc(game_scene.numprx * sizeof(SDL_Point));
            for(i = 0; i < game_scene.numprx; i++)
                game_scene.prx[i] = spr_getbg(game_scene.maps[MAP_MAIN]->prx[i].spr.idx,
                    &game_scene.prxdst[i].x, &game_scene.prxdst[i].y);
        }
        if(game_scene.maps[MAP_MAIN]->bgmus >= 0 && game_scene.maps[MAP_MAIN]->bgmus < world.nummusic)
            media_playmus(NULL, &world.music[game_scene.maps[MAP_MAIN]->bgmus]);
        if(world.text && game_scene.maps[MAP_MAIN]->trname >= 0 && game_scene.maps[MAP_MAIN]->trname < world.numtext)
            hud_showtitle(world.text[game_scene.maps[MAP_MAIN]->trname]);
    }
}

/**
 * Free additional main map resources
 */
void game_freemap()
{
    int i;

    if(game_scene.prxdst) { free(game_scene.prxdst); game_scene.prxdst = NULL; }
    if(game_scene.prx) {
        for(i = 0; i < game_scene.numprx; i++)
            if(game_scene.prx[i]) SDL_DestroyTexture(game_scene.prx[i]);
        free(game_scene.prx); game_scene.prx = NULL;
    }
    game_scene.numprx = 0;
}

/**
 * Use an item
 */
void game_useitem(int idx, int belt)
{
    if(!world.players || !world.objects || idx < 0 || idx >= world.numspr[SPR_TILE] || !world.objects[idx]) return;
    printf("use item %u.%04x belt index %d\n", SPR_TILE, idx, belt >= 0 ? (int)world.players[0].belt[belt] : -1);
}

/**
 * Move player
 */
void game_moveplayer(int dx, int dy)
{
    int x, y;

    if(world.players) {
        world.players[0].data.x += dx;
        world.players[0].data.y += dy;
        /* check if player wandered to neightboring map */
        map_map2tile(world.players[0].data.x, world.players[0].data.y, &x, &y);
        if((x < 0 || x >= world.mapsize) || (y < 0 || y >= world.mapsize)) {
            printf("need scroll %d %d\n",x < 0 ? -1 : (x >= world.mapsize ? 1 : 0), y < 0 ? -1 : (y >= world.mapsize ? 1 : 0));
            game_freemap();
            map_scroll(game_scene.maps, x < 0 ? -1 : (x >= world.mapsize ? 1 : 0), y < 0 ? -1 : (y >= world.mapsize ? 1 : 0));
            game_setmap();
            world.players[0].data.x += x < 0 ? map_w : (x >= world.mapsize ? -map_w : 0);
            world.players[0].data.y += y < 0 ? map_h : (y >= world.mapsize ? -map_h : 0);
            world.players[0].data.map = game_scene.maps[MAP_MAIN] ? game_scene.maps[MAP_MAIN]->id : 0;

            map_map2tile(world.players[0].data.x, world.players[0].data.y, &x, &y);
            printf("new map %d x %d y %d\n",world.players[0].data.map,x,y);
        }
    }
    game_scene.tx = world.players[0].data.x; game_scene.ty = world.players[0].data.y;
}

/**
 * Run game scripts
 */
void game_run()
{
    int i, ret;

    if(tab == TAB_STARTGAME) {
        /* the run once cron script is for startup */
        for(i = 0, ret = SCRIPT_AGAIN; i < WORLD_NUMCRON; i++)
            if(!world.cron[i].value) {
                ret = script_load(SCRIPT_CALLER(SCRIPT_STARTUP, i), world.cron[i].bc, world.cron[i].bclen, world.cron[i].tng,
                    world.players ? &world.players[0].data : NULL, NULL);
                break;
            }
        if(ret == SCRIPT_EXIT) { main_switchtab(world.hasmenu ? TAB_MAINMENU : TAB_CREDITS); return; }
        if(ret != SCRIPT_AGAIN) { main_switchtab(TAB_GAME); return; }
    } else
    if(world.hdr.magic[0] == '#')
        script_crons();
}

/**
 * Controller
 */
int game_ctrl()
{
    int ret, i;

    if(game_a != 255) return 1;
    /* reverse order as in game_view() */
    if(tab == TAB_GAME && (ret = hud_ctrl()) != -1) return ret;
    if(dialog_idx != -1) { dialog_ctrl(); return 1; }
    if(chooser_idx != -1) { chooser_ctrl(); return 1; }

    switch(event.type) {
        case SDL_KEYDOWN:
            ui_input_gp &= ~1;
            switch(event.key.keysym.sym) {
                case SDLK_AC_BACK:
                case SDLK_F10:
                case SDLK_BACKSPACE:
                case SDLK_ESCAPE:
                    if(tab == TAB_STARTGAME) { main_switchtab(TAB_GAME); return 1; }
                    return 0;
                case SDLK_UP: game_dy = -10; break;
                case SDLK_DOWN: game_dy = 10; break;
                case SDLK_LEFT: game_dx = -10; break;
                case SDLK_RIGHT: game_dx = 10; break;
            }
        break;
        case SDL_KEYUP:
            game_dx = game_dy = 0;
        break;
        case SDL_MOUSEMOTION:
            game_dx = game_dy = 0;
            if(touch && game_navx > 0 && game_navy > 0) {
                i = hud_navsiz / 6;
                if(event.motion.x < game_navx - i) game_dx = -10;
                if(event.motion.x > game_navx + i) game_dx = 10;
                if(event.motion.y < game_navy - i) game_dy = -10;
                if(event.motion.y > game_navy + i) game_dy = 10;
            }
        break;
        case SDL_MOUSEBUTTONDOWN:
            ui_input_gp &= ~1;
            if(touch) {
                if(event.button.x <= hud_navsiz && event.button.y >= win_h - hud_navsiz) {
                    game_navx = event.button.x;
                    game_navy = event.button.y;
                }
            }
        break;
        case SDL_MOUSEBUTTONUP:
            ui_input_gp &= ~1;
            game_dx = game_dy = game_navx = game_navy = 0;
        break;
    }
    return 1;
}

/**
 * View layer
 */
void game_view()
{
    SDL_Rect dst = { 0 };

    if(game_fade) {
        if(game_fade > 0) {
            game_a = (main_tick - game_fadeb) * 255 / 500;
            if(game_a >= 255) { game_fade = 0; game_a = 255; }
        } else {
            game_a = 255 - ((main_tick - game_fadeb) * 255 / 500);
            if(game_a <= 0) {
                game_fade = game_a = 0;
                if(cutscn_idx != -1) { cutscn_start(); }
                else { main_switchtab(world.hasmenu ? TAB_MAINMENU : TAB_CREDITS); }
                return;
            }
        }
    }
    if(!main_draw) return;
    game_moveplayer(game_dx, game_dy);
    game_scene.d = main_timetotal;
    game_scene.numnpc = world.numnpcs;
    game_scene.npc = world.npcs;
    game_map(&game_scene, (main_tick - main_start) / 1000, game_a);
    if(!main_takescrshot) {
        dst.w = win_w; dst.h = win_h; chooser_view(&dst);
        dialog_view();
        alert_view();
    }
    if(chooser_idx == -1)
        hud_view();
}

/**
 * Render parallaxes
 */
void game_parallax(world_scene_t *scene, int bg, int a)
{
    world_mapprx_t *prx;
    SDL_Rect src, dst;
    int i;

    if(!scene || !scene->prx || !scene->numprx || !scene->maps[MAP_MAIN] || !scene->maps[MAP_MAIN]->prx) return;
    prx = scene->maps[MAP_MAIN]->prx;
    for(i = 0; i < scene->numprx; i++)
        if(scene->prx[i] && prx[i].type == bg) {
            src.w = dst.w = scene->prxdst[i].x; src.x = 0;
            src.h = dst.h = scene->prxdst[i].y; src.y = 0;
            if(prx[i].last + prx[i].t * 10 < main_tick) {
                prx[i].last = main_tick;
                prx[i].x += prx[i].sx;
                prx[i].y += prx[i].sy;
                if(prx[i].x + dst.w < 0 && prx[i].sx < 0) prx[i].x += 2 * dst.w;
                if(prx[i].x > win_w && prx[i].sx > 0) prx[i].x -= 2 * dst.w;
                if(prx[i].y + dst.h < 0 && prx[i].sy < 0) prx[i].y += 2 * dst.h;
                if(prx[i].y > win_h && prx[i].sy > 0) prx[i].y -= 2 * dst.h;
            }
            SDL_SetTextureAlphaMod(scene->prx[i], a);
            for(dst.y = -(dst.h - (prx[i].y + scene->cy * prx[i].dy) % dst.h); dst.y < win_h; dst.y += dst.h)
                for(dst.x = -(dst.w - (prx[i].x + scene->cx * prx[i].dx) % dst.w); dst.x < win_w; dst.x += dst.w)
                    SDL_RenderCopy(renderer, scene->prx[i], &src, &dst);
        }
}

/**
 * Render one tile
 * lyr: layer, one of MAPS_LYR_*
 * midx: map index 0..8, one of MAP_MAIN, MAP_S, MAP_W, MAP_N etc.
 * o: offset to layer index (lyr * world.mapsize * world.mapsize)
 * tx, ty: tile coordinates
 * sx, sy: screen coordinates
 * a: alpha
 */
void game_rendertile(world_scene_t *scene, int lyr, int midx, int o, int tx, int ty, int sx, int sy, int a)
{
    world_sprite_t *spr = NULL;
    world_map_t *map = scene->maps[midx];
    int ti = ty * world.mapsize + tx;

    if(lyr != MAPS_LYR_OBJECTS) {
        /* simple case, just a tile */
        spr = spr_get(SPR_TILE, map->tiles[o + ti], DIR_SO);
    } else {
        /* on objects layer, we have to check modified layer and display character sprites as well */
        if(scene != &cutscn_scene && world.objlyr && world.objlyr[map->id])
            spr = spr_get(SPR_TILE, world.objlyr[map->id][ti], DIR_SO);
        else
            spr = spr_get(SPR_TILE, map->tiles[o + ti], DIR_SO);
    }
    if(spr)
        spr_render(sx - spr->w / 2, sy - spr->h, spr, ui_anim(spr->t, spr->f, -1), 0, spr->w, spr->h, a);
}

/**
 * View map
 */
void game_map(world_scene_t *scene, int d, int a)
{
    int i, j, k, l, o, ms = 3 * world.mapsize, m2 = ms * 2, mask = world.mapsize - 1;
    double dx, dy, force, len;
    int midx, w, h;

    (void)d;
    if(!scene || !scene->maps[MAP_MAIN]) return;

    /* FIXME: find good looking elastic parameters and eliminate sqrt() */
    scene->cx = scene->tx; scene->cy = scene->ty;
    /* the map should lag behind a bit as player moves, tx, ty: target coordinates, cx, cy: current coordinates */
    if(scene->cx != scene->tx || scene->cy != scene->ty) {
        dx = scene->tx - scene->cx; dy = scene->ty - scene->cy;
        len = sqrt(dx*dx + dy*dy);
        if(len < 1.0) { scene->cx = scene->tx; scene->cy = scene->ty; }
        else {
            force = 1 * (len - (double)world.tilew);
            scene->cx += force * dx / len;
            scene->cy += force * dy / len;
        }
    }

    w = win_w / 2 - scene->cx;
    h = win_h / 2 - scene->cy;

    for(i = 0, j = 9 * map_mm; i < j; i++) scene->list[i] = -1;

    game_parallax(scene, 1, a);

    for(k = MAPS_LYR_GRD1, o = 0; k <= MAPS_LYR_ROOF4; k++, o += map_mm) {
        if(k == MAPS_LYR_NPCS || k == MAPS_LYR_SPWN) continue;
        switch(world.hdr.type) {
            case TNG_TYPE_ORTHO:
                for(j = 0; j < ms; j++)
                    for(i = 0; i < ms; i++) {
                        midx = (j >> world.hdr.mapsize) * 3 + (i >> world.hdr.mapsize);
                        if(!scene->maps[midx] || !scene->maps[midx]->tiles) continue;
                        game_rendertile(scene, k, midx, o, i & mask, j & mask,
                            (i - world.mapsize) * world.hdr.tilew + w,
                            (j - world.mapsize + 1) * world.hdr.tileh + h, a);
                    }
            break;
            case TNG_TYPE_ISO:
                for(j = 0; j < m2; j++)
                    for(i = 0; i < ms; i++) {
                        l = j - i;
                        midx = (l >> world.hdr.mapsize) * 3 + (i >> world.hdr.mapsize);
                        if(l < 0 || l >= ms || !scene->maps[midx] || !scene->maps[midx]->tiles) continue;
                        game_rendertile(scene, k, midx, o, i & mask, l & mask,
                            ((i - world.mapsize) - (l - world.mapsize)) * map_halftw + w,
                            ((i - world.mapsize) + (l - world.mapsize)) * map_halfth + world.hdr.tileh + h, a);
                    }
            break;
            /* TODO: hexagonal grids */
            case TNG_TYPE_HEXV: break;
            case TNG_TYPE_HEXH: break;
        }
    }

    game_parallax(scene, 0, a);
}
