//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2010 James Haley, Samuel Villarreal
//
// 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.  See the
// GNU General Public License for more details.
//
// DESCRIPTION:
//
// [STRIFE] New Module
//
// Strife Hub Saving Code
//

// For GNU C and POSIX targets, dirent.h should be available. Otherwise, for
// Visual C++, we need to include the win_opendir module.
//
// OpenVMS port: added the VMS arm.  See COMPILER_FIXES.TXT.  DEC C is neither
// __GNUC__ nor does it predefine POSIX, so this chain fell through to the
// #error and Strife would not build - even though DEC C 8.4 DOES ship a
// perfectly good <dirent.h> in DECC$RTLDEF, declaring DIR, opendir(),
// readdir(), closedir() and struct dirent with a d_name member, which is
// everything this file uses.  No compatibility shim is needed; the header just
// has to be reachable, so the VMS arm includes the SAME <dirent.h> the POSIX
// arm does.
//
// Note that d_name is the ONLY member this file touches, which is what makes
// the plain include sufficient.  VMS's struct dirent has no d_namlen and no
// d_type (this is established: the sibling Chocolate Duke port hit exactly
// that and had to replace a d_namlen reference with strlen - see section 5 of
// ITS COMPILER_FIXES.TXT), so a file that used either would need real work
// here.  This one does not: all four functions below just hand d_name straight
// back to M_SafeFilePath() to be reopened, removed or copied, and never parse
// it or measure it.  The VMS CRTL NUL-terminates d_name, so the strcmp()s
// against "." and ".." are well-formed.
#if defined(_MSC_VER)
#include <win_opendir.h>
#elif defined(__GNUC__) || defined(POSIX) || defined(VMS) || defined(__VMS)
#include <dirent.h>
#else
#error Need an include for dirent.h!
#endif
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>

#include "z_zone.h"
#include "i_system.h"
#include "d_player.h"
#include "deh_str.h"
#include "doomstat.h"
#include "m_misc.h"
#include "m_saves.h"
#include "p_dialog.h"

//
// File Paths
//
// Strife maintains multiple file paths related to savegames.
//
char *savepath;     // The actual path of the selected saveslot
char *savepathtemp; // The path of the temporary saveslot (slot 6; see m_saves.h)
char *loadpath;     // Path used while loading the game

char character_name[CHARACTER_NAME_LEN]; // Name of "character" for saveslot

//
// ClearTmp
//
// Clear the temporary save directory
//
void ClearTmp(void)
{
    DIR *sp2dir = NULL;
    struct dirent *f = NULL;

    if(savepathtemp == NULL)
        I_Error("you fucked up savedir man!");

    if(!(sp2dir = opendir(savepathtemp)))
        I_Error("ClearTmp: Couldn't open dir %s", savepathtemp);

    while((f = readdir(sp2dir)))
    {
        char *filepath = NULL;

        // haleyjd: skip "." and ".." without assuming they're the
        // first two entries like the original code did.
        if(!strcmp(f->d_name, ".") || !strcmp(f->d_name, ".."))
            continue;

        // haleyjd: use M_SafeFilePath, not sprintf
        filepath = M_SafeFilePath(savepathtemp, f->d_name);
        remove(filepath);

        Z_Free(filepath);
    }

    closedir(sp2dir);
}

//
// ClearSlot
//
// Clear a single save slot folder
//
void ClearSlot(void)
{
    DIR *spdir = NULL;
    struct dirent *f = NULL;

    if(savepath == NULL)
        I_Error("userdir is fucked up man!");

    if(!(spdir = opendir(savepath)))
        I_Error("ClearSlot: Couldn't open dir %s", savepath);

    while((f = readdir(spdir)))
    {
        char *filepath = NULL;

        if(!strcmp(f->d_name, ".") || !strcmp(f->d_name, ".."))
            continue;
        
        // haleyjd: use M_SafeFilePath, not sprintf
        filepath = M_SafeFilePath(savepath, f->d_name);
        remove(filepath);

        Z_Free(filepath);
    }

    closedir(spdir);
}

//
// FromCurr
//
// Copying files from savepathtemp to savepath
//
void FromCurr(void)
{
    DIR *sp2dir = NULL;
    struct dirent *f = NULL;

    if(!(sp2dir = opendir(savepathtemp)))
        I_Error("FromCurr: Couldn't open dir %s", savepathtemp);

    while((f = readdir(sp2dir)))
    {
        byte *filebuffer  = NULL;
        int   filelen     = 0;
        char *srcfilename = NULL;
        char *dstfilename = NULL;

        // haleyjd: skip "." and ".." without assuming they're the
        // first two entries like the original code did.
        if(!strcmp(f->d_name, ".") || !strcmp(f->d_name, ".."))
            continue;

        // haleyjd: use M_SafeFilePath, NOT sprintf.
        srcfilename = M_SafeFilePath(savepathtemp, f->d_name);
        dstfilename = M_SafeFilePath(savepath,     f->d_name);

        filelen = M_ReadFile(srcfilename, &filebuffer);
        M_WriteFile(dstfilename, filebuffer, filelen);

        Z_Free(filebuffer);
        Z_Free(srcfilename);
        Z_Free(dstfilename);
    }

    closedir(sp2dir);
}

//
// ToCurr
//
// Copying files from savepath to savepathtemp
//
void ToCurr(void)
{
    DIR *spdir = NULL;
    struct dirent *f = NULL;

    ClearTmp();

    // BUG: Rogue copypasta'd this error message, which is why we don't know
    // the real original name of this function.
    if(!(spdir = opendir(savepath)))
        I_Error("ClearSlot: Couldn't open dir %s", savepath);

    while((f = readdir(spdir)))
    {
        byte *filebuffer  = NULL;
        int   filelen     = 0;
        char *srcfilename = NULL;
        char *dstfilename = NULL;

        if(!strcmp(f->d_name, ".") || !strcmp(f->d_name, ".."))
            continue;

        // haleyjd: use M_SafeFilePath, NOT sprintf.
        srcfilename = M_SafeFilePath(savepath,     f->d_name);
        dstfilename = M_SafeFilePath(savepathtemp, f->d_name);

        filelen = M_ReadFile(srcfilename, &filebuffer);
        M_WriteFile(dstfilename, filebuffer, filelen);

        Z_Free(filebuffer);
        Z_Free(srcfilename);
        Z_Free(dstfilename);
    }

    closedir(spdir);
}

//
// M_SaveMoveMapToHere
//
// Moves a map to the "HERE" save.
//
void M_SaveMoveMapToHere(void)
{
    char *mapsave  = NULL;
    char *heresave = NULL;
    char tmpnum[33];

    // haleyjd: no itoa available...
    M_snprintf(tmpnum, sizeof(tmpnum), "%d", gamemap);

    // haleyjd: use M_SafeFilePath, not sprintf
    mapsave  = M_SafeFilePath(savepath, tmpnum);
    heresave = M_SafeFilePath(savepath, "here");

    // haleyjd: use M_FileExists, not access
    if(M_FileExists(mapsave))
    {
        remove(heresave);
        rename(mapsave, heresave);
    }

    Z_Free(mapsave);
    Z_Free(heresave);
}

//
// M_SaveMoveHereToMap
//
// Moves the "HERE" save to a map.
//
void M_SaveMoveHereToMap(void)
{
    char *mapsave  = NULL;
    char *heresave = NULL;
    char tmpnum[33];

    // haleyjd: no itoa available...
    M_snprintf(tmpnum, sizeof(tmpnum), "%d", gamemap);

    mapsave  = M_SafeFilePath(savepathtemp, tmpnum);
    heresave = M_SafeFilePath(savepathtemp, "here");

    if(M_FileExists(heresave))
    {
        remove(mapsave);
        rename(heresave, mapsave);
    }

    Z_Free(mapsave);
    Z_Free(heresave);
}

//
// M_SaveMisObj
//
// Writes the mission objective into the MIS_OBJ file.
//
boolean M_SaveMisObj(const char *path)
{
    boolean result;
    char *destpath = NULL;

    // haleyjd 20110210: use M_SafeFilePath, not sprintf
    destpath = M_SafeFilePath(path, "mis_obj");
    result   = M_WriteFile(destpath, mission_objective, OBJECTIVE_LEN);

    Z_Free(destpath);
    return result;
}

//
// M_ReadMisObj
//
// Reads the mission objective from the MIS_OBJ file.
//
void M_ReadMisObj(void)
{
    FILE *f = NULL;
    char *srcpath = NULL;

    // haleyjd: use M_SafeFilePath, not sprintf
    srcpath = M_SafeFilePath(savepathtemp, "mis_obj");

    if((f = fopen(srcpath, "rb")))
    {
        fread(mission_objective, 1, OBJECTIVE_LEN, f);
        fclose(f);
    }

    Z_Free(srcpath);
}

//=============================================================================
//
// Original Routines
//
// haleyjd - None of the below code is derived from Strife itself, but has been
// adapted or created in order to provide secure, portable filepath handling
// for the purposes of savegame support. This is partially needed to allow for
// differences in Choco due to it being multiplatform. The rest exists because
// I cannot stand programming in an impoverished ANSI C environment that
// calls sprintf on fixed-size buffers. :P
//

//
// M_Calloc
//
// haleyjd 20110210 - original routine
// Because Choco doesn't have Z_Calloc O_o
//
void *M_Calloc(size_t n1, size_t n2)
{
    return (n1 *= n2) ? memset(Z_Malloc(n1, PU_STATIC, NULL), 0, n1) : NULL;
}

//
// M_StringAlloc
//
// haleyjd: This routine takes any number of strings and a number of extra
// characters, calculates their combined length, and calls Z_Alloca to create
// a temporary buffer of that size. This is extremely useful for allocation of
// file paths, and is used extensively in d_main.c.  The pointer returned is
// to a temporary Z_Alloca buffer, which lives until the next main loop
// iteration, so don't cache it. Note that this idiom is not possible with the
// normal non-standard alloca function, which allocates stack space.
//
// [STRIFE] - haleyjd 20110210
// This routine is taken from the Eternity Engine and adapted to do without
// Z_Alloca. I need secure string concatenation for filepath handling. The
// only difference from use in EE is that the pointer returned in *str must
// be manually freed.
//
int M_StringAlloc(char **str, int numstrs, size_t extra, const char *str1, ...)
{
    va_list args;
    size_t len = extra;

    if(numstrs < 1)
        I_Error("M_StringAlloc: invalid input\n");

    len += strlen(str1);

    --numstrs;

    if(numstrs != 0)
    {   
        va_start(args, str1);

        while(numstrs != 0)
        {
            const char *argstr = va_arg(args, const char *);

            len += strlen(argstr);

            --numstrs;
        }

        va_end(args);
    }

    ++len;

    *str = (char *)(M_Calloc(1, len));

    return len;
}

//
// M_NormalizeSlashes
//
// Remove trailing slashes, translate backslashes to slashes
// The string to normalize is passed and returned in str
//
// killough 11/98: rewritten
//
// [STRIFE] - haleyjd 20110210: Borrowed from Eternity and adapted to respect 
// the DIR_SEPARATOR define used by Choco Doom. This routine originated in
// BOOM.
//
void M_NormalizeSlashes(char *str)
{
    char *p;
   
    // Convert all slashes/backslashes to DIR_SEPARATOR
    for(p = str; *p; p++)
    {
        if((*p == '/' || *p == '\\') && *p != DIR_SEPARATOR)
            *p = DIR_SEPARATOR;
    }

    // Remove trailing slashes
    // OpenVMS port: "*--p == DIR_SEPARATOR" rewritten; see COMPILER_FIXES.TXT.
    // The decrement is the RIGHT operand of && inside a loop condition, so it
    // can neither be hoisted out (the condition is re-evaluated every
    // iteration) nor made unconditional (it must not run when p == str).
    // Stepping the pointer back first and testing p[0] is exactly equivalent.
    while(p > str)
    {
        --p;

        if(*p != DIR_SEPARATOR)
            break;

        *p = 0;
    }

    // Collapse multiple slashes
    for(p = str; (*str++ = *p); )
        if(*p++ == DIR_SEPARATOR)
            while(*p == DIR_SEPARATOR)
                p++;
}

//
// M_SafeFilePath
//
// haleyjd 20110210 - original routine.
// This routine performs safe, portable concatenation of a base file path
// with another path component or file name. The returned string is Z_Malloc'd
// and should be freed when it has exhausted its usefulness.
//
char *M_SafeFilePath(const char *basepath, const char *newcomponent)
{
    int   newstrlen = 0;
    char *newstr = NULL;

    if (!strcmp(basepath, ""))
    {
        basepath = ".";
    }

    // Always throw in a slash. M_NormalizeSlashes will remove it in the case
    // that either basepath or newcomponent includes a redundant slash at the
    // end or beginning respectively.
    newstrlen = M_StringAlloc(&newstr, 3, 1, basepath, "/", newcomponent);
    M_snprintf(newstr, newstrlen, "%s/%s", basepath, newcomponent);
    M_NormalizeSlashes(newstr);

    return newstr;
}

//
// M_CreateSaveDirs
//
// haleyjd 20110210: Vanilla Strife went tits-up if it didn't have the full set
// of save folders which were created externally by the installer. fraggle says
// that's no good for Choco purposes, and I agree, so this routine will create
// the full set of folders under the configured savegamedir.
//
void M_CreateSaveDirs(const char *savedir)
{
    int i;

    // OpenVMS port: 7 spelled as STRIFE_NUM_SAVEDIRS, so that the count of
    // folders created here and the temp slot number used by g_game.c cannot
    // drift apart.  Same set of directories as before: slots 0 through 6.
    for(i = 0; i < STRIFE_NUM_SAVEDIRS; i++)
    {
        char *compositedir;

        // compose the full path by concatenating with savedir
        compositedir = M_SafeFilePath(savedir, M_MakeStrifeSaveDir(i, ""));

        M_MakeDirectory(compositedir);

        Z_Free(compositedir);
    }
}

//
// M_MakeStrifeSaveDir
//
// haleyjd 20110211: Convenience routine
//
// OpenVMS port: the "strfsav%d.ssg%s" literal moved to m_saves.h as
// STRIFE_SAVEDIR_FMT so that the one place that knows how a slot folder is
// spelled is shared with the temp-slot name built in g_game.c, which used to
// hardcode "strfsav6.ssg" separately and would otherwise have kept the dot.
// See COMPILER_FIXES.TXT section 16.
char *M_MakeStrifeSaveDir(int slotnum, const char *extra)
{
    static char tmpbuffer[32];

    M_snprintf(tmpbuffer, sizeof(tmpbuffer),
               STRIFE_SAVEDIR_FMT, slotnum, extra);

    return tmpbuffer;
}

// 
// M_GetFilePath
//
// haleyjd: STRIFE-FIXME: Temporary?
// Code borrowed from Eternity, and modified to return separator char
//
char M_GetFilePath(const char *fn, char *dest, size_t len)
{
    boolean found_slash = false;
    char *p;
    char sepchar = '\0';

    memset(dest, 0, len);

    p = dest + len - 1;

    M_StringCopy(dest, fn, len);

    while(p >= dest)
    {
        if(*p == '/' || *p == '\\')
        {
            sepchar = *p;
            found_slash = true; // mark that the path ended with a slash
            *p = '\0';
            break;
        }
        *p = '\0';
        p--;
    }

    // haleyjd: in the case that no slash was ever found, yet the
    // path string is empty, we are dealing with a file local to the
    // working directory. The proper path to return for such a string is
    // not "", but ".", since the format strings add a slash now. When
    // the string is empty but a slash WAS found, we really do want to
    // return the empty string, since the path is relative to the root.
    if(!found_slash && *dest == '\0')
        *dest = '.';

    // if a separator is not found, default to forward, because Windows 
    // supports that too.
    if(sepchar == '\0') 
        sepchar = '/';

    return sepchar;
}

// EOF


