/*
 * tngp/cutscn.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 Cutscene player
 *
 */

#include "tngp.h"
#include "theora.h"

int cutscn_ret = TAB_GAME;
int cutscn_idx = -1, cutscn_inloop = 0;
int cutscn_a, *cutscn_flag = NULL;
int cutscn_img, cutscn_spc, cutscn_sub;
uint32_t cutscn_fadeb = 0;
world_scene_t cutscn_scene = { 0 };
/* movies */
static SDL_Texture *movie = NULL;
static SDL_Rect dstm, cutscn_dst;

typedef struct {
    SDL_Texture *txt;
    SDL_Rect src, dst;
} cutscn_txt_t;
cutscn_txt_t *cutscn_imgs = NULL, *cutscn_subs = NULL;

typedef struct {
    char type;                      /* command type, see CMD_* */
    int par1, par2;                 /* parameters */
} cutscn_npccmd_t;

typedef struct {
    int dx, dy;                     /* delta movement */
    int nx, ny;                     /* new coordinate to move to */
    int t, c;                       /* total time for movement, current movement */
    uint32_t nm, la;                /* next command timestamp, last animation timestamp */
    int anim, f;                    /* animation to play, current frame */
    int numcmd, idxcmd;             /* number of commands and current command index */
    cutscn_npccmd_t *cmd;           /* commands to execute */
    int speed;                      /* speed */
    int light;                      /* light radious */
} cutscn_npc_t;
cutscn_npc_t *cutscn_actors = NULL;
world_npc_t *cutscn_npcs = NULL;
int cutscn_numnpc = 0;

/**
 * Free actors
 */
void cutscn_freeactors()
{
    int i;

    if(cutscn_actors) {
        for(i = 0; i < cutscn_numnpc; i++) {
            if(cutscn_actors[i].cmd) free(cutscn_actors[i].cmd);
        }
        free(cutscn_actors); cutscn_actors = NULL;
    }
    if(cutscn_npcs) { free(cutscn_npcs); cutscn_npcs = NULL; }
    cutscn_numnpc = 0;
}

/**
 * Place an NPC
 */
int cutscn_placenpc(int idx, int x, int y, int d)
{
    if(cutscn_idx == -1 || idx < 0 || idx >= world.numnpc) return -1;
    cutscn_actors = (cutscn_npc_t*)realloc(cutscn_actors, (cutscn_numnpc + 1) * sizeof(cutscn_npc_t));
    if(!cutscn_actors) { cutscn_freeactors(); return -1; }
    memset(&cutscn_actors[cutscn_numnpc], 0, sizeof(cutscn_npc_t));
    cutscn_npcs = (world_npc_t*)realloc(cutscn_npcs, (cutscn_numnpc + 1) * sizeof(world_npc_t));
    if(!cutscn_npcs) { cutscn_freeactors(); return -1; }
    memset(&cutscn_npcs[cutscn_numnpc], 0, sizeof(world_npc_t));
    cutscn_npcs[cutscn_numnpc].type = idx;
    cutscn_npcs[cutscn_numnpc].data.x = x;
    cutscn_npcs[cutscn_numnpc].data.y = y;
    cutscn_npcs[cutscn_numnpc].data.d = d;
    cutscn_numnpc++;
    return cutscn_numnpc - 1;
}

/**
 * Initialize cutscene to play
 */
void cutscn_init(int idx, int *flag)
{
    if(idx >= 0 && idx < world.numcut) {
        cutscn_flag = flag; if(flag) *flag = 1;
        cutscn_idx = idx; cutscn_inloop = 0;
        /* we can't start the cutscene if we're on the game tab, we have to fade that out first */
        if(tab == TAB_GAME) game_fadeout();
        else cutscn_start();
    }
}
void cutscn_start()
{
    world_cutscn_t *cutscn = NULL;
    ssfn_buf_t buf = { 0 };
    ssfn_t *font;
    uint8_t *ptr;
    char *str;
    int i, j, o, l, w, h, b;

    if(cutscn_idx == -1) return;
    cutscn_ret = tab; tab = TAB_CUTSCN;
    cutscn_fadeb = cutscn_img = cutscn_spc = cutscn_sub = 0;
    memset(&cutscn_dst, 0, sizeof(cutscn_dst));
    memset(&cutscn_scene, 0, sizeof(cutscn_scene));
    cutscn_scene.list = (int*)main_alloc(world.mapsize * world.mapsize * sizeof(int));
    cutscn = &world.cutscns[cutscn_idx];
    o = cutscn->pomov; l = cutscn->plmov;
    theora_start(&theora_ctx,
        o >= 0 && o < world.nummovie ? &world.movie[o] : NULL, l >= 0 && l < world.nummovie ? &world.movie[l] : NULL);
    if(theora_ctx.hasVideo) {
        movie = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, theora_ctx.w, theora_ctx.h);
        if(movie) {
            SDL_SetTextureBlendMode(movie, SDL_BLENDMODE_BLEND);
            /* planar YUV is green by default, let's make it black */
            SDL_LockTexture(movie, NULL, (void**)&ptr, &l);
            memset(ptr + (theora_ctx.w * theora_ctx.h), 0x80, (theora_ctx.w / 2) * theora_ctx.h);
            SDL_UnlockTexture(movie);
            ui_fit(win_w, win_h, theora_ctx.w, theora_ctx.h, &dstm.w, &dstm.h);
            dstm.x = (win_w - dstm.w) / 2; dstm.y = (win_h - dstm.h) / 2;
            cutscn_dst.w = dstm.w; cutscn_dst.h = dstm.h;
        }
    }
    if(cutscn->numimg && cutscn->img) {
        cutscn_imgs = (cutscn_txt_t*)main_alloc((cutscn->numimg + 1) * sizeof(cutscn_txt_t));
        for(i = 0; i < cutscn->numimg; i++)
            if(cutscn->img[i].color) {
                cutscn_imgs[i].txt = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, 1, 1);
                if(cutscn_imgs[i].txt) {
                    SDL_SetTextureBlendMode(cutscn_imgs[i].txt, SDL_BLENDMODE_BLEND);
                    SDL_LockTexture(cutscn_imgs[i].txt, NULL, (void**)&ptr, &l);
                    memcpy(ptr, &cutscn->img[i].color, 4);
                    SDL_UnlockTexture(cutscn_imgs[i].txt);
                    cutscn_imgs[i].src.w = cutscn_imgs[i].src.h = 1;
                    cutscn_imgs[i].dst.w = win_w; cutscn_imgs[i].dst.h = win_h;
                } else break;
            } else {
                cutscn_imgs[i].txt = spr_getbg(cutscn->img[i].spr.idx, &cutscn_imgs[i].src.w, &cutscn_imgs[i].src.h);
                if(cutscn_imgs[i].txt) {
                    ui_fit(win_w, win_h, cutscn_imgs[i].src.w, cutscn_imgs[i].src.h, &cutscn_imgs[i].dst.w, &cutscn_imgs[i].dst.h);
                    cutscn_imgs[i].dst.x = (win_w - cutscn_imgs[i].dst.w) / 2;
                    cutscn_imgs[i].dst.y = (win_h - cutscn_imgs[i].dst.h) / 2;
                    if(cutscn_imgs[i].dst.w > cutscn_dst.w) cutscn_dst.w = cutscn_imgs[i].dst.w;
                    if(cutscn_imgs[i].dst.h > cutscn_dst.h) cutscn_dst.h = cutscn_imgs[i].dst.h;
                } else break;
            }
    }
    if(!cutscn_dst.w || !cutscn_dst.h) { cutscn_dst.w = win_w; cutscn_dst.h = win_h; }
    else { cutscn_dst.x = (win_w - cutscn_dst.w) / 2; cutscn_dst.y = (win_h - cutscn_dst.h) / 2; }
    if(cutscn->numsub && cutscn->sub && world.text) {
        cutscn_subs = (cutscn_txt_t*)main_alloc((cutscn->numsub + 1) * sizeof(cutscn_txt_t));
        for(i = 0; i < cutscn->numsub; i++) {
            if(cutscn->sub[i].color && cutscn->sub[i].txt >= 0 && cutscn->sub[i].txt < world.numtext) {
                str = world.text[cutscn->sub[i].txt];
                font = font_custom(&cutscn->sub[i].fnt);
                if(*str && ssfn_bbox(font, str, &w, &h, &l, &b) == SSFN_OK) {
                    buf.p = w * 4; buf.x = l; buf.y = b; buf.w = cutscn_subs[i].src.w = w; buf.h = cutscn_subs[i].src.h = h;
                    buf.fg = cutscn->sub[i].color;
                    cutscn_subs[i].txt = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, w, h);
                    if(cutscn_subs[i].txt) {
                        SDL_SetTextureBlendMode(cutscn_subs[i].txt, SDL_BLENDMODE_BLEND);
                        SDL_LockTexture(cutscn_subs[i].txt, NULL, (void**)&buf.ptr, &o);
                        while(*str && ((j = ssfn_render(font, &buf, str)) > 0 || j == SSFN_ERR_NOGLYPH)) str += (j < 1 ? 1 : j);
                        SDL_UnlockTexture(cutscn_subs[i].txt);
                        cutscn_subs[i].dst.x = cutscn_dst.x + cutscn_dst.w * cutscn->sub[i].x / 100;
                        cutscn_subs[i].dst.y = cutscn_dst.y + cutscn_dst.h * cutscn->sub[i].y / 100 - b;
                        switch(cutscn->sub[i].a) {
                            case 1: cutscn_subs[i].dst.x -= w / 2; break;
                            case 2: cutscn_subs[i].dst.x -= w; break;
                        }
                    }
                }
                ssfn_free(font); free(font);
            }
            if(!cutscn_subs[i].txt) break;
        }
    }
    media_open();
    o = cutscn->pomus; l = cutscn->plmus;
    media_playmus(
        o >= 0 && o < world.nummusic ? &world.music[o] : NULL, l >= 0 && l < world.nummusic ? &world.music[l] : NULL);
    if(theora_ctx.hasAudio)
        theora_callback(0);
    /* delibearetly not main_tick here */
    theora_ctx.baseticks = SDL_GetTicks();
}

/**
 * Free resources
 */
void cutscn_free()
{
    int i;

    media_stopmus(1);
    media_close();
    chooser_free();
    if(cutscn_imgs) {
        for(i = 0; cutscn_imgs[i].txt; i++)
            SDL_DestroyTexture(cutscn_imgs[i].txt);
        free(cutscn_imgs); cutscn_imgs = NULL;
    }
    if(cutscn_subs) {
        for(i = 0; cutscn_subs[i].txt; i++)
            SDL_DestroyTexture(cutscn_subs[i].txt);
        free(cutscn_subs); cutscn_subs = NULL;
    }
    if(cutscn_scene.list) { free(cutscn_scene.list); cutscn_scene.list = NULL; }
    cutscn_freemap();
    map_unload(cutscn_scene.maps);
    cutscn_freeactors();
    if(movie) { SDL_DestroyTexture(movie); movie = NULL; }
    cutscn_fadeb = 0;
    world.intro = cutscn_idx = -1;
    /* normally cutscene does not switch tab because it preserves the calling context, except for the intro */
    if(cutscn_ret == TAB_INTRO)
        main_switchtab(cutscn_ret);
    else {
        tab = cutscn_ret;
        if(tab == TAB_GAME) game_fadein();
    }
    cutscn_ret = -1;
    if(cutscn_flag) { *cutscn_flag = 0; cutscn_flag = NULL; }
}

/**
 * Exit cutscene
 */
void cutscn_exit()
{
    cutscn_inloop = 0;
    if(cutscn_idx == -1) return;
    if(cutscn_ret == TAB_STARTGAME) {
        media_fadeout(10);
        cutscn_fadeb = main_tick - 500;
    } else {
        media_fadeout(500);
        cutscn_fadeb = main_tick;
    }
}

/**
 * Run cutscne script
 */
void cutscn_run()
{
    if(cutscn_idx != -1 && world.cutscns[cutscn_idx].bc) {
        script_load(SCRIPT_CALLER(SCRIPT_CUTSCN, cutscn_idx), world.cutscns[cutscn_idx].bc, world.cutscns[cutscn_idx].bclen,
            world.cutscns[cutscn_idx].tng, NULL, NULL);
    }
}

/**
 * Load a map for cutscene background
 */
int cutscn_loadmap(int map, int x, int y, int daytime)
{
    if(cutscn_idx == -1) return 0;
    if(cutscn_scene.maps[MAP_MAIN] && cutscn_scene.maps[MAP_MAIN]->id == map) {
        cutscn_scene.tx = x; cutscn_scene.ty = y;
    } else {
        cutscn_scene.tx = x; cutscn_scene.cx = x; cutscn_scene.ty = x; cutscn_scene.cy = y;
        map_unload(cutscn_scene.maps);
        map_load(cutscn_scene.maps, MAP_MAIN, map);
        cutscn_setmap();
    }
    cutscn_scene.d = daytime * 3600;
    return 1;
}

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

    cutscn_freemap();
    if(cutscn_scene.maps[MAP_MAIN]) {
        if(cutscn_scene.maps[MAP_MAIN]->numprx > 0 && cutscn_scene.maps[MAP_MAIN]->prx) {
            cutscn_scene.numprx = cutscn_scene.maps[MAP_MAIN]->numprx;
            cutscn_scene.prx = (SDL_Texture**)main_alloc(cutscn_scene.numprx * sizeof(SDL_Texture*));
            cutscn_scene.prxdst = (SDL_Point*)main_alloc(cutscn_scene.numprx * sizeof(SDL_Point));
            for(i = 0; i < cutscn_scene.numprx; i++)
                cutscn_scene.prx[i] = spr_getbg(cutscn_scene.maps[MAP_MAIN]->prx[i].spr.idx,
                    &cutscn_scene.prxdst[i].x, &cutscn_scene.prxdst[i].y);
        }
    }
}

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

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

/**
 * Controller
 */
int cutscn_ctrl()
{
    if(cutscn_inloop && chooser_idx != -1) return chooser_ctrl();
    switch(event.type) {
        case SDL_KEYDOWN:
            ui_input_gp &= ~1;
            cutscn_exit();
        break;
        case SDL_CONTROLLERBUTTONUP:
        case SDL_MOUSEBUTTONUP:
            ui_input_gp &= ~1;
            cutscn_exit();
        break;
    }
    return 1;
}

/**
 * View layer
 */
void cutscn_view()
{
    uint32_t now = (main_tick - theora_ctx.baseticks) / 10;
    world_cutscn_t *cutscn = NULL;
    SDL_Rect src, dst;
    int i, a;

    if(!main_draw) return;
    if(cutscn_fadeb) { cutscn_a = 255 - ((main_tick - cutscn_fadeb) * 255 / 500); if(cutscn_a < 0) cutscn_a = 0; }
    else cutscn_a = 255;
    /* video */
    if(movie) {
        theora_produce(&theora_ctx);
        theora_video(&theora_ctx, movie);
        SDL_SetTextureAlphaMod(movie, cutscn_a);
        SDL_RenderCopy(renderer, movie, NULL, &dstm);
    }
    /* map */
    game_map(&cutscn_scene, now / 100, cutscn_a);

    /* dialog */
    if(dialog_idx != -1) {
        if(dialog_stop <= main_tick) dialog_free();
        else dialog_view();
    }

    if(cutscn_idx != -1) {
        cutscn = &world.cutscns[cutscn_idx];
        /* speech */
        if(cutscn_spc < cutscn->numspc && cutscn->spc && cutscn->spc[cutscn_spc].s <= now)
            media_playspc(cutscn->spc[cutscn_spc++].spc, 0, 0);
        /* slideshow */
        if(cutscn_imgs && cutscn_img < cutscn->numimg && cutscn->img && cutscn->img[cutscn_img].s <= now) {
            if(cutscn->img[cutscn_img].e <= now) cutscn_img++;
            for(i = cutscn_img; i < cutscn->numimg && cutscn_imgs[i].txt && cutscn->img[i].s <= now; i++) {
                if(now < cutscn->img[i].s || now >= cutscn->img[i].e) a = 0; else
                if(now >= cutscn->img[i].fi && now < cutscn->img[i].fo) a = 255; else
                if(now < cutscn->img[i].fi) a = ((main_tick - cutscn->img[i].s) * 255 / (cutscn->img[i].fi - cutscn->img[i].s + 1)); else
                if(now >= cutscn->img[i].fo) a = 255 - ((main_tick - cutscn->img[i].s) * 255 / (cutscn->img[i].fi - cutscn->img[i].s + 1));
                if(a < 0) a = 0;
                if(a > cutscn_a) a = cutscn_a;
                SDL_SetTextureAlphaMod(cutscn_imgs[i].txt, a);
                SDL_RenderCopy(renderer, cutscn_imgs[i].txt, &cutscn_imgs[i].src, &cutscn_imgs[i].dst);
            }
        }
        /* subtitles */
        if(cutscn_sub < cutscn->numsub && cutscn->sub && cutscn->sub[cutscn_sub].s <= now) {
            if(cutscn->sub[cutscn_sub].e <= now) cutscn_sub++;
            for(i = cutscn_sub; i < cutscn->numsub && cutscn_subs[i].txt && cutscn->sub[i].s <= now; i++) {
                if(now < cutscn->sub[i].s || now >= cutscn->sub[i].e) a = 0; else
                if(now >= cutscn->sub[i].fi && now < cutscn->sub[i].fo) a = 255; else
                if(now < cutscn->sub[i].fi) a = ((main_tick - cutscn->sub[i].s) * 255 / (cutscn->sub[i].fi - cutscn->sub[i].s + 1)); else
                if(now >= cutscn->sub[i].fo) a = 255 - ((main_tick - cutscn->sub[i].s) * 255 / (cutscn->sub[i].fi - cutscn->sub[i].s + 1));
                if(a < 0) a = 0;
                if(a > cutscn_a) a = cutscn_a;
                SDL_SetTextureAlphaMod(cutscn_subs[i].txt, a);
                SDL_RenderCopy(renderer, cutscn_subs[i].txt, &cutscn_subs[i].src, &cutscn_subs[i].dst);
            }
        }
    }
    if(world.intro != -1 && world.hdr.magic[0] == '#' && !world.hdr.enc) {
        if(world.gpl) { src.x = 120; src.w = 100; } else { src.x = 0; src.w = 120; }
        src.y = 158; dst.h = src.h = 42; dst.w = src.w;
        dst.x = 0; dst.y = win_h - src.h;
        SDL_RenderCopy(renderer, logo, &src, &dst);
    }
    if(cutscn_inloop && chooser_idx != -1)
        chooser_view(&cutscn_dst);
    /* do not use theora_playing() here, that checks video too, and we only need to check audio */
    if(cutscn_a < 1 || (!(!theora_ctx.stop && (!theora_ctx.done || theora_ctx.ahead != theora_ctx.atail)) && !Mix_PlayingMusic()))
        cutscn_free();
}
