/*
 *
 *  Singe 2
 *  Copyright (C) 2019 Scott Duensing <scott@kangaroopunch.com>
 *
 * 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.
 *
 * 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.
 *
 */


#include <stdio.h>
#include <getopt.h>

#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <SDL2/SDL_mixer.h>
#include <SDL2/SDL_ttf.h>
#include <manymouse.h>

#include "stddclmr.h"
#include "common.h"
#include "util.h"
#include "videoPlayer.h"
#include "singe.h"
#include "extensions.h"


typedef struct RatioS {
	int aspectNum;
	int aspectDom;
} RatioT;

typedef struct ResolutionS {
	int width;
	int height;
} ResolutionT;

typedef struct ModeS {
	RatioT      ratio;
	ResolutionT resolution;
} ModeT;


void showUsage(char *name, char *message);


// /home/scott/code/singe2/videotest/Stargate.singe
// /home/scott/code/singe2/videotest/TestClip_720x480_29.97fps_2000_frames.avi


__attribute__((noreturn))
void showUsage(char *name, char *message) {
	utilSay(" ___ ___ _  _  ___ ___");
	utilSay("/ __|_ _| \\| |/ __| __|   Somewhat Interactive Nostalgic Game Emulator %s", VERSION_STRING);
	utilSay("\\__ \\| || .` | (_ | _|    Copyright (c) 2006-%s Scott C. Duensing", COPYRIGHT_END_YEAR);
	utilSay("|___/___|_|\\_|\\___|___|   https://kangaroopunch.com");
	utilSay("");
	utilSay("Usage:  %s [OPTIONS] scriptName{.singe}", utilGetLastPathComponent(name));
	utilSay("");
	utilSay("  -v, --framefile=FILENAME       use an alternate video file");
	utilSay("  -d, --datadir=PATHNAME         alternate location for written files");
	utilSay("  -m, --nomouse                  disable mouse");
	utilSay("  -n, --noserversend             do not send usage statistics");
	utilSay("  -s, --nosound, --mutesound     mutes all sound");
	utilSay("  -f, --fullscreen               run in full screen mode");
	utilSay("  -w, --fullscreen_window        run in windowed full screen mode");
	utilSay("  -l, --volume_vldp=PERCENT      specify laserdisc volume in percent");
	utilSay("  -e, --volume_nonvldp=PERCENT   specify sound effects volume in percent");
	//       00000000011111111112222222222333333333344444444445555555555666666666677777777778
	//       12345678901234567890123456789012345678901234567890123456789012345678901234567890
	utilSay("  -z, --scalefactor=PERCENT      reduce screen size for overscan compensation");
	utilSay("  -a, --aspect=N:D               force aspect ratio");
	utilSay("  -u, --stretch                  use ugly stretched video");
	utilSay("  -x, --xresolution=VALUE        specify horizontal resolution");
	utilSay("  -y, --yresolution=VALUE        specify vertical resolution");
	utilSay("  -h, --help                     this display");
	utilSay("");
	if (message) {
		utilSay("Error:  %s", message);
		utilSay("");
		exit(1);
	}
	exit(0);
}


int main(int argc, char *argv[]) {

	int                         x              =  0;
	int                         err            =  0;
	int                         flags          =  0;
	int                         optionIndex    =  0;
	int                         bestResIndex   = -1;
	int                         bestRatioIndex = -1;
	int                         aspectNum      = -1;
	int                         aspectDom      = -1;
	char                       *temp           =  NULL;
	char                       *aspectString   =  NULL;
	float                       thisRatio      =  0;
	float                       bestRatio      =  9999;
	SDL_Window                 *window         =  NULL;
	SDL_Renderer               *renderer       =  NULL;
	SDL_DisplayMode             mode;
	static struct option        options[]      = {
		{ "nomouse",           no_argument,       NULL, 'm' },
		{ "noserversend",      no_argument,       NULL, 'n' },
		{ "nosound",           no_argument,       NULL, 's' },
		{ "mutesound",         no_argument,       NULL, 's' },
		{ "fullscreen",        no_argument,       NULL, 'f' },
		{ "fullscreen_window", no_argument,       NULL, 'w' },
		{ "stretch",           no_argument,       NULL, 'u' },
		{ "datadir",           optional_argument, NULL, 'd' },
		{ "framefile",         optional_argument, NULL, 'v' },
		{ "volume_vldp",       optional_argument, NULL, 'l' },
		{ "volume_nonlvdp",    optional_argument, NULL, 'e' },
		{ "scalefactor",       optional_argument, NULL, 'z' },
		{ "aspect",            optional_argument, NULL, 'a' },
		{ "xresolution",       optional_argument, NULL, 'x' },
		{ "yresolution",       optional_argument, NULL, 'y' },
		{ "help",              no_argument,       NULL, 'h' },
		{ NULL,                0,                 NULL,  0  }
	};
	static ModeT                modes[]        = {
		{ {  4,  3 }, {  640,  480 } },
		{ {  4,  3 }, {  800,  600 } },
		{ {  4,  3 }, {  960,  720 } },
		{ {  4,  3 }, { 1024,  768 } },
		{ {  4,  3 }, { 1280,  960 } },
		{ {  4,  3 }, { 1400, 1050 } },
		{ {  4,  3 }, { 1440, 1080 } },
		{ {  4,  3 }, { 1600, 1200 } },
		{ {  4,  3 }, { 1856, 1392 } },
		{ {  4,  3 }, { 1920, 1440 } },
		{ {  4,  3 }, { 2048, 1536 } },
		{ { 16,  9 }, { 1024,  576 } },
		{ { 16,  9 }, { 1152,  648 } },
		{ { 16,  9 }, { 1280,  720 } },
		{ { 16,  9 }, { 1366,  768 } },
		{ { 16,  9 }, { 1600,  900 } },
		{ { 16,  9 }, { 1920, 1080 } },
		{ { 16,  9 }, { 2560, 1440 } },
		{ { 16,  9 }, { 3840, 2160 } },
		{ { 16,  9 }, { 7680, 4320 } },
		{ { 16, 10 }, { 1280,  800 } },
		{ { 16, 10 }, { 1440,  900 } },
		{ { 16, 10 }, { 1680, 1050 } },
		{ { 16, 10 }, { 1920, 1200 } },
		{ { 16, 10 }, { 2560, 1600 } },
		{ {  0,  0 }, {    0,    0 } }
	};

	// Parse command line
	while (true) {
		optionIndex = 0;
		opterr = 0;
		x = getopt_long(argc, argv, "mnsfwha:d:v:l:e:z:x:y:", options, &optionIndex);

		// Out of options?
		if (x == -1) break;

		// Handle options
		switch (x) {
			// No Mouse
			case 'm':
				_confNoMouse = true;
				break;

			// No Server Statss
			case 'n':
				_confNoStats = true;
				break;

			// No Sound
			case 's':
				_confNoSound = true;
				break;

			// Full Screen
			case 'f':
				_confFullScreen = true;
				break;

			// Full Screen Windowed
			case 'w':
				_confFullScreenWindow = true;
				break;

			// Ugly Stretched Video
			case 'u':
				_confStretchVideo = true;
				break;

			// Video File
			case 'v':
				if (_confVideoFile) free(_confVideoFile);
				_confVideoFile = strdup(optarg);
				break;

			// Data Dir
			case 'd':
				if (_confDataDir) free(_confDataDir);
				_confDataDir = strdup(optarg);
				break;

			// Video Volume
			case 'l':
				_confVolumeVldp = atoi(optarg);
				break;

			// Effects Volume
			case 'e':
				_confVolumeNonVldp = atoi(optarg);
				break;

			// Zoom
			case 'z':
				_confScaleFactor = atoi(optarg);
				break;

			// Aspect
			case 'a':
				if (aspectString) free(aspectString);
				aspectString = strdup(optarg);
				break;

			// X Resolution
			case 'x':
				_confXResolution = atoi(optarg);
				break;

			// Y Resolution
			case 'y':
				_confYResolution = atoi(optarg);
				break;

			// Help
			case 'h':
				showUsage(argv[0], NULL);
				break;

			case '?':
				showUsage(argv[0], "Unknown option.");
				break;

			default:
				abort();  // Something bad happened
				break;
		}
	}

	// Did we get a filename or path to open?
	if (optind != (argc - 1)) showUsage(argv[0], "No script file specified.");
	_confScriptFile = strdup(argv[optind]);
	// Exists?
	if (!utilFileExists(_confScriptFile)) {
		// Missing.  Is a path?
		if (utilPathExists(_confScriptFile)) {
			// See if the script exists in the path.
			temp = utilCreateString("%s%c%s.singe", _confScriptFile, utilGetPathSeparator(), utilGetLastPathComponent(_confScriptFile));
			if (utilFileExists(temp)) {
				// Found script named for path inside path.
				free(_confScriptFile);
				_confScriptFile = temp;
				temp = NULL;
			} else {
				// Not in the path either.
				free(_confScriptFile);
				_confScriptFile = NULL;
			}
		} else {
			// Not a path either.
			free(_confScriptFile);
			_confScriptFile = NULL;
		}
	}
	if (!_confScriptFile) showUsage(argv[0], "Unable to locate script.");

	// Do we need to generate a video name?
	if (_confVideoFile) {
		if (!utilFileExists(_confVideoFile)) {
			free(_confVideoFile);
			_confVideoFile = NULL;
		}
	} else {
		x = (int)(strlen(_confScriptFile) - strlen(utilGetFileExtension(_confScriptFile))) - 1;
		if (x < 0) {
			x = 0;
		}
		temp = strdup(_confScriptFile);
		temp[x] = 0;
		// Check all known extensions - lower case only, Windows users!
		x = 0;
		while (ffmpegExtensions[x]) {
			_confVideoFile = utilCreateString("%s.%s", temp, ffmpegExtensions[x]);
			if (utilFileExists(_confVideoFile)) {
				break;
			}
			free(_confVideoFile);
			_confVideoFile = NULL;
			x++;
		}
		free(temp);
	}
	if (!_confVideoFile) showUsage(argv[0], "Unable to locate video.");

	// Do we need to generate a data directory name?
	if (_confDataDir) {
		if (!utilPathExists(_confDataDir)) {
			free(_confDataDir);
			_confDataDir = NULL;
		}
	} else {
		// Put it in the game folder.
		x = (int)(strlen(_confScriptFile) - strlen(utilGetLastPathComponent(_confScriptFile))) - 1;
		if (x < 0) {
			x = 0;
		}
		_confDataDir = strdup(_confScriptFile);
		_confDataDir[x] = 0;
	}
	if (!_confDataDir) showUsage(argv[0], "Unable to locate data directory.");

	// Do the full screen options make sense?
	if (_confFullScreen && _confFullScreenWindow) showUsage(argv[0], "Full Screen or Full Screen Windowed.  Pick one.");

	// Sane volume values?
	if ((_confVolumeVldp < 0) || (_confVolumeVldp > 100)) showUsage(argv[0], "Laserdisc volume must be between 0 and 100 percent.");
	if ((_confVolumeNonVldp < 0) || (_confVolumeNonVldp > 100)) showUsage(argv[0], "Effects volume must be between 0 and 100 percent.");

	// Sane scale factor?
	if ((_confScaleFactor < 50) || (_confScaleFactor > 100)) showUsage(argv[0], "Display scale must be between 50 and 100 percent.");

	// Init SDL
	if (SDL_Init(SDL_INIT_EVERYTHING) != 0) utilDie("%s", SDL_GetError());
	if (SDL_GetCurrentDisplayMode(0, &mode) < 0) utilDie("%s", SDL_GetError());

	// Determine resolution if not specified
	if ((_confXResolution <= 0) || (_confYResolution <= 0)) {
		// Did they specify an aspect ratio?
		if (aspectString) {
			aspectNum = atoi(aspectString);
			temp = strstr(aspectString, ":");
			if (temp) {
				temp++;
				aspectDom = atoi(temp);
				temp = NULL;
			}
			if ((aspectNum > 0) && (aspectDom > 0)) {
				// Do we understand what they asked for?
				x = 0;
				while (modes[x].ratio.aspectNum != 0) {
					if ((modes[x].ratio.aspectNum == aspectNum) && (modes[x].ratio.aspectDom == aspectDom)) {
						bestRatioIndex = x;
						break;
					}
					x++;
				}
			}
		} else {
			// Find our current aspect ratio
			x = 0;
			while (modes[x].ratio.aspectNum != 0) {
				thisRatio = fabsf(((float)modes[x].ratio.aspectNum / (float)modes[x].ratio.aspectDom) - ((float)mode.w / (float)mode.h));
				if (thisRatio < bestRatio) {
					bestRatio = thisRatio;
					bestRatioIndex = x;
				}
				x++;
			}
		}
		if (bestRatioIndex < 0) showUsage(argv[0], "Unknown aspect ratio.");
		x = 0;
		// Were both resolutions not specified?
		if ((_confXResolution <= 0) && (_confYResolution <= 0)) {
			// Are we full screen?
			if (_confFullScreen || _confFullScreenWindow) {
				// Use desktop resolution
				_confXResolution = mode.w;
				_confYResolution = mode.h;
			} else {
				// Find largest window that will fit on the screen but not fill it
				while (modes[x].ratio.aspectNum != 0) {
					if ((modes[x].ratio.aspectNum == modes[bestRatioIndex].ratio.aspectNum) && (modes[x].ratio.aspectDom == modes[bestRatioIndex].ratio.aspectDom)) {
						if ((modes[x].resolution.width < mode.w) && (modes[x].resolution.height < mode.h)) {
							bestResIndex = x;
						}
					}
					x++;
				}
				_confXResolution = modes[bestResIndex].resolution.width;
				_confYResolution = modes[bestResIndex].resolution.height;
			}
		} else {
			// Find unprovided width/height using provided value
			while (modes[x].ratio.aspectNum != 0) {
				// Is this the aspect ratio we're using?
				if ((modes[bestRatioIndex].ratio.aspectNum == modes[x].ratio.aspectNum) && (modes[bestRatioIndex].ratio.aspectDom == modes[x].ratio.aspectDom)) {
					// Do we have the width or height?
					if (_confXResolution > 0) {
						// We have the width.  Is this the matching entry?
						if (modes[x].resolution.width == _confXResolution) {
							bestResIndex = x;
							_confYResolution = modes[x].resolution.height;
							break;
						}
					} else {
						// We have the height.  Is this the matching entry?
						if (modes[x].resolution.height == _confYResolution) {
							bestResIndex = x;
							_confXResolution = modes[x].resolution.width;
							break;
						}
					}
				}
				x++;
			}
		}
	}
	// Did we end up with a valid resolution?
	if (_confXResolution <= 0) showUsage(argv[0], "Unable to determine X resolution.  (Is the Y value sane?)");
	if (_confYResolution <= 0) showUsage(argv[0], "Unable to determine Y resolution.  (Is the X value sane?)");
	if ((_confXResolution > mode.w) || (_confYResolution > mode.h)) showUsage(argv[0], "Specified resolution is larger than the display.");

	// Init SDL_mixer  ***FIX***
	flags = /* MIX_INIT_FLAC | */ MIX_INIT_MOD | MIX_INIT_MP3 | MIX_INIT_OGG | MIX_INIT_MID | MIX_INIT_OPUS;
	err = Mix_Init(flags);
	if (err != flags) utilDie("%s", Mix_GetError());

	// Init SDL_image  ***FIX***
	flags = IMG_INIT_JPG | IMG_INIT_PNG |  /* IMG_INIT_TIF | */ IMG_INIT_WEBP;
	err = IMG_Init(flags);
	if (err != flags) utilDie("%s", IMG_GetError());

	// Init SDL_ttf
	if (TTF_Init() < 0) utilDie("%s", TTF_GetError());

	// Create Resizable Window
	window = SDL_CreateWindow("SINGE", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, _confXResolution, _confYResolution, SDL_WINDOW_RESIZABLE);
	if (window == NULL) utilDie("%s", SDL_GetError());

	// Do we want full screen of some kind?
	if (_confFullScreen || _confFullScreenWindow) {
		flags = _confFullScreen ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_FULLSCREEN_DESKTOP;
		SDL_SetWindowFullscreen(window, (Uint32)flags);
	}

	// Create an accelerated renderer.
	renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
	if (renderer == NULL) utilDie("%s", SDL_GetError());

	// Clear screen with black
	SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
	SDL_RenderClear(renderer);

	// Create audio mixer device
	err = Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 44100 /* freq */ * 16  /* bits */ * 2 /* channels */ * 2 /* seconds */);
	if (err != 0) utilDie("%s", Mix_GetError());

	// Start our video playback system
	if (videoInit()) utilDie("Unable to initialize video player.");

	// Finish our setup
	SDL_DisableScreenSaver();

	singe(window, renderer);

	// Shutdown
	videoQuit();
	Mix_CloseAudio();
	SDL_DestroyRenderer(renderer);
	SDL_DestroyWindow(window);
	SDL_EnableScreenSaver();
	TTF_Quit();
	IMG_Quit();
	Mix_Quit();
	SDL_Quit();
	if (_confDataDir) free(_confDataDir);
	if (_confVideoFile) free(_confVideoFile);
	if (_confScriptFile) free(_confScriptFile);

	return 0;
}
