//-------------------------------------------------------------------------
/*
Copyright (C) 1997, 2005 - 3D Realms Entertainment

This file is part of Shadow Warrior version 1.2

Shadow Warrior 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.

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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

Original Source: 1997 - Frank Maddin and Jim Norwood
Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
*/
//-------------------------------------------------------------------------

//#define MAIN
//#define QUIET
#include "build.h"
#include "cache1d.h"

#include "keys.h"
#include "names2.h"
#include "panel.h"
#include "game.h"
#include "network.h"

#include "mytypes.h"
#include "control.h"
#include "function.h"
#include "demo.h"

#include "player.h"
#include "menus.h"


DFILE DemoFileIn = DF_ERR;
FILE *DemoFileOut;
SWBOOL DemoPlaying = FALSE;
SWBOOL DemoRecording = FALSE;
SWBOOL DemoEdit = FALSE;
SWBOOL DemoMode = FALSE;
SWBOOL DemoModeMenuState = FALSE;
char DemoFileName[16] = "demo.dmo";
char DemoLevelName[16] = "";
extern SWBOOL NewGame;

// Demo sync stuff
FILE *DemoSyncFile;
SWBOOL DemoSyncTest = FALSE, DemoSyncRecord = FALSE;
char DemoTmpName[16] = "";

SW_PACKET DemoBuffer[DEMO_BUFFER_MAX];
int DemoRecCnt = 0;                    // Can only record 1-player game

SWBOOL DemoDone;

void DemoWriteHeader(void);
void DemoReadHeader(void);
void DemoReadBuffer(void);


//
// DemoDebug Vars
//

// DemoDebugMode will close the file after every write
SWBOOL DemoDebugMode = FALSE;
//SWBOOL DemoDebugMode = TRUE;
SWBOOL DemoInitOnce = FALSE;
short DemoDebugBufferMax = 1;

extern char LevelName[];
extern char LevelSong[16];
extern uint8_t FakeMultiNumPlayers;
extern SWBOOL QuitFlag;

///////////////////////////////////////////
//
// Demo File Manipulation
//
///////////////////////////////////////////

char *DemoSyncFileName(void)
{
    static char file_name[32];
    char *ptr;

    strcpy(file_name, DemoFileName);

    if ((ptr = strchr(file_name, '.')) == 0)
        strcat(file_name, ".dms");
    else
    {
        *ptr = '\0';
        strcat(file_name, ".dms");
    }

    return file_name;
}

void
DemoSetup(void)
{
    if (DemoRecording)
    {
        if (DemoSyncRecord)
            DemoSyncFile = fopen(DemoSyncFileName(),"wb");

        DemoWriteHeader();
        memset(&DemoBuffer, -1, sizeof(DemoBuffer));
    }

    if (DemoPlaying)
    {
        if (DemoSyncRecord)
            DemoSyncFile = fopen(DemoSyncFileName(),"wb");
        if (DemoSyncTest)
            DemoSyncFile = fopen(DemoSyncFileName(),"rb");

        DemoReadHeader();
        memset(&DemoBuffer, -1, sizeof(DemoBuffer));
        DemoReadBuffer();
    }
}

void
DemoRecordSetup(void)
{
    if (DemoRecording)
    {
        if (DemoSyncRecord)
            DemoSyncFile = fopen(DemoSyncFileName(),"wb");

        DemoWriteHeader();
        memset(&DemoBuffer, -1, sizeof(DemoBuffer));
    }
}

void
DemoPlaySetup(void)
{
    if (DemoPlaying)
    {
        if (DemoSyncRecord)
            DemoSyncFile = fopen(DemoSyncFileName(),"wb");
        if (DemoSyncTest)
            DemoSyncFile = fopen(DemoSyncFileName(),"rb");

        DemoReadHeader();
        memset(&DemoBuffer, -1, sizeof(DemoBuffer));
        DemoReadBuffer();
    }
}

void
DemoWriteHeader(void)
{
    DEMO_HEADER dh;
    DEMO_START_POS dsp;
    PLAYERp pp;

    DemoFileOut = fopen(DemoFileName, "wb");

    if (!DemoFileOut)
        return;

    strcpy(dh.map_name, LevelName);
    strcpy(dh.LevelSong, LevelSong);
    dh.Level = Level;

    if (FakeMultiNumPlayers)
        dh.numplayers = FakeMultiNumPlayers;
    else
        dh.numplayers = numplayers;

    fwrite(&dh, sizeof(dh), 1, DemoFileOut);

    for (pp = Player; pp < Player + dh.numplayers; pp++)
    {
        dsp.x = pp->posx;
        dsp.y = pp->posy;
        dsp.z = pp->posz;
        fwrite(&dsp, sizeof(dsp), 1, DemoFileOut);
        fwrite(&pp->Flags, sizeof(pp->Flags), 1, DemoFileOut);
        int16_t ang = fix16_to_int(pp->q16ang);
        fwrite(&ang, sizeof(ang), 1, DemoFileOut);
    }

    fwrite(&Skill, sizeof(Skill), 1, DemoFileOut);
    fwrite(&gNet, sizeof(gNet), 1, DemoFileOut);

    if (DemoDebugMode)
    {
        DemoDebugBufferMax = numplayers;
        fclose(DemoFileOut);
    }
}

void
DemoReadHeader(void)
{
    DEMO_HEADER dh;
    DEMO_START_POS dsp;
    PLAYERp pp;

#if DEMO_FILE_TYPE != DEMO_FILE_GROUP
    if (DemoEdit)
    {
        DemoFileIn = fopen(DemoFileName, "rb+");
    }
    else
#endif
    {
        //DemoFileIn = fopen(DemoFileName, "rb");
        DemoFileIn = DOPEN_READ(DemoFileName);
    }

    if (DemoFileIn == DF_ERR)
        TerminateWithMsg(0, "File %s is not a valid demo file.", DemoFileName);

    DREAD(&dh, sizeof(dh), 1, DemoFileIn);

    strcpy(DemoLevelName, dh.map_name);
    strcpy(LevelSong, dh.LevelSong);
    Level = dh.Level;
    if (dh.numplayers > 1)
    {
        FakeMultiNumPlayers = dh.numplayers;
    }
    else
        numplayers = dh.numplayers;

    for (pp = Player; pp < Player + dh.numplayers; pp++)
    {
        DREAD(&dsp, sizeof(dsp), 1, DemoFileIn);
        pp->posx = dsp.x;
        pp->posy = dsp.y;
        pp->posz = dsp.z;
        COVERupdatesector(pp->posx, pp->posy, &pp->cursectnum);
        //pp->cursectnum = 0;
        //updatesectorz(pp->posx, pp->posy, pp->posz, &pp->cursectnum);
        DREAD(&pp->Flags, sizeof(pp->Flags), 1, DemoFileIn);
        int16_t ang;
        DREAD(&ang, sizeof(ang), 1, DemoFileIn);
        pp->q16ang = fix16_from_int(ang);
    }

    DREAD(&Skill, sizeof(Skill), 1, DemoFileIn);
    DREAD(&gNet, sizeof(gNet), 1, DemoFileIn);
}

// TODO: Write all data at once
static void
DemoWritePackets(const SW_PACKET *buffer, int32_t count, FILE *f)
{
    OLD_SW_PACKET packet;
    for (; count > 0; ++buffer, --count)
    {
        packet.vel = B_LITTLE16(buffer->vel);
        packet.svel = B_LITTLE16(buffer->svel);
        packet.angvel = fix16_to_int(buffer->q16angvel);
        packet.aimvel = fix16_to_int(buffer->q16aimvel);
        packet.bits = B_LITTLE32(buffer->bits);
        fwrite(&packet, sizeof(packet), 1, f);
    }
}

// TODO: Read all data at once
static void
DemoReadPackets(SW_PACKET *buffer, int32_t count, DFILE f)
{
    OLD_SW_PACKET packet;
    for (; count > 0; ++buffer, --count)
    {
        DREAD(&packet, sizeof(packet), 1, f);
        buffer->vel = B_LITTLE16(packet.vel);
        buffer->svel = B_LITTLE16(packet.svel);
        buffer->q16angvel = fix16_from_int(packet.angvel);
        buffer->q16aimvel = fix16_from_int(packet.aimvel);
        buffer->bits = B_LITTLE32(packet.bits);
    }
}

void
DemoDebugWrite(void)
{
    DemoFileOut = fopen(DemoFileName, "ab");

    ASSERT(DemoFileOut);

    DemoWritePackets(DemoBuffer, DemoDebugBufferMax, DemoFileOut);
    memset(DemoBuffer, -1, sizeof(SW_PACKET) * DemoDebugBufferMax);

    fclose(DemoFileOut);
}

void
DemoWriteBuffer(void)
{
    DemoWritePackets(DemoBuffer, sizeof(DemoBuffer)/sizeof(*DemoBuffer), DemoFileOut);
    memset(&DemoBuffer, -1, sizeof(DemoBuffer));
}

void
DemoReadBuffer(void)
{
    memset(&DemoBuffer, -1, sizeof(DemoBuffer));
    DemoReadPackets(DemoBuffer, sizeof(DemoBuffer)/sizeof(*DemoBuffer), DemoFileIn);
}

void
DemoBackupBuffer(void)
{
#if DEMO_FILE_TYPE != DEMO_FILE_GROUP
    FILE *NewDemoFile;
    FILE *OldDemoFile = DemoFileIn;
    int pos,i;
    char copy_buffer;
    char NewDemoFileName[16] = "!";

    // seek backwards to beginning of last buffer
    fseek(OldDemoFile, -sizeof(DemoBuffer)/sizeof(*DemoBuffer)*sizeof(OLD_SW_PACKET), SEEK_CUR);
    pos = ftell(OldDemoFile);

    // open a new edit file
    strcat(NewDemoFileName, DemoFileName);
    NewDemoFile = fopen(NewDemoFileName, "wb");

    rewind(OldDemoFile);

    // copy old demo to new demo
    for (i = 0; i < pos; i++)
    {
        fread(&copy_buffer, sizeof(copy_buffer), 1, OldDemoFile);
        fwrite(&copy_buffer,sizeof(copy_buffer), 1, NewDemoFile);
    }

    DemoFileOut = NewDemoFile;
    fclose(OldDemoFile);
#endif
}

void
DemoTerm(void)
{
    if (DemoRecording)
    {
        // if already closed
        if (DemoFileOut == NULL)
            return;

        if (DemoDebugMode)
        {
            DemoFileOut = fopen(DemoFileName, "ab");
            ASSERT(DemoFileOut);
        }
        else
        {
            // paste on a -1 record to the current buffer
            if (DemoRecCnt < DEMO_BUFFER_MAX)
                memset(&DemoBuffer[DemoRecCnt], -1, sizeof(DemoBuffer[DemoRecCnt]));

            DemoWriteBuffer();
        }

        // write at least 1 record at the end filled with -1
        // just for good measure
        memset(&DemoBuffer[0], -1, sizeof(DemoBuffer[0]));
        DemoWritePackets(DemoBuffer, 1, DemoFileOut);

        fclose(DemoFileOut);
        DemoFileOut = NULL;
    }

    if (DemoPlaying)
    {
        if (DemoFileIn == DF_ERR)
            return;

        DCLOSE(DemoFileIn);
        DemoFileIn = DF_ERR;
    }

    if (DemoSyncTest||DemoSyncRecord)
    {
        if (DemoSyncFile == NULL)
            return;

        fclose(DemoSyncFile);
        DemoSyncFile = NULL;
    }
}

///////////////////////////////////////////
//
// Demo Play Back
//
///////////////////////////////////////////


void
DemoPlayBack(void)
{
    int pnum, cnt;
    static int buf_ndx;
    PLAYERp pp;
    ControlInfo info;

    if (SW_SHAREWARE)
    {
        // code here needs to be similar to RunLevel startup code
        PlaySong(LevelSong, -1, TRUE, TRUE);
    }


    // Initialize Game part of network code (When ready2send != 0)
    InitNetVars();

    // IMPORTANT - MUST be right before game loop
    InitTimingVars();

    // THIS STUFF DEPENDS ON MYCONNECTINDEX BEING SET RIGHT
    pp = Player + myconnectindex;
    SetRedrawScreen(pp);

    if (!DemoInitOnce)
        buf_ndx = 0;

    // everything has been inited at least once for PLAYBACK
    DemoInitOnce = TRUE;

    cnt = 0;
    ready2send = 0;
    DemoDone = FALSE;

    while (TRUE)
    {
        timerUpdateClock();

        // makes code run at the same rate
        while (totalclock > totalsynctics)
        {
            handleevents();
            OSD_DispatchQueued();

            TRAVERSE_CONNECT(pnum)
            {
                pp = Player + pnum;
                pp->inputfifo[pp->movefifoend & (MOVEFIFOSIZ-1)] = DemoBuffer[buf_ndx];
                pp->movefifoend++;
                buf_ndx++;

                if (pp->inputfifo[(pp->movefifoend - 1) & (MOVEFIFOSIZ-1)].bits == -1)
                {
                    DemoDone = TRUE;
                    break;
                }

                if (buf_ndx > DEMO_BUFFER_MAX - 1)
                {
                    DemoReadBuffer();
                    buf_ndx = 0;
                }
            }

            if (DemoDone)
                break;

            cnt++;

            CONTROL_GetInput(&info);

            domovethings();

            MNU_CheckForMenus();

            // fast forward and slow mo
            if (DemoEdit)
            {
                if (KEY_PRESSED(KEYSC_F))
                {
                    if (KEY_PRESSED(KEYSC_LSHIFT) || KEY_PRESSED(KEYSC_RSHIFT))
                        totalclock += synctics;
                    else
                        totalclock += synctics-1;
                }

                if (KEY_PRESSED(KEYSC_S))
                    totalclock += 1-synctics;
            }
            else
            {
#if DEBUG
                if (KEY_PRESSED(KEYSC_ALT) && KEY_PRESSED(KEYSC_CTRL) && KEY_PRESSED(KEYSC_S))
                {
                    KEY_PRESSED(KEYSC_ALT) = KEY_PRESSED(KEYSC_CTRL) = KEY_PRESSED(KEYSC_S) = 0;
                    int16_t ang = fix16_to_int(Player->pang);
                    saveboard("demosave.map", &Player->pos, ang, &Player->cursectnum);
                }
#endif

                if (BUTTON(gamefunc_See_Co_Op_View))
                {
                    CONTROL_ClearButton(gamefunc_See_Co_Op_View);

                    screenpeek = connectpoint2[screenpeek];

                    if (screenpeek < 0)
                        screenpeek = connecthead;
                }

#if DEBUG
                if (KEY_PRESSED(KEYSC_RIGHT) || KEY_PRESSED(KEYSC_UP))
                {
                    if (KEY_PRESSED(KEYSC_LSHIFT) || KEY_PRESSED(KEYSC_RSHIFT))
                        totalclock += synctics;
                    else
                        totalclock += synctics-1;
                }

                if (KEY_PRESSED(KEYSC_LEFT) || KEY_PRESSED(KEYSC_DOWN))
                    totalclock += 1-synctics;
#endif
            }


            if (DemoSyncRecord)
                demosync_record();
            if (DemoSyncTest)
                demosync_test(cnt);
        }

        // Put this back in later when keyboard stuff is stable
        if (DemoEdit)
        {
            //CONTROL_GetButtonInput();
            CONTROL_GetInput(&info);

            // if a key is pressed, start recording from the point the key
            // was pressed
            if (BUTTON(gamefunc_Move_Forward) ||
                BUTTON(gamefunc_Move_Backward) ||
                BUTTON(gamefunc_Turn_Left) ||
                BUTTON(gamefunc_Turn_Right) ||
                BUTTON(gamefunc_Fire) ||
                BUTTON(gamefunc_Open) ||
                BUTTON(gamefunc_Jump) ||
                BUTTON(gamefunc_Crouch) ||
                BUTTON(gamefunc_Look_Up) ||
                BUTTON(gamefunc_Look_Down))
            {
                DemoBackupBuffer();

                DemoRecCnt = buf_ndx;
                DemoPlaying = FALSE;
                DemoRecording = TRUE;
                return;
            }
        }

        if (BUTTON(gamefunc_See_Co_Op_View))
        {
            screenpeek += 1;
            if (screenpeek > numplayers-1)
                screenpeek = 0;
        }

        // demo is over
        if (DemoDone)
            break;

        if (QuitFlag)
        {
            DemoMode = FALSE;
            break;
        }

        if (ExitLevel)
        {
            // Quiting Demo
            ExitLevel = FALSE;
            if (DemoMode)
            {
                DemoPlaying = FALSE;
                DemoMode = FALSE;
            }
            break;
        }

        drawscreen(Player + screenpeek);
    }

    // only exit if conditions are write
    if (DemoDone && !DemoMode && !NewGame)
    {
        TerminateLevel();
        TerminateGame();
        exit(0);
    }

}
