/*
 *
 *  Singe 2
 *  Copyright (C) 2006-2020 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 "thirdparty/uthash.h"

#include "util.h"
#include "videoPlayer.h"
#include "frameFile.h"


extern bool  _confShowCalculated;


typedef struct FrameLineS {
	int32_t  videoHandle;
	int64_t  lastFramePlayed;
	int64_t  frame;
	char    *filename;
} FrameLineT;


typedef struct FrameFileS {
	int32_t         id;
	int32_t         count;
	char           *videoPath;
	FrameLineT     *files;
	UT_hash_handle  hh;
} FrameFileT;


static FrameFileT *_frameFileHash =  NULL;
static int32_t     _nextId        =  0;


void _transferProperties(int32_t  oldHandle, int32_t  newHandle);


void _transferProperties(int32_t  oldHandle, int32_t  newHandle) {
	int32_t  l = 0;
	int32_t  r = 0;

	if ((oldHandle < 0) || (newHandle < 0)) {
		// Invalid handle somewhere
		return;
	}

	// Transfer previous video's properties to this one
	videoGetVolume(oldHandle, &l, &r);
	videoSetVolume(newHandle, l, r);
	if (videoIsPlaying(oldHandle)) {
		videoPlay(newHandle);
	} else {
		videoPause(newHandle);
	}
	videoPause(oldHandle);
}


int64_t frameFileGetFrame(int32_t frameFileHandle, int32_t videoHandle) {
	int32_t     i = 0;
	FrameFileT *f = NULL;

	// Get our framefile structure
	HASH_FIND_INT(_frameFileHash, &frameFileHandle, f);
	if (!f) utilDie("No framefile at index %d in frameFileSeek.", frameFileHandle);

	// Search through loaded video segments
	for (i=0; i<f->count; i++) {
		if (f->files[i].videoHandle == videoHandle) {
			// Frame is in this video
			return videoGetFrame(videoHandle) + f->files[i].frame;
		}
	}

	// Didn't find it.
	return -1;
}


int32_t frameFileInit(void) {
	return 0;  // Nothing to do
}


int32_t frameFileLoad(char *filename, char *indexPath, bool stretchVideo, SDL_Renderer *renderer) {
	int32_t     result    =  0;
	int32_t     count     =  0;
	int64_t     frame     =  0;
	int64_t     next      =  0;
	size_t      bytes     =  0;
	char       *audio     =  NULL;
	char       *data      =  NULL;
	char       *path      =  NULL;
	char       *offset    =  NULL;
	char       *frameLine =  NULL;
	char       *space     =  NULL;
	char       *endptr    =  NULL;
	char       *temp      =  NULL;
	FrameLineT *files     =  NULL;
	FrameLineT *newFiles  =  NULL;
	FrameFileT *frameFile =  NULL;

	data = utilReadFile(filename, &bytes);
	if (!data) utilDie("Unable to open framefile:  %s", filename);

	// Get path where video files live
	path = utilReadLine(data, bytes, &offset);
	if (!path) utilDie("Cannot read video path from framefile!");
	utilFixPathSeparators(&path, true);

	// If it's not an absolute path, pre-pend the path to the framefile
	if ((path[0] != utilGetPathSeparator()) && (path[1] != ':')) {
		temp = path;
		count = strlen(filename) - strlen(utilGetLastPathComponent(filename));
		path = malloc(sizeof(char) * (count + strlen(temp) + 1));
		memcpy(path, filename, count);
		memcpy(&path[count], temp, strlen(temp));
		path[count + strlen(temp)] = 0;
		free(temp);
		utilFixPathSeparators(&path, true);
		count = 0;
	}

	// Read frame offsets and filenames - silently ignore bad lines
	while ((frameLine = utilReadLine(data, bytes, &offset)) != NULL) {
		// Covert tabs to spaces
		for (next=0; next<(int64_t)strlen(frameLine); next++) {
			if (frameLine[next] == 9) frameLine[next] = 32;
		}
		// Find first space in this file.
		space = strstr(frameLine, " ");
		if (space) {
			// Write a zero in there to make it two fields.
			*space = 0;
			// Is the first part an integer?
			endptr = NULL;
			frame = strtol(frameLine, &endptr, 10);
			*space = 32;
			if (endptr == space) {
				// Got an integer.  Point at filename.
				space++;
				while (space[0] == 32) {
					space++;
				}
				// Copy frame number and filename into array
				newFiles = realloc(files, sizeof(FrameLineT) * (count + 1));
				if (!newFiles) utilDie("Unable to allocate new framefile entry!");
				files = newFiles;
				files[count].lastFramePlayed = 0;
				files[count].frame = frame;
				files[count].filename = utilCreateString("%s%s", path, space);
				// Is this an old m2v/ogg pair?
				if (strncmp(utilGetFileExtension(files[count].filename), "m2v", 3) == 0) {
					// Open split video and audio files
					audio = strdup(files[count].filename);
					audio[strlen(audio) - 3] = 'o';
					audio[strlen(audio) - 2] = 'g';
					audio[strlen(audio) - 1] = 'g';
					if (utilFileExists(audio)) {
						files[count].videoHandle = videoLoadWithAudio(files[count].filename, audio, indexPath, stretchVideo, renderer);
					} else {
						files[count].videoHandle = videoLoadWithAudio(files[count].filename, NULL, indexPath, stretchVideo, renderer);
					}
					free(audio);
					audio = NULL;
				} else {
					// Open combined video/audio file
					files[count].videoHandle = videoLoad(files[count].filename, indexPath, stretchVideo, renderer);
				}
				count++;
			}
		}
		free(frameLine);
	}

	// Allocate new framefile
	frameFile = malloc(sizeof(FrameFileT));
	frameFile->id = _nextId;
	frameFile->count = count;
	frameFile->videoPath = path;
	frameFile->files = files;
	HASH_ADD_INT(_frameFileHash, id, frameFile);

	result = _nextId++;

	// Show debug output?
	if (_confShowCalculated) {
		//       00000000011111111112222222222333333333344444444445555555555666666666677777777778
		//       12345678901234567890123456789012345678901234567890123456789012345678901234567890
		utilSay("Existing Framefile:\n");
		utilSay("   Start    Length       End  File");
		utilSay("--------  --------  --------  -------------------------------------------------");
		for (count=0; count<frameFile->count; count++) {
			frame = videoGetFrameCount(frameFile->files[count].videoHandle);
			utilSay("%8ld  %8ld  %8ld  %s", frameFile->files[count].frame, frame, frameFile->files[count].frame + frame, frameFile->files[count].filename);
		}
		next = 0;
		utilSay("\nIdeal Framefile:\n");
		utilSay("   Start  File");
		utilSay("--------  ---------------------------------------------------------------------");
		for (count=0; count<frameFile->count; count++) {
			frame = videoGetFrameCount(frameFile->files[count].videoHandle);
			utilSay("%8ld  %s", next, frameFile->files[count].filename);
			next += frame + 1;
		}
		utilSay("");
	}

	return result;
}


int32_t frameFileSeek(int32_t frameFileHandle, int64_t seekFrame, int32_t *videoHandle, int64_t *actualFrame) {
	int32_t     i     =  0;
	int32_t     found = -1;
	FrameFileT *f     =  NULL;

	// Get our framefile structure
	HASH_FIND_INT(_frameFileHash, &frameFileHandle, f);
	if (!f) utilDie("No framefile at index %d in frameFileSeek.", frameFileHandle);

	// Search through loaded video segments

	// Daphne-like framefile searching
	found = 0;
	for (i=0; i<f->count; i++) {
		if (seekFrame >= f->files[i].frame) {
			found = i;
		}
	}

	/*
	// Strict framefile searching
	for (i=0; i<f->count; i++) {
		if ((seekFrame >= f->files[i].frame) && (seekFrame <= (f->files[i].frame + videoGetFrameCount(f->files[i].videoHandle)))) {
			found = i;
			break;
		}
	}
	*/

	if (found >= 0) {
		// Frame is in this video
		*actualFrame = seekFrame - f->files[found].frame;
		//utilSay("Frame %ld found in file %d - %s at %ld", seekFrame, found, f->files[found].filename, *actualFrame);
		f->files[found].lastFramePlayed = *actualFrame;
		videoSeek(f->files[found].videoHandle, *actualFrame);
		// Is this a different video from the previous one?
		if (*videoHandle != f->files[found].videoHandle) {
			// Yes
			_transferProperties(*videoHandle, f->files[found].videoHandle);
			*videoHandle = f->files[found].videoHandle;
		}
		return 0;
	}

	// Didn't find it
	*videoHandle = -1;
	*actualFrame = -1;
	return 1;
}


int32_t frameFileQuit(void) {
	FrameFileT *f = NULL;
	FrameFileT *t = NULL;

	// Unload any remaining frameFiles
	HASH_ITER(hh, _frameFileHash, f, t) {
		frameFileUnload(f->id);
	}

	return 0;
}


int32_t frameFileUnload(int32_t frameFileHandle) {
	int32_t     i = 0;
	FrameFileT *f = NULL;

	// Get our framefile structure
	HASH_FIND_INT(_frameFileHash, &frameFileHandle, f);
	if (!f) utilDie("No framefile at index %d in frameFileUnload.", frameFileHandle);

	// Unload videos
	for (i=0; i<f->count; i++) {
		free(f->files[i].filename);
		videoUnload(f->files[i].videoHandle);
	}

	// Free memory
	free(f->files);
	free(f->videoPath);

	// Remove from hash
	HASH_DEL(_frameFileHash, f);
	free(f);

	return 0;
}


int32_t frameFileUpdate(int32_t frameFileHandle, int32_t *videoHandle) {
	int32_t     i     = 0;
	int64_t     frame = 0;
	FrameFileT *f     = NULL;

	// Get our framefile structure
	HASH_FIND_INT(_frameFileHash, &frameFileHandle, f);
	if (!f) utilDie("No framefile at index %d in frameFileUpdate.", frameFileHandle);

	// Did the current video loop?  If so, move to next video.
	for (i=0; i<f->count; i++) {
		if (f->files[i].videoHandle == *videoHandle) {
			frame = videoGetFrame(*videoHandle);
			if ((f->files[i].lastFramePlayed > frame) && (frame == 0)) {
				// Switch video files
				if (i == (f->count - 1)) {
					i = 0;
				} else {
					i++;
				}
				_transferProperties(*videoHandle, f->files[i].videoHandle);
				*videoHandle = f->files[i].videoHandle;
			}
			break;
		}
	}

	return 0;
}
