/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * 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, see <http://www.gnu.org/licenses/>.
 *
 */

#include "common/savefile.h"
#include "common/file.h"
#include "gui/filebrowser-dialog.h"
#include "gui/browser.h"
#include "director/director.h"
#include "director/picture.h"
#include "director/types.h"
#include "director/util.h"
#include "director/lingo/lingo.h"
#include "director/lingo/lingo-object.h"
#include "director/lingo/lingo-utils.h"
#include "director/lingo/xlibs/m/msfile.h"

/*************************************
 *
 * USED IN:
 * The Simpsons Cartoon Studio
 *
 *************************************/

/*
"I      mNew                      --Instantiates XObject"	1028:0023	string	56
"X      mDispose                  --Removes XObject Instance"	1028:005b	string	60
"S      mName                     --Returns XObject Name"	1028:0097	string	56
"SS     mGetFullDirList, dospath  --Returns MS-DOS Full Directory List (use *.*)"	1028:00cf	string	80
"SS     mGetFileList, dospath     --Returns MS-DOS File List (use *.*)"	1028:011f	string	70
"SS     mGetDirList, dospath      --Returns MS-DOS Directory List (use *.*)"	1028:0165	string	75
"II     mGetDiskFree, drive       --Returns Free disk space, drive is 0 for current, 1 for A, ..."	1028:01b0	string	97
"SS     mGetVolLabel, drive       --Returns Drive Label (use d:\\*.*)"	1028:0211	string	68
"IS     mCreateDir, dospath       --Creates an MS-DOS Directory"	1028:0255	string	63
"IS     mRemoveDir, dospath       --Removes an MS-DOS Directory"	1028:0294	string	63
"S      mGetCurrDir               --Returns Current Working Directory"	1028:02d3	string	69
"IS     mSetCurrDir, dospath      --Sets Current Working Directory"	1028:0318	string	66
"IS     mRemoveFile, dospath      --Deletes File named by dospath"	1028:035a	string	65
"ISS    mRenameFile, oldpath ,newpath      --Renames File named by oldpath"	1028:039b	string	74
"ISS    mCopyFile, srcpath, destpath       --Copys srcpath to destpath, overwrites"	1028:03e5	string	82
"ISSI   mCopyFiles, srcpath, destpath, incsubdir   --Copys srcpath to destpath, overwrites"	1028:0437	string	90
"ISSSS  mSetAppINI, app, key, string, file --Sets app.ini"	1028:0491	string	57
"SSSS   mGetAppINI, app, key, file         --Gets app.ini"	1028:04ca	string	57
"S      mGetWindowsDirectory               --Gets Windows directory path"	1028:0503	string	72
"I      mGetWinVer                         --Gets Windows major version number"	1028:054b	string	78
"I      mRestartWindows                    --Gets Windows major version number"	1028:0599	string	78
"SSSS   mOpenFileDlg, title, defspec, file --Displays Director Open File Dialog Box"	1028:05e7	string	83
"SSSS   mSaveFileDlg, title, defspec, file --Displays Director Save File Dialog Box"	1028:063a	string	83
"SSSS   mGetOFileName, deffile, filter, defext --Displays Open File Dialog Box"	1028:068d	string	78
"SSSS   mGetSFileName, deffile, filter, defext --Displays Save File Dialog Box"	1028:06db	string	78
"SSSS   mDisplayDlg, title, message, defspec --Displays Dialog Box"	1028:0729	string	66
"ISSSS  mProgMgrAdd, group, command, title, icon  --\r\r"	1028:076b	string	57
*/

namespace Director {

const char *const MSFile::xlibName = "MSFile";
const XlibFileDesc MSFile::fileNames[] = {
	{"MSFile", nullptr},
	{"MSFILE16", nullptr},
	{nullptr, nullptr},
};

static const MethodProto xlibMethods[] = {
	// XObject
	{"new",                 MSFile::m_new,                 0, 0, 400},
	{"dispose",             MSFile::m_dispose,             0, 0, 400},
	{"name",                MSFile::m_name,                0, 1, 400},
	{"getFullDirList",      MSFile::m_getFullDirList,      1, 1, 400},
	{"getFileList",         MSFile::m_getFileList,         1, 1, 400},
	{"getDirList",          MSFile::m_getDirList,          1, 1, 400},
	{"getDiskFree",         MSFile::m_getDiskFree,         1, 1, 400},
	{"getVolLabel",         MSFile::m_getVolLabel,         1, 1, 400},
	{"createDir",           MSFile::m_createDir,           1, 1, 400},
	{"removeDir",           MSFile::m_removeDir,           1, 1, 400},
	{"getCurrDir",          MSFile::m_getCurrDir,          0, 1, 400},
	{"setCurrDir",          MSFile::m_setCurrDir,          1, 1, 400},
	{"removeFile",          MSFile::m_removeFile,          1, 1, 400},
	{"renameFile",          MSFile::m_renameFile,          2, 1, 400},
	{"copyFile",            MSFile::m_copyFile,            2, 1, 400},
	{"copyFiles",           MSFile::m_copyFiles,           3, 1, 400},
	{"setAppINI",           MSFile::m_setAppINI,           4, 1, 400},
	{"getAppINI",           MSFile::m_getAppINI,           3, 1, 400},
	{"getWindowsDirectory", MSFile::m_getWindowsDirectory, 0, 1, 400},
	{"getWinVer",           MSFile::m_getWinVer,           0, 1, 400},
	{"restartWindows",      MSFile::m_restartWindows,      0 ,1, 400},
	{"openFileDlg",         MSFile::m_openFileDlg,         3, 1, 400},
	{"saveFileDlg",         MSFile::m_saveFileDlg,         3, 1, 400},
	{"getOFileName",        MSFile::m_getOFileName,        3, 1, 400},
	{"getSFileName",        MSFile::m_getSFileName,        3, 1, 400},
	{"displayDlg",          MSFile::m_displayDlg,          3, 1, 400},
	{"progMgrAdd",          MSFile::m_progMgrAdd,          4, 1, 400},
	{nullptr, nullptr, 0, 0, 0}};

static const BuiltinProto xlibBuiltins[] = {
	{nullptr, nullptr, 0, 0, 0, VOIDSYM}};

MSFileObject::MSFileObject(ObjectType objType) : Object<MSFileObject>("MSFile") {
	_objType = objType;
	_lastError = msErrorNone;
}

MSFileObject::MSFileObject(const MSFileObject &obj) : Object<MSFileObject>(obj) {
	_objType = obj.getObjType();
	_lastError = msErrorNone;
}

MSFileObject::~MSFileObject() {
}

void MSFileObject::dispose() {
	_disposed = true;
}

bool MSFileObject::hasProp(const Common::String &propName) {
	return (propName == "name");
}

Datum MSFileObject::getProp(const Common::String &propName) {
	if (propName == "name")
		return Datum(MSFile::xlibName);
	warning("MSFile::getProp: unknown property '%s'", propName.c_str());
	return Datum();
}

void MSFile::m_new(int nargs) {
	Datum result = g_lingo->_state->me;
	g_lingo->push(result);
}

XOBJSTUB(MSFile::m_dispose, 0)
XOBJSTUB(MSFile::m_name, "")
XOBJSTUB(MSFile::m_getFullDirList, "")
XOBJSTUB(MSFile::m_getFileList, "")
XOBJSTUB(MSFile::m_getDirList, "")

void MSFile::m_getDiskFree(int nargs) {
	// We don't care about the drive letter. We will always just return 500 MB.
	g_lingo->pop();

	// 500 MB in KB
	g_lingo->push(Datum(500 * 1024 * 1024));
}

void MSFile::m_getVolLabel(int nargs) {
	Datum d = g_lingo->pop();

	Common::String volLabel;

	const char *gameId = g_director->getGameId();
	if (strncmp(gameId, "simpsonsstudio", 14) == 0 || strncmp(gameId, "simpsonsplayer", 14) == 0) {
		volLabel = "Simpsons";
	} else {
		warning("MSFile::m_getVolLabel(): Unsupported gameid '%s'", gameId);
	}

	g_lingo->push(volLabel);
}

XOBJSTUB(MSFile::m_createDir, 0)
XOBJSTUB(MSFile::m_removeDir, 0)
XOBJSTUB(MSFile::m_getCurrDir, "")
XOBJSTUB(MSFile::m_setCurrDir, 0)
XOBJSTUB(MSFile::m_removeFile, 0)
XOBJSTUB(MSFile::m_renameFile, 0)

Common::Path MSFile::resolveSourceFilePath(const Common::String& srcParam) {
	return findPath(srcParam);
}

Common::SeekableReadStream *MSFile::openSourceStream(const Common::Path &srcFilePath, const Common::String &srcParam, char dirSeparator) {
	Common::SaveFileManager *saves = g_system->getSavefileManager();
	Common::String prefix = savePrefix();

	// The source file may be a save file in the ScummVM save folder.
	Common::String saveFileName = lastPathComponent(srcParam, dirSeparator);

	// Modify the source file name to match the name of the potential save file
	if (!saveFileName.hasSuffixIgnoreCase(".txt")) {
		saveFileName += ".txt";
	}

	if (!saveFileName.hasPrefixIgnoreCase(prefix)) {
		saveFileName = prefix + saveFileName;
	}

	// Try to open the save file
	Common::SeekableReadStream *srcReadStream = saves->openForLoading(saveFileName);

	// If the save file was not found then we will try opening the file directly
	if (!srcReadStream) {
		// We have a relative path so we can open it using Common::File
		Common::File *srcFile = new Common::File();
		if (srcFilePath.empty() || !srcFile->open(srcFilePath)) {
			warning("MSFile::m_copyFile(): Error opening file %s", srcFilePath.toString(Common::Path::kNativeSeparator).c_str());
			delete srcFile;
			return nullptr;
		}
		srcReadStream = srcFile;
	}

	return srcReadStream;
}

Common::SeekableWriteStream *MSFile::openDestinationStream(Common::String &destFileName) {
	Common::SaveFileManager *saves = g_system->getSavefileManager();
	Common::String prefix = savePrefix();

	Common::OutSaveFile *destFile = nullptr;

	if (!destFileName.hasSuffixIgnoreCase(".txt")) {
		destFileName += ".txt";
	}

	if (!destFileName.hasPrefixIgnoreCase(prefix)) {
		destFileName = prefix + destFileName;
	}

	destFile = saves->openForSaving(destFileName, false);

	return destFile;
}

bool MSFile::copyStream(Common::SeekableReadStream* srcStream, Common::SeekableWriteStream* destStream) {
	if (srcStream && destStream) {
		destStream->writeStream(srcStream);
		destStream->finalize();
		bool success = !destStream->err();
		delete destStream;
		return success;
	}

	return false;
}

bool MSFile::copyGameFile(const Common::String& srcParam, const Common::String& destParam) {
	char dirSeparator = g_director->_dirSeparator;
	Common::Path srcFilePath = resolveSourceFilePath(srcParam);
	Common::String destFileName = Common::lastPathComponent(destParam, dirSeparator);

	Common::SeekableReadStream *srcStream = openSourceStream(srcFilePath, srcParam, dirSeparator);
	Common::SeekableWriteStream *destStream = openDestinationStream(destFileName);

	return copyStream(srcStream, destStream);
}

void MSFile::m_copyFile(int nargs) {
	Datum d2 = g_lingo->pop();
	Datum d1 = g_lingo->pop();

	// The source and destination file parameters are absolute paths from the games perspective but do not match the paths for ScummVM
	Common::String srcParam = d1.asString();
	Common::String destParam = d2.asString();

	bool success = copyGameFile(srcParam, destParam);

	g_lingo->push(Datum(success ? 0 : 1)); // Push 0 for success, 1 for failure
}

XOBJSTUB(MSFile::m_copyFiles, 0)
XOBJSTUB(MSFile::m_setAppINI, 0)
XOBJSTUB(MSFile::m_getAppINI, "")
XOBJSTUB(MSFile::m_getWindowsDirectory, "")
XOBJSTUB(MSFile::m_getWinVer, 0)
XOBJSTUB(MSFile::m_restartWindows, 0)
XOBJSTUB(MSFile::m_openFileDlg, "")
XOBJSTUB(MSFile::m_saveFileDlg, "")

void MSFile::m_getOFileName(int nargs) {
	Datum d3 = g_lingo->pop();
	Datum d2 = g_lingo->pop();
	Datum d1 = g_lingo->pop();

	Common::String defFile = d1.asString();
	Common::String filter = d2.asString();
	Common::String defExt = d3.asString();

	g_lingo->push(getFileNameFromModal(false, defFile, filter, defExt.c_str()));
}

XOBJSTUB(MSFile::m_getSFileName, "")
XOBJSTUB(MSFile::m_displayDlg, "")
XOBJSTUB(MSFile::m_progMgrAdd, 0)

void MSFile::open(ObjectType type, const Common::Path &path) {
	MSFileObject::initMethods(xlibMethods);
	MSFileObject *xobj = new MSFileObject(type);
	if (g_director->getVersion() >= 500)
		g_lingo->_openXtras.push_back(xlibName);
	g_lingo->exposeXObject(xlibName, xobj);
	g_lingo->initBuiltIns(xlibBuiltins);
}

void MSFile::close(ObjectType type) {
	g_lingo->_globalvars[xlibName] = Datum();
}
} // End of namespace Director
