/*
 * Copyright (c) 2006-2006 Endace Technology Ltd, Hamilton, New Zealand.
 * All rights reserved.
 *
 * This source code is proprietary to Endace Technology Limited and no part
 * of it may be redistributed, published or disclosed except as outlined in
 * the written contract supplied with this product.
 *
 * $Id: dagsnap_model.c 5772 2006-11-14 00:41:51Z andras $
 */

/* dagsnap headers. */
#include "dagsnap_model.h"

/* Endace headers. */
#include "dagapi.h"
#include "dagutil.h"

/* CVS Header. */
static const char* const kCvsHeader __attribute__ ((unused)) = "$Id: dagsnap_model.c 5772 2006-11-14 00:41:51Z andras $";
static const char* const kRevisionString = "$Revision: 5772 $";


#define OPTBUFSIZE ONE_KIBI


/* Put all state inside a structure. */
typedef struct dagsnap_state_
{
#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || defined(__APPLE__)

	pthread_cond_t cond;
	pthread_mutex_t mutex;
	FILE * outfile;
	int outfd;
	
#elif defined(_WIN32) 

	int outfile;
	
#endif /* Platform-specific code. */

	int dagfd;
	DagsnapConfigPtr config;
	int report_now;
	int stop_now;
	dagsnap_info_t info;
	DagsnapInfoPtr client_info;

} dagsnap_state_t, * DagsnapStatePtr;


/* Internal routines. */
static DagsnapStatePtr dagsnap_state_init(DagsnapConfigPtr config);
static void dagsnap_state_dispose(DagsnapStatePtr state);
static void update_view_state(DagsnapStatePtr state);


/* Implementation of internal routines. */
static DagsnapStatePtr
dagsnap_state_init(DagsnapConfigPtr config)
{
	DagsnapStatePtr result = (DagsnapStatePtr) dagutil_malloc(sizeof(*result));
	
	assert(config);
	
	memset(result, 0, sizeof(*result));
	
	result->dagfd = -1;
	result->config = config;
	
#if defined(_WIN32) 

	result->outfile = -1;

#elif defined (__FreeBSD__) || defined(__linux__) || defined(__NetBSD__) || (defined(__SVR4) && defined(__sun)) || defined(__APPLE__)

	result->outfd = -1;
	pthread_cond_init(&result->cond, NULL);
	pthread_mutex_init(&result->mutex, NULL);

#endif /* Platform-specific code. */

	return result;
}


static void
dagsnap_state_dispose(DagsnapStatePtr state)
{
	assert(state);
	
#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || defined(__APPLE__)

	pthread_cond_destroy(&state->cond);
	pthread_mutex_destroy(&state->mutex);

#elif defined(_WIN32)


#endif /* Platform-specific code. */
	
	dagsnap_config_dispose(state->config);

	/* Shut down file descriptors. */
	if (-1 != state->dagfd)
	{
		dag_close(state->dagfd);
		state->dagfd = -1;
	}
	
#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || defined(__APPLE__)

	if (state->outfile)
	{
		fclose(state->outfile);
	}
	state->outfile = NULL;
	state->outfd = -1;

#elif defined(_WIN32)

	close(state->outfile);
	state->outfile = -1;
	
#endif /* Platform-specific code. */

	dagutil_free(state);
}


/* The only reason to use condition variables and a mutex is to provide the view with consistent information.
 * If we were willing to sacrifice 100% accurate information then it would be OK to have the view get the 
 * information it uses directly.  But occasionally the capture thread would be halfway through updating a 
 * 64-bit variable when the display thread prints it out, and the result might be garbage.
 */
static void
update_view_state(DagsnapStatePtr state)
{
	/* Copy information to client's copy so client can access it in a consistent state. */
	memcpy(state->client_info, &state->info, sizeof(state->info));
	
	state->report_now = 0;
	
#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || defined(__APPLE__)

	/* Notify the client. */
	pthread_cond_signal(&state->cond);

#elif defined(_WIN32)


#endif /* Platform-specific code. */
}


/* Public routines. */
DagsnapPtr
dagsnap_create(DagsnapConfigPtr config)
{
	DagsnapStatePtr state = NULL;
	char buffer[OPTBUFSIZE] = "";
	
	/* Set up and verify configuration, but don't start. */

	/* Cache variables for the configuration options. */
	const char * cached_device = dagsnap_config_get_device(config);
	int cached_performance = dagsnap_config_get_max_performance(config);
	int cached_data_bytes = dagsnap_config_get_data_bytes(config);
	const char * cached_outfile_name = dagsnap_config_get_outfile_name(config);
	 
	dagutil_set_verbosity(dagsnap_config_get_verbosity(config));
	
	state = dagsnap_state_init(config);
	if (NULL == state)
	{
		dagutil_error("internal error: %s\n", strerror(errno));
		return NULL;
	}

	/* Set the default for the number of bytes to write. */
	if (0 == cached_data_bytes)
	{
		if ((cached_performance) && (cached_data_bytes < 16 * ONE_MEBI))
		{
			dagsnap_config_set_data_bytes(config, 16 * ONE_MEBI);
		}
		else if (cached_data_bytes < 4 * ONE_MEBI)
		{
			dagsnap_config_set_data_bytes(config, 4 * ONE_MEBI);
		}
	}

	/* Open the output file. */
	if (cached_outfile_name)
	{
		int flags = O_RDWR | O_CREAT | O_TRUNC | O_LARGEFILE;

		if (cached_performance)
		{
			flags |= O_DIRECT;
		}

#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || defined(__APPLE__)

		state->outfd = open(cached_outfile_name, flags, 0664);
		if (state->outfd < 0)
		{
			dagutil_error("open %s: %s\n", cached_outfile_name, strerror(errno));
			return NULL;
		}
		
		state->outfile = fdopen(state->outfd, "w");
		if (NULL == state->outfile)
		{
			dagutil_error("fdopen(%d): %s\n", state->outfd, strerror(errno));
			close(state->outfd);
			return NULL;
		}

#elif defined(_WIN32)

		hFile = CreateFile(cached_outfile_name,    /* name of file to create */
					GENERIC_READ | GENERIC_WRITE,  /* permissions of file to create */
					FILE_SHARE_READ,               /* sharing permissions */
					NULL,                          /* security attributes for inheritance */
					CREATE_ALWAYS,                 /* action to take on files that exist */
					FILE_ATTRIBUTE_NORMAL,         /* file flags and attributes */
					NULL);                         /* file template for creation attributes */
	
		if (INVALID_HANDLE_VALUE == hFile)
		{
			LPTSTR message_buffer;
			FormatMessage(
				FORMAT_MESSAGE_ALLOCATE_BUFFER | 
				FORMAT_MESSAGE_FROM_SYSTEM | 
				FORMAT_MESSAGE_IGNORE_INSERTS,
				NULL,
				GetLastError(),
				MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* default language*/
				&message_buffer,
				0,
				NULL);
	
			fprintf(stderr, "open(): %s\n", message_buffer);
			return NULL;
		}
	
		state->outfile = _open_osfhandle((intptr_t) hFile, _O_APPEND | _O_CREAT | _O_TRUNC);
		if (-1 == state->outfile)
		{
			dagutil_error("open %s: %s\n", cached_outfile_name, strerror(errno));
			return NULL;
		}
#endif /* Platform-specific code. */
	}
	else
	{
#if defined(_WIN32)
		dagutil_error("Stdout can not be used with Windows. Please specify the filename to Write to\n");
		return NULL;
#endif /* _WIN32 */
	
		cached_outfile_name = "(stdout)";
	}

	/* Open the DAG card. */
	state->dagfd = dag_open((char*) dagsnap_config_get_device(config));
	if (state->dagfd < 0)
	{
		dagutil_error("dag_open %s: %s\n", cached_device, strerror(errno));
		return NULL;
	}
	
	if (dag_configure(state->dagfd, buffer) < 0)
	{
		dagutil_error("dag_configure %s: %s\n", cached_device, strerror(errno));
		return NULL;
	}
	
	return (DagsnapPtr) state;
}


void
dagsnap_dispose(DagsnapPtr snapper)
{
	DagsnapStatePtr state = (DagsnapStatePtr) snapper;

	assert(state);

	dagsnap_state_dispose(state);
}


void
dagsnap_get_status(DagsnapPtr snapper, DagsnapInfoPtr info)
{
	DagsnapStatePtr state = (DagsnapStatePtr) snapper;

	assert(state);
	assert(info);
	
#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || defined(__APPLE__)

	/* Acquire mutex. */
	pthread_mutex_lock(&state->mutex);

#elif defined(_WIN32)


#endif /* Platform-specific code. */
	
	state->client_info = info;
	state->report_now = 1;
	while (1 == state->report_now)
	{
#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || defined(__APPLE__)

		/* Wait on condition variable for main thread to update view state. */
		pthread_cond_wait(&state->cond, &state->mutex);

#elif defined(_WIN32)


#endif /* Platform-specific code. */
	}
	
	state->client_info = NULL;
	
#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || defined(__APPLE__)

	/* Release mutex. */
	pthread_mutex_unlock(&state->mutex);

#elif defined(_WIN32)


#endif /* Platform-specific code. */
}


void
dagsnap_stop(DagsnapPtr snapper)
{
	DagsnapStatePtr state = (DagsnapStatePtr) snapper;

	assert(state);
	
	state->stop_now = 1;
}


int
dagsnap_capture(DagsnapPtr snapper)
{
	DagsnapStatePtr state = (DagsnapStatePtr) snapper;
	struct timeval maxwait;
	struct timeval poll;
	uint8_t * bottom = NULL;
	uint8_t * top = NULL;
	void * bounce_buffer = NULL;
	int written;
	time_t finish_time = 0;
	
	/* Cached config variables. */
	const char * cached_device = dagsnap_config_get_device(state->config);
	int cached_stream = dagsnap_config_get_dagstream(state->config);
	int cached_performance = dagsnap_config_get_max_performance(state->config);
	uint32_t cached_bytes = dagsnap_config_get_data_bytes(state->config);
	const char * cached_outfile_name = dagsnap_config_get_outfile_name(state->config);
	int cached_dagfd = state->dagfd;
	
#if defined(_WIN32) 
	HANDLE hFile;
#endif /* _WIN32 */


	/* Verify state. */
#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || defined(__APPLE__)

	assert(state->outfile);
	assert(-1 != state->outfd);

#elif defined(_WIN32)

	assert(-1 != state->outfile);

#endif /* Platform-specific code. */


	if (dag_attach_stream(cached_dagfd, cached_stream, 0, 0) < 0)
	{
		dagutil_error("dag_attach_stream %s:%u: %s\n", cached_device, cached_stream, strerror(errno));
		return EXIT_FAILURE;
	}

	if (dag_start_stream(cached_dagfd, cached_stream) < 0)
	{
		dagutil_error("dag_start_stream %s:%u: %s\n", cached_device, cached_stream, strerror(errno));
		return EXIT_FAILURE;
	}

	if (0 != dagsnap_config_get_run_seconds(state->config))
	{
		finish_time = dagsnap_config_get_run_seconds(state->config) + time(NULL);
	}
	
	/*
	 * Initialise DAG Polling parameters.
	 */
	timerclear(&maxwait);
	maxwait.tv_usec = 100 * 1000; /* 100ms timeout */
	timerclear(&poll);
	poll.tv_usec = 10 * 1000; /* 10ms poll interval */

	/* 32kB minimum data to return */
	dag_set_stream_poll(cached_dagfd, cached_stream, 32*ONE_KIBI, &maxwait, &poll);

	/* The main capture loop, wait for data and deliver. */
	while (0 == state->stop_now)
	{
		/* Call nonblocking to permit reporting in the absence of packets. */
		top = dag_advance_stream(cached_dagfd, cached_stream, &bottom);

		if (NULL == top)
		{
			dagutil_error("dag_advance_stream %s:%u: %s\n", cached_device, cached_stream, strerror(dag_get_last_error()));
			return EXIT_FAILURE;
		}
		state->info.diff = top - bottom;

		/* 
		 * If more than cached_bytes data available, then just write cached_bytes now
		 * and then call dag_advance_stream again to free cached_bytes space
		 */
		if ((cached_bytes) && (state->info.diff > cached_bytes))
		{
			if (dagutil_get_verbosity() > 1)
				dagutil_verbose("diff = 0x%"PRId64" (greater than maxbytes) bottom = 0x%08"PRIxPTR", top = 0x%08"PRIxPTR"\n", state->info.diff, (uintptr_t) bottom, (uintptr_t) top);
			written = cached_bytes;
		}
		else if (0 == cached_performance)
		{
			written = (uint32_t) state->info.diff;
		}
		else
		{
			/* With the uMaximizePerformance option given, we only write complete chunks. */
			if (dagutil_get_verbosity() > 1)
				dagutil_verbose("diff = 0x%"PRId64"    (less than maxbytes) bottom = 0x%08"PRIxPTR", top = 0x%08"PRIxPTR"\n", state->info.diff, (uintptr_t) bottom, (uintptr_t) top);
			
			if (state->report_now)
			{
				update_view_state(state);
			}
			
			continue;
		}

		errno = 0; /* no error */

		if (1 == cached_performance)
		{
			const void* source = bottom;
			int result;
			
			if (NULL != bounce_buffer)
			{
				/* Copy data to the bounce buffer, then write it. */
				memcpy(bounce_buffer, bottom, written);
				
				source = bounce_buffer;
			}

			/* Try writing directly. */
			result = write(state->outfd, source, written);
			if (result != written)
			{
				if ((EFAULT == errno) && (NULL == bounce_buffer))
				{
					/* Allocate bounce buffer for next time through.
					posix_memalign(&bounce_buffer, ONE_KIBI, cached_bytes); */
				
				#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) || defined(_WIN32) || (defined(__SVR4) && defined(__sun))
				
					/* FreeBSD (4.9, 4.11) doesn't have posix_memalign(), so use malloc() with extra padding and then align manually. */
					bounce_buffer = dagutil_malloc(cached_bytes + ONE_KIBI);
					if (NULL == bounce_buffer)
					{
						dagutil_error("malloc() failed: %s\n", strerror(errno));
						return EXIT_FAILURE;
					}

#if defined (__LP64__)
					/* Align to a 1024-byte boundary if necessary. */
					if (0 != ((uint64_t) bounce_buffer & 0x3ffLL))
					{
						/* Round up to next 1024-byte boundary. */
						bounce_buffer = (void*) (((uint64_t) bounce_buffer + 0x400LL) & 0xfffffffffffffc00LL);
					}

#else				
					/* Align to a 1024-byte boundary if necessary. */
					if (0 != ((uint32_t) bounce_buffer & 0x3ff))
					{
						/* Round up to next 1024-byte boundary. */
						bounce_buffer = (void*) (((uint32_t) bounce_buffer + 0x400) & 0xfffffc00);
					}
#endif			
				#else
				
					/* Use system posix_memalign(). */
					int error = posix_memalign(&bounce_buffer, ONE_KIBI, cached_bytes);
					
					if (0 != error)
					{
						dagutil_error("posix_memalign() failed: %d\n", error);
						return EXIT_FAILURE;
					}
					
				#endif /* posix_memalign() */
					
					if (dagutil_get_verbosity() > 1)
						dagutil_verbose("posix_memalign(%u, %d) returned 0x%08"PRIxPTR"\n", ONE_KIBI, written, (uintptr_t) bounce_buffer);
					continue;
				}
				else
				{
					dagutil_warning("write(%d, 0x%08"PRIxPTR", %u) returned %d\n", state->outfd, (uintptr_t) source, written, result);
					dagutil_error("write %d bytes to %s: %s\n", written, cached_outfile_name, strerror(errno));
					return EXIT_FAILURE;
				}
			}
		}
		else
		{
#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || defined(__APPLE__)

			if (fwrite(bottom, 1, written, state->outfile) != written)
			{
				dagutil_error("fwrite %d bytes to %s: %s\n", written, cached_outfile_name, strerror(errno));
				return EXIT_FAILURE;
			}

#elif defined(_WIN32)

			if (_write(state->outfile , bottom , written) != written)
			{
				dagutil_error("write failed\n");
				return EXIT_FAILURE;
			}
			
#else

#error Platform not defined.

#endif /* Platform-specific code. */
		}

		state->info.file_offset += written;

		if (state->report_now)
		{
			update_view_state(state);
		}
		
		if (state->info.diff)
		{
			bottom += written;
		}
		
		if (finish_time && (time(NULL) >= finish_time))
		{
			break;
		}
	}

	if (dag_stop_stream(cached_dagfd, cached_stream) < 0)
	{
		dagutil_error("dag_stop_stream %s:%u: %s\n", cached_device, cached_stream, strerror(errno));
		return EXIT_FAILURE;
	}

	if (dag_detach_stream(cached_dagfd, cached_stream) < 0)
	{
		dagutil_error("dag_detach_stream %s:%u: %s\n", cached_device, cached_stream, strerror(errno));
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}
