/*
Copyright (C) 1994-1995  Apogee Software, Ltd.
Copyright (C) 2002-2015  icculus.org, GNU/Linux port
Copyright (C) 2017-2018  Steven LeVesque

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 2
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.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

#include "cin_glob.h"
#include "cin_util.h"
#include "cin_def.h"
#include "cin_main.h"
#include "f_scale.h"
#include "watcom.h"
#include "lumpy.h"
#include "w_wad.h"
#include "z_zone.h"
#include <string.h>

#include "modexlib.h"

static int cin_sprtopoffset;
static int cin_invscale;

void DrawFadeout ( void );
void DrawBlankScreen ( void );
void DrawClearBuffer ( void );

/*
===============
=
= SpawnCinematicFlic
=
===============
*/

flicevent * SpawnCinematicFlic ( char * name, boolean loop, boolean usefile )
{
    flicevent * flic;

    flic = SafeMalloc ( sizeof(flicevent) );

    // copy name of flic

    strcpy ( flic->name, name );

    flic->loop=loop;

    flic->usefile=usefile;

    return flic;
}

/*
===============
=
= SpawnCinematicSprite
=
===============
*/

spriteevent * SpawnCinematicSprite ( char * name,
                                     int duration,
                                     int numframes,
                                     int framedelay,
                                     int x,
                                     int y,
                                     int scale,
                                     int endx,
                                     int endy,
                                     int endscale
                                   )
{
    spriteevent * sprite;

    sprite = SafeMalloc ( sizeof (spriteevent) );

    // copy name of sprite

    strcpy ( sprite->name, name );

    // copy rest of sprite information

    sprite->duration = duration;
    sprite->numframes = numframes;
    sprite->framedelay = framedelay;
    sprite->frame=0;
    sprite->frametime=framedelay;

    sprite->x=x << FRACTIONBITS;
    sprite->y=y << FRACTIONBITS;

//   sprite->y+=(p->width-p->height)<<(FRACTIONBITS-1);

    sprite->scale=scale << FRACTIONBITS;
    sprite->dx= ( (endx-x) << FRACTIONBITS ) / duration;
    sprite->dy= ( (endy-y) << FRACTIONBITS ) / duration;
    sprite->dscale= ( (endscale-scale) << FRACTIONBITS ) / duration;

    return sprite;
}

/*
===============
=
= SpawnCinematicBack
=
===============
*/

backevent * SpawnCinematicBack ( char * name,
                                 int duration,
                                 int width,
                                 int startx,
                                 int endx,
                                 int yoffset
                               )
{
    backevent * back;

    back = SafeMalloc ( sizeof (backevent) );

    // copy name of back

    strcpy ( back->name, name );

    // copy rest of back information

    back->duration = duration;
    back->backdropwidth = width;
    back->dx = ((endx-startx) << FRACTIONBITS)/duration;
    back->currentoffset = startx << FRACTIONBITS;
    back->yoffset=yoffset;

    return back;
}

/*
===============
=
= SpawnCinematicMultiBack
=
===============
*/

backevent * SpawnCinematicMultiBack ( char * name,
                                      char * name2,
                                      int duration,
                                      int startx,
                                      int endx,
                                      int yoffset
                                    )
{
    backevent * back;
    lpic_t * pic1;
    lpic_t * pic2;

    pic1=(lpic_t *)W_CacheLumpName(name,PU_CACHE, Cvt_lpic_t, 1);
    pic2=(lpic_t *)W_CacheLumpName(name2,PU_CACHE, Cvt_lpic_t, 1);

    back = SafeMalloc ( sizeof (backevent) );

    // copy name of back

    strcpy ( back->name, name );

    // copy rest of back information

    back->duration = duration;
    back->dx = ((endx-startx) << FRACTIONBITS)/duration;
    back->currentoffset = startx << FRACTIONBITS;
    back->yoffset=yoffset;
    if (pic1->height != pic2->height)
    {
        Error("SpawnCinematicMultiBack: heights are not the same\n"
              "                         name1=%s name2=%s\n",name,name2);
    }
    back->backdropwidth=pic1->width+pic2->width;
    back->height=pic1->height;
    back->data=SafeMalloc (back->backdropwidth*back->height);
    memcpy( back->data, &(pic1->data), pic1->width*pic1->height);
    memcpy( back->data+(pic1->width*pic1->height),
            &(pic2->data),
            pic2->width*pic2->height
          );
    return back;
}

/*
===============
=
= SpawnCinematicPalette
=
===============
*/

paletteevent * SpawnCinematicPalette ( char * name )
{
    paletteevent * palette;

    palette = SafeMalloc ( sizeof (paletteevent) );

    // copy name of palette

    strcpy ( palette->name, name );

    return palette;
}


/*
=================
=
= ScaleFilmPost
=
=================
*/
void ScaleFilmPost (byte * src, byte * buf)
{
    int  offset;
    int  length;
    int  topscreen;
    int  bottomscreen;


    offset=*(src++);
    for (; offset!=255;)
    {
        length=*(src++);
        topscreen = cin_sprtopoffset + (cin_invscale*offset);
        bottomscreen = topscreen + (cin_invscale*length);
        cin_yl = (topscreen+FRACTIONUNIT-1)>>FRACTIONBITS;
        cin_yh = (bottomscreen-FRACTIONUNIT)>>FRACTIONBITS;
        if (cin_yh >= iGLOBAL_SCREENHEIGHT)
            cin_yh = iGLOBAL_SCREENHEIGHT-1;
        if (cin_yl < 0)
            cin_yl = 0;
        if (cin_yl <= cin_yh)
        {
            cin_source=src-offset;
            R_DrawFilmColumn (buf);
        }
        src+=length;
        offset=*(src++);
    }

}

/*
=================
=
= DrawFlic
=
=================
*/
void DrawFlic ( flicevent * flic )
{
    byte * curpal;
    char flicname[40];

    curpal = SafeMalloc (768);

    CinematicGetPalette (curpal);

    DrawFadeout ( );

    if (flic->usefile==false)
    {
        strcpy(flicname,flic->name);
    }
    else
    {
        strcpy(flicname,flic->name);
        strcat(flicname,".fli");
    }

// med
//   PlayFlic ( flicname, buf, flic->usefile, flic->loop);

    if (flic->loop==true)
        ClearCinematicAbort();

    DrawFadeout ( );

    DrawBlankScreen ( );

    CinematicSetPalette (curpal);

    SafeFree (curpal);
    GetCinematicTics ();
    GetCinematicTics ();
}

/*
=================
=
= PrecacheFlic
=
=================
*/

void PrecacheFlic (flicevent * flic)
{
    if (flic->usefile==false)
    {
        W_CacheLumpName(flic->name,PU_CACHE, CvtNull, 1);
    }
}

/*
===============
=
= DrawCinematicBackground
=
===============
*/

void DrawCinematicBackground ( backevent * back )
{
    byte * src;
    byte * buf;
    lpic_t * pic;
    int i;
    int plane;
    int offset;
    int height;

    pic=(lpic_t *)W_CacheLumpName(back->name,PU_CACHE, Cvt_lpic_t, 1);

    height = pic->height;
    if (height+back->yoffset>iGLOBAL_SCREENHEIGHT)
        height=iGLOBAL_SCREENHEIGHT-back->yoffset;

    if (height!=iGLOBAL_SCREENHEIGHT)
        DrawClearBuffer ();

    plane = 0;

    {
        buf=(byte *)bufferofs+ylookup[back->yoffset];
        offset=(back->currentoffset>>FRACTIONBITS)+plane;

        for (i=0; i<iGLOBAL_SCREENWIDTH; i++,offset++,buf++)
        {
            if (offset>=back->backdropwidth)
                src=&(pic->data) + ( (offset - back->backdropwidth) * (pic->height) );
            else if (offset<0)
                src=&(pic->data) + ( (offset + back->backdropwidth) * (pic->height) );
            else
                src=&(pic->data) + ( offset * (pic->height) );
            DrawFilmPost(buf,src,height);
        }
    }
}

/*
===============
=
= DrawCinematicMultiBackground
=
===============
*/

void DrawCinematicMultiBackground ( backevent * back )
{
    byte * src;
    byte * buf;
    int i;
    int plane;
    int offset;
    int height;

    height = back->height;
    if (height+back->yoffset>iGLOBAL_SCREENHEIGHT)
        height=iGLOBAL_SCREENHEIGHT-back->yoffset;

    if (height!=iGLOBAL_SCREENHEIGHT)
        DrawClearBuffer ();

    plane = 0;

    {
        buf=(byte *)bufferofs+ylookup[back->yoffset];
        offset=(back->currentoffset>>FRACTIONBITS)+plane;

        for (i=0; i<iGLOBAL_SCREENWIDTH; i++,offset++,buf++)
        {
            if (offset>=back->backdropwidth)
                src=back->data + ( (offset - back->backdropwidth) * (back->height) );
            else if (offset<0)
                src=back->data + ( (offset + back->backdropwidth) * (back->height) );
            else
                src=back->data + ( offset * (back->height) );
            DrawFilmPost(buf,src,height);
        }
    }
}

/*
===============
=
= DrawCinematicBackdrop
=
===============
*/

void DrawCinematicBackdrop ( backevent * back )
{
    byte * src;
    byte * shape;
    byte * buf;
    patch_t * p;
    int i;
    int plane;
    int offset;
    int postoffset;
    int postlength;
    int toppost;

    shape=W_CacheLumpName(back->name,PU_CACHE, Cvt_patch_t, 1);
    p=(patch_t *)shape;

    toppost=-p->topoffset+back->yoffset;

    plane = 0;

    {
        buf=(byte *)bufferofs;
        offset=(back->currentoffset>>FRACTIONBITS)+plane;

        for (i=0; i<iGLOBAL_SCREENWIDTH; i++,offset++,buf++)
        {
            if (offset>=back->backdropwidth)
                src = shape + p->collumnofs[offset - back->backdropwidth];
            else if (offset<0)
                src=shape + p->collumnofs[offset + back->backdropwidth];
            else
                src = shape + p->collumnofs[offset];

            postoffset=*(src++);
            for (; postoffset!=255;)
            {
                postlength=*(src++);
                DrawFilmPost(buf + ylookup[toppost+postoffset],src,postlength);
                src+=postlength;
                postoffset=*(src++);
            }
        }
    }
}

/*
=================
=
= PrecacheBack
=
=================
*/
void PrecacheBack ( backevent * back )
{
    W_CacheLumpName( back->name, PU_CACHE, CvtNull, 1);
}


/*
=================
=
= DrawCinematicSprite
=
=================
*/
void DrawCinematicSprite ( spriteevent * sprite )
{
    byte   *shape;
    int    frac;
    patch_t *p;
    int    x1,x2;
    int    tx;
    int    xcent;
    byte * buf;
    int    height;

    height = sprite->scale >> FRACTIONBITS;

    if (height<2)
        return;

    shape=W_CacheLumpNum( W_GetNumForName(sprite->name)+sprite->frame, PU_CACHE, Cvt_patch_t, 1);
    p=(patch_t *)shape;


    cin_ycenter=sprite->y >> FRACTIONBITS;
    cin_invscale = (height<<FRACTIONBITS)/p->origsize;
    buf=(byte *)bufferofs;
    tx=-p->leftoffset;
    xcent=(sprite->x & 0xffff0000)-(height<<(FRACTIONBITS-1))+(FRACTIONUNIT>>1);

//
// calculate edges of the shape
//
    x1 = (xcent+(tx*cin_invscale))>>FRACTIONBITS;
    if (x1 >= iGLOBAL_SCREENWIDTH)
        return;               // off the right side
    tx+=p->width;
    x2 = ((xcent+(tx*cin_invscale)) >>FRACTIONBITS) - 1 ;
    if (x2 < 0)
        return;         // off the left side

    cin_iscale=(p->origsize<<FRACTIONBITS)/height;

    if (x1<0)
    {
        frac=cin_iscale*(-x1);
        x1=0;
    }
    else
        frac=0;
    x2 = x2 >= iGLOBAL_SCREENWIDTH ? (iGLOBAL_SCREENWIDTH-1) : x2;

    cin_texturemid = (((p->origsize>>1)+p->topoffset)<<FRACTIONBITS)+(FRACTIONUNIT>>1);
    cin_sprtopoffset = (cin_ycenter<<16) - FixedMul(cin_texturemid,cin_invscale);

    for (; x1<=x2 ; x1++, frac += cin_iscale)
    {
        ScaleFilmPost(((p->collumnofs[frac>>FRACTIONBITS])+shape),buf+x1);
    }
}

/*
=================
=
= PrecacheCinematicSprite
=
=================
*/
void PrecacheCinematicSprite ( spriteevent * sprite )
{
    int i;

    for (i=0; i<sprite->numframes; i++)
    {
        W_CacheLumpNum( W_GetNumForName(sprite->name)+i, PU_CACHE, Cvt_patch_t, 1);
    }
}


/*
=================
=
= DrawPalette
=
=================
*/

void DrawPalette (paletteevent * event)
{
    byte * pal;

    pal=W_CacheLumpName(event->name,PU_CACHE, CvtNull, 1);
    XFlipPage ();
    CinematicSetPalette (pal);
}

/*
=================
=
= PrecachePalette
=
=================
*/

void PrecachePalette (paletteevent * event)
{
    W_CacheLumpName(event->name,PU_CACHE, CvtNull, 1);
}


/*
=================
=
= DrawFadeout
=
=================
*/
#define FADEOUTTIME 20

void DrawFadeout ( void )
{
    byte origpal[768];
    byte newpal[768];
    int      i,j;

    CinematicGetPalette (&origpal[0]);
    for (j = 0; j < FADEOUTTIME; j++)
    {
        for (i = 0; i < 768; i++)
        {
            newpal[i] = ( origpal[i] * (FADEOUTTIME - j - 1) ) / FADEOUTTIME;
        }
        WaitVBL();
        CinematicSetPalette (&newpal[0]);
        CinematicDelay();
    }
    VL_ClearVideo (0);
    GetCinematicTics ();
    GetCinematicTics ();
}

/*
=================
=
= DrawBlankScreen
=
=================
*/
void DrawBlankScreen ( void )
{
    VL_ClearVideo (0);
}

/*
=================
=
= DrawClearBuffer
=
=================
*/
void DrawClearBuffer ( void )
{
    memset((byte *)bufferofs,0,iGLOBAL_SCREENWIDTH*iGLOBAL_SCREENHEIGHT);
}

/*
===============
=
= UpdateCinematicBack
=
===============
*/

boolean UpdateCinematicBack ( backevent * back )
{
    back->duration--;

    if (back->duration<0)
        return false;

    back->currentoffset += back->dx;

    return true;
}

/*
=================
=
= UpdateCinematicSprite
=
=================
*/
boolean UpdateCinematicSprite ( spriteevent * sprite )
{
    sprite->duration--;

    if (sprite->duration<0)
        return false;

    sprite->framedelay--;

    if (sprite->framedelay==0)
    {
        sprite->frame++;
        if (sprite->frame==sprite->numframes)
            sprite->frame=0;
        sprite->framedelay=sprite->frametime;
    }

    sprite->x+=sprite->dx;
    sprite->y+=sprite->dy;
    sprite->scale+=sprite->dscale;

    return true;
}

/*
=================
=
= UpdateCinematicEffect
=
=================
*/
boolean UpdateCinematicEffect ( enum_eventtype type, void * effect )
{
    switch (type)
    {
    case background_noscrolling:
    case background_scrolling:
    case backdrop_scrolling:
    case backdrop_noscrolling:
    case background_multi:
        return UpdateCinematicBack ( (backevent *) effect );
        break;
    case sprite_background:
    case sprite_foreground:
        return UpdateCinematicSprite ( (spriteevent *) effect );
        break;
    case flic:
        return true;
        break;
    case palette:
    case fadeout:
    case blankscreen:
    case clearbuffer:
        return true;
        break;
    case cinematicend:
        cinematicdone=true;
        return true;
        break;
    }
    return true;
}
/*
=================
=
= DrawCinematicEffect
=
=================
*/
boolean DrawCinematicEffect ( enum_eventtype type, void * effect )
{
    switch (type)
    {
    case background_noscrolling:
    case background_scrolling:
        DrawCinematicBackground ( (backevent *) effect );
        return true;
        break;
    case background_multi:
        DrawCinematicMultiBackground ( (backevent *) effect );
        return true;
        break;
    case backdrop_scrolling:
    case backdrop_noscrolling:
        DrawCinematicBackdrop ( (backevent *) effect );
        return true;
        break;
    case sprite_background:
    case sprite_foreground:
        DrawCinematicSprite ( (spriteevent *) effect );
        return true;
        break;
    case flic:
        DrawFlic ( (flicevent *) effect );
        return false;
        break;
    case palette:
        DrawPalette ( (paletteevent *) effect );
        return false;
        break;
    case fadeout:
        DrawFadeout ();
        return false;
        break;
    case blankscreen:
        DrawBlankScreen ();
        return false;
        break;
    case clearbuffer:
        DrawClearBuffer ();
        return false;
        break;
    case cinematicend:
        return true;
        break;
    }
    return true;
}

/*
=================
=
= PrecacheCinematicEffect
=
=================
*/
void PrecacheCinematicEffect ( enum_eventtype type, void * effect )
{
    switch (type)
    {
    case background_noscrolling:
    case background_scrolling:
    case backdrop_scrolling:
    case backdrop_noscrolling:
        PrecacheBack ( (backevent *) effect );
        break;
    case sprite_background:
    case sprite_foreground:
        PrecacheCinematicSprite ( (spriteevent *) effect );
        break;
    case palette:
        PrecachePalette ( (paletteevent *) effect );
        break;
    case flic:
        PrecacheFlic ( (flicevent *) effect );
        break;
    default:
        ;
    }
}

/*
===============
=
= ProfileDisplay
=
===============
*/

void ProfileDisplay ( void )
{
    byte * buf;
    int i;
    byte src[200];
    int width = StretchScreen? 320:iGLOBAL_SCREENWIDTH;

    DrawClearBuffer ();

    {
        buf=(byte *)bufferofs;

        for (i=0; i<width; i++,buf++)
        {
            DrawFilmPost(buf,&src[0],200);
        }
    }
}

/*
===============
=
= DrawPostPic
=
===============
*/

void DrawPostPic ( int lumpnum )
{
    byte * src;
    byte * buf;
    lpic_t * pic;
    int i;
    int plane;
    int height;
    int width = StretchScreen? 320:iGLOBAL_SCREENWIDTH;

    pic=(lpic_t *)W_CacheLumpNum(lumpnum,PU_CACHE, Cvt_lpic_t, 1);

    height = pic->height;

    plane = 0;

    {
        buf=(byte *)bufferofs;

        src=&(pic->data) + (plane*pic->height);

        for (i=0; i<width; i++,src+=pic->height,buf++)
        {
            DrawFilmPost(buf,src,height);
        }
    }
}
