#include "lib_ccx.h"
#include "ccx_common_option.h"
#include "activity.h"
#include "file_buffer.h"
long FILEBUFFERSIZE = 1024*1024*16; // 16 Mbytes no less. Minimize number of real read calls()

#ifdef _WIN32
WSADATA wsaData = {0};
int iResult = 0;
#endif

LLONG get_file_size (int in)
{
	int ret = 0;
	LLONG current = LSEEK (in, 0, SEEK_CUR);
	LLONG length  = LSEEK (in, 0, SEEK_END);
	if(current < 0 ||length < 0)
		return -1;

	ret = LSEEK (in, current, SEEK_SET);
	if (ret < 0)
		return -1;

	return length;
}

LLONG get_total_file_size (struct lib_ccx_ctx *ctx) // -1 if one of the file(s) failed to open
{
	LLONG ts=0;
	int h;
	for (int i = 0; i < ctx->num_input_files; i++)
	{
		if (0 == strcmp(ctx->inputfile[i], "-")) // Skip stdin
			continue;
#ifdef _WIN32
		h = OPEN (ctx->inputfile[i], O_RDONLY | O_BINARY);
#else
		h = OPEN (ctx->inputfile[i], O_RDONLY);
#endif

		if (h == -1) {
			switch (errno)
			{
			case ENOENT:
				return -1 * ENOENT;
			case EACCES:
				return -1 * EACCES;
			case EINVAL:
				return -1 * EINVAL;
			case EMFILE:
				return -1 * EMFILE;
			default:
				return -1;
			}
		}

		if (!ccx_options.live_stream)
			ts += get_file_size (h);
		close (h);
	}
	return ts;
}

void prepare_for_new_file (struct lib_ccx_ctx *ctx)
{
	// Init per file variables
	ctx->last_reported_progress =-1;
	ctx->stat_numuserheaders    = 0;
	ctx->stat_dvdccheaders      = 0;
	ctx->stat_scte20ccheaders   = 0;
	ctx->stat_replay5000headers = 0;
	ctx->stat_replay4000headers = 0;
	ctx->stat_dishheaders       = 0;
	ctx->stat_hdtv              = 0;
	ctx->stat_divicom           = 0;
	total_frames_count          = 0;
	ctx->false_pict_header      = 0;
	frames_since_ref_time       = 0;
	gop_time.inited             = 0;
	first_gop_time.inited       = 0;
	gop_rollover                = 0;
	printed_gop.inited          = 0;
	pts_big_change              = 0;
	firstcall                   = 1;

	if(ctx->epg_inited)
	{
		for(int x = 0; x < 0xfff; x++)
		{
			ctx->epg_buffers[x].buffer   = NULL;
			ctx->epg_buffers[x].ccounter = 0;
		}
		for (int i = 0; i < TS_PMT_MAP_SIZE; i++)
		{
			ctx->eit_programs[i].array_len = 0;
			ctx->eit_current_events[i] = -1;
		}
		ctx->epg_last_output      = -1;
		ctx->epg_last_live_output = -1;
	}
}

/* Close input file if there is one and let the GUI know */
void close_input_file (struct lib_ccx_ctx *ctx)
{
	ctx->demux_ctx->close(ctx->demux_ctx);
}

/* Close current file and open next one in list -if any- */
/* bytesinbuffer is the number of bytes read (in some buffer) that haven't been added
to 'past' yet. We provide this number to switch_to_next_file() so a final sanity check
can be done */

int switch_to_next_file (struct lib_ccx_ctx *ctx, LLONG bytesinbuffer)
{
	int ret = 0;
	if (ctx->current_file == -1 || !ccx_options.binary_concat)
	{
		ctx->demux_ctx->reset(ctx->demux_ctx);
	}

	switch(ccx_options.input_source)
	{
		case CCX_DS_STDIN:
		case CCX_DS_NETWORK:
		case CCX_DS_TCP:
			ret = ctx->demux_ctx->open(ctx->demux_ctx, NULL);
			if (ret < 0)
				return 0;
			else if (ret)
				return ret;
			else
				return 1;
			break;
		default:
			break;
	}
	/* Close current and make sure things are still sane */
	if (ctx->demux_ctx->is_open(ctx->demux_ctx))
	{
		dbg_print(CCX_DMT_708, "[CEA-708] The 708 decoder was reset [%d] times.\n", ctx->freport.data_from_708->reset_count);
		if (ccx_options.print_file_reports)
			print_file_report(ctx);

		if (ctx->inputsize > 0 && ((ctx->demux_ctx->past+bytesinbuffer) < ctx->inputsize) && is_decoder_processed_enough(ctx) == CCX_FALSE)
		{
			mprint("\n\n\n\nATTENTION!!!!!!\n");
			mprint("In switch_to_next_file(): Processing of %s %d ended prematurely %lld < %lld, please send bug report.\n\n",
					ctx->inputfile[ctx->current_file], ctx->current_file, ctx->demux_ctx->past, ctx->inputsize);
		}
		close_input_file (ctx);

		if (ccx_options.binary_concat)
		{
			ctx->total_past     += ctx->inputsize;
			ctx->demux_ctx->past = 0; // Reset always or at the end we'll have double the size
		}
	}
	for (;;)
	{
		ctx->current_file++;
		if (ctx->current_file >= ctx->num_input_files)
			break;

		// The following \n keeps the progress percentage from being overwritten.
		mprint ("\n\r-----------------------------------------------------------------\n");
		mprint ("\rOpening file: %s\n", ctx->inputfile[ctx->current_file]);
		ret = ctx->demux_ctx->open(ctx->demux_ctx, ctx->inputfile[ctx->current_file]);
		if (ret < 0)
			mprint ("\rWarning: Unable to open input file [%s]\n", ctx->inputfile[ctx->current_file]);
		else
		{
			activity_input_file_open (ctx->inputfile[ctx->current_file]);
			if (!ccx_options.live_stream)
			{
				ctx->inputsize = ctx->demux_ctx->get_filesize (ctx->demux_ctx);
				if (!ccx_options.binary_concat)
					ctx->total_inputsize = ctx->inputsize;
			}
			return 1; // Succeeded
		}
	}
	return 0;
}

void position_sanity_check(struct ccx_demuxer *ctx)
{
#ifdef SANITY_CHECK
	if (ctx->infd!=-1)
	{
		LLONG realpos = LSEEK (ctx->infd,0,SEEK_CUR);
		if (realpos == -1) // Happens for example when infd==stdin.
			return;
		if (realpos != ctx->past - ctx->filebuffer_pos + ctx->bytesinbuffer)
		{
			fatal (CCX_COMMON_EXIT_BUG_BUG, "Position desync, THIS IS A BUG. Real pos =%lld, past=%lld.\n", realpos, ctx->past);
		}
	}
#endif
}


int init_file_buffer(struct ccx_demuxer *ctx)
{
	ctx->filebuffer_start = 0;
	ctx->filebuffer_pos = 0;
	if (ctx->filebuffer == NULL)
	{
		ctx->filebuffer = (unsigned char *) malloc (FILEBUFFERSIZE);
		ctx->bytesinbuffer = 0;
	}
	if (ctx->filebuffer == NULL)
	{
		return -1;
	}
	return 0;
}

void buffered_seek (struct ccx_demuxer *ctx, int offset)
{
	position_sanity_check(ctx);
	if (offset < 0)
	{
		ctx->filebuffer_pos += offset;
		if (ctx->filebuffer_pos < 0)
		{
			// We got into the start buffer (hopefully)
			if ((ctx->filebuffer_pos + ctx->startbytes_pos) < 0)
			{
				fatal (CCX_COMMON_EXIT_BUG_BUG, "PANIC: Attempt to seek before buffer start, this is a bug!");
			}
			ctx->startbytes_pos += ctx->filebuffer_pos;
			ctx->filebuffer_pos = 0;
		}
	}
	else
	{
		buffered_read_opt (ctx, NULL, offset);
		position_sanity_check(ctx);
	}
}

void sleepandchecktimeout (time_t start)
{
	if (ccx_options.input_source == CCX_DS_STDIN)
	{
		// CFS: Not 100% sure about this. Fine for files, not so sure what happens if stdin is
		// real time input from hardware.
		sleep_secs (1);
		ccx_options.live_stream = 0;
		return;
	}

	if (ccx_options.live_stream == -1) // Just sleep, no timeout to check
	{
		sleep_secs (1);
		return;
	}
	if (time(NULL) > start + ccx_options.live_stream) // More than live_stream seconds elapsed. No more live
		ccx_options.live_stream = 0;
	else
		sleep_secs(1);
}

void return_to_buffer (struct ccx_demuxer *ctx, unsigned char *buffer, unsigned int bytes)
{
	if (bytes == ctx->filebuffer_pos)
	{
		// Usually we're just going back in the buffer and memcpy would be
		// unnecessary, but we do it in case we intentionally messed with the
		// buffer
		memcpy (ctx->filebuffer, buffer, bytes);
		ctx->filebuffer_pos = 0;
		return;
	}
	if (ctx->filebuffer_pos > 0) // Discard old bytes, because we may need the space
	{
		// Non optimal since data is moved later again but we don't care since
		// we're never here in ccextractor.
		memmove (ctx->filebuffer, ctx->filebuffer + ctx->filebuffer_pos, ctx->bytesinbuffer-ctx->filebuffer_pos);
		ctx->bytesinbuffer  = 0;
		ctx->filebuffer_pos = 0;
	}

	if (ctx->bytesinbuffer + bytes > FILEBUFFERSIZE)
		fatal (CCX_COMMON_EXIT_BUG_BUG, "Invalid return_to_buffer() - please submit a bug report.");

	memmove (ctx->filebuffer + bytes, ctx->filebuffer, ctx->bytesinbuffer);
	memcpy (ctx->filebuffer, buffer, bytes);
	ctx->bytesinbuffer += bytes;
}

/**
 * @param buffer can be NULL, in case when user want to just buffer it or skip some data.
 *
 * Global options that have effect on this function are following
 * 1) ccx_options.live_stream
 * 2) ccx_options.buffer_input
 * 3) ccx_options.input_source
 * 4) ccx_options.binary_concat
 *
 * TODO instead of using global ccx_options move them to ccx_demuxer
 */
size_t buffered_read_opt (struct ccx_demuxer *ctx, unsigned char *buffer, size_t bytes)
{
	size_t origin_buffer_size = bytes;
	size_t copied   = 0;
	time_t seconds = 0;

	position_sanity_check(ctx);

	if (ccx_options.live_stream > 0)
		time (&seconds);

	if (ccx_options.buffer_input || ctx->filebuffer_pos < ctx->bytesinbuffer)
	{
		// Needs to return data from filebuffer_start+pos to filebuffer_start+pos+bytes-1;
		int eof = (ctx->infd == -1);

		while ((!eof || ccx_options.live_stream) && bytes)
		{
			if (terminate_asap)
				break;
			if (eof)
			{
				// No more data available immediately, we sleep a while to give time
				// for the data to come up
				sleepandchecktimeout (seconds);
			}
			size_t ready = ctx->bytesinbuffer - ctx->filebuffer_pos;
			if (ready == 0) // We really need to read more
			{
				if (!ccx_options.buffer_input)
				{
					// We got in the buffering code because of the initial buffer for
					// detection stuff. However we don't want more buffering so
					// we do the rest directly on the final buffer.
					int i;
					do
					{
						// No code for network support here, because network is always
						// buffered - if here, then it must be files.
						if (buffer != NULL) // Read
						{
							i = read (ctx->infd, buffer, bytes);
							if( i == -1)
								fatal (EXIT_READ_ERROR, "Error reading input file!\n");
							buffer += i;
						}
						else // Seek
						{
							LLONG op, np;
							op = LSEEK (ctx->infd, 0, SEEK_CUR); // Get current pos
							if (op + bytes < 0) // Would mean moving beyond start of file: Not supported
								return 0;
							np = LSEEK (ctx->infd, bytes, SEEK_CUR); // Pos after moving
							i = (int) (np - op);
						}
						// if both above lseek returned -1 (error); i would be 0 here and
						// in case when its not live stream copied would decrease and bytes would...
						if (i == 0 && ccx_options.live_stream)
						{
							if (ccx_options.input_source == CCX_DS_STDIN)
							{
								ccx_options.live_stream = 0;
								break;
							}
							else
							{
								sleepandchecktimeout (seconds);
							}
						}
						else
						{
							copied += i;
							bytes  -= i;
						}

					}
					while ((i || ccx_options.live_stream ||
								(ccx_options.binary_concat && switch_to_next_file(ctx->parent, copied))) && bytes);
					return copied;
				}
				// Keep the last 8 bytes, so we have a guaranteed
				// working seek (-8) - needed by mythtv.
				int keep = ctx->bytesinbuffer > 8 ? 8 : ctx->bytesinbuffer;
				memmove (ctx->filebuffer, ctx->filebuffer+(FILEBUFFERSIZE-keep),keep);
				int i;
				if (ccx_options.input_source == CCX_DS_FILE || ccx_options.input_source == CCX_DS_STDIN)
					i = read (ctx->infd, ctx->filebuffer + keep, FILEBUFFERSIZE-keep);
				else if (ccx_options.input_source == CCX_DS_TCP)
					i = net_tcp_read(ctx->infd, (char *) ctx->filebuffer + keep, FILEBUFFERSIZE - keep);
				else
					i = net_udp_read(ctx->infd, (char*) ctx->filebuffer + keep, FILEBUFFERSIZE - keep, ccx_options.udpsrc, ccx_options.udpaddr);
				if (terminate_asap) /* Looks like receiving a signal here will trigger a -1, so check that first */
					break;
				if (i == -1)
					fatal (EXIT_READ_ERROR, "Error reading input stream!\n");
				if (i == 0)
				{
					/* If live stream, don't try to switch - acknowledge eof here as it won't
					   cause a loop end */
					if (ccx_options.live_stream || ((struct lib_ccx_ctx *)ctx->parent)->inputsize <= origin_buffer_size || !(ccx_options.binary_concat && switch_to_next_file(ctx->parent, copied)))
						eof = 1;
				}
				ctx->filebuffer_pos = keep;
				ctx->bytesinbuffer = (int) i + keep;
				ready = i;
			}
			int copy = (int) (ready>=bytes ? bytes:ready);
			if (copy)
			{
				if (buffer != NULL)
				{
					memcpy (buffer, ctx->filebuffer + ctx->filebuffer_pos, copy);
					buffer += copy;
				}
				ctx->filebuffer_pos += copy;
				bytes  -= copy;
				copied += copy;
			}
		}
	}
	else // Read without buffering
	{

		if (buffer != NULL)
		{
			int i;
			while (bytes > 0 && ctx->infd != -1 &&
					((i = read(ctx->infd, buffer, bytes)) != 0 || ccx_options.live_stream ||
					 (ccx_options.binary_concat && switch_to_next_file(ctx->parent, copied))))
			{
				if (terminate_asap)
					break;
				if( i == -1)
					fatal (EXIT_READ_ERROR, "Error reading input file!\n");
				else if (i == 0)
					sleepandchecktimeout (seconds);
				else
				{
					copied += i;
					bytes  -= i;
					buffer += i;
				}
			}
			return copied;
		}
		while (bytes != 0 && ctx->infd != -1)
		{
			LLONG op, np;
			if (terminate_asap)
				break;
			op = LSEEK (ctx->infd, 0, SEEK_CUR); // Get current pos
			if (op + bytes < 0) // Would mean moving beyond start of file: Not supported
				return 0;

			np     = LSEEK (ctx->infd, bytes, SEEK_CUR); // Pos after moving
			if (op == -1 && np == -1) // Possibly a pipe that doesn't like "skipping"
			{
				char c;
				for (size_t i = 0; i<bytes; i++) read(ctx->infd, &c, 1);
				copied = bytes;
			}
			else
				copied = copied + (np - op);
			bytes  = bytes- (unsigned int) copied;
			if (copied == 0)
			{
				if (ccx_options.live_stream)
					sleepandchecktimeout (seconds);
				else
				{
					if (ccx_options.binary_concat)
						switch_to_next_file(ctx->parent, 0);
					else
						break;
				}
			}
		}
	}
	return copied;
}

uint16_t buffered_get_be16(struct ccx_demuxer *ctx)
{
	unsigned char a,b;
	unsigned char *a_p = &a; // Just to suppress warnings
	unsigned char *b_p = &b;
	buffered_read_byte(ctx, a_p);
	ctx->past++;
	buffered_read_byte(ctx, b_p);
	ctx->past++;
	return ( (unsigned short) (a<<8) )| ( (unsigned short) b);
}

unsigned char buffered_get_byte (struct ccx_demuxer *ctx)
{
	unsigned char b;
	unsigned char *b_p = &b;
	size_t result;

	result = buffered_read_byte(ctx, b_p);
	if (result == 1)
	{
		ctx->past++;
		return b;
	}
	else
		return 0;
}

uint32_t buffered_get_be32(struct ccx_demuxer *ctx)
{
	unsigned int val;
	val = buffered_get_be16(ctx) << 16;
	val |= buffered_get_be16(ctx);
	return val;
}

unsigned short buffered_get_le16(struct ccx_demuxer *ctx)
{
	unsigned char a,b;
	unsigned char *a_p = &a; // Just to suppress warnings
	unsigned char *b_p = &b;
	buffered_read_byte(ctx, a_p);
	ctx->past++;
	buffered_read_byte(ctx, b_p);
	ctx->past++;
	return ( (unsigned short) (b<<8) )| ( (unsigned short) a);
}

unsigned int buffered_get_le32(struct ccx_demuxer *ctx)
{
	unsigned int val;
	val = buffered_get_le16(ctx);
	val |= buffered_get_le16(ctx) << 16;
	return val;
}

uint64_t buffered_get_be64(struct ccx_demuxer *ctx)
{
	uint64_t val;
	val = (uint64_t)buffered_get_be32(ctx) << 32;
	val |= buffered_get_be32(ctx);
	return val;
}
