#include <stdio.h>
#include <stdarg.h>
#include "swis.h"

#include "FSysLog.h"
#include "GlobHdr.h"
#include "ISong.h"
#include "Loaders.h"

#define SysLog_LogMessage	0x4c880
#define SysLog_GetLogLevel	0x4c881
#define SysLog_LogData      0x4c88a

static const char* LogModule = NULL;
static int LogLevel = 0;

#ifdef USELOG
#define LogBuffer_Size 1024
static char LogBuffer[LogBuffer_Size];
#endif

/**
 *
 */
const _kernel_oserror* SysLog_Init(GlobHdr* g, void* pw)
{
	IGNORE(pw);

	LogModule = g->pmodname;

	return NULL;
}
/**
 *
 */
const _kernel_oserror* SysLog_Finalize(GlobHdr* g, void* pw)
{
	IGNORE(g);
	IGNORE(pw);

	return NULL;
}

/**
 * Sets the logging level (converter)
 */
void SysLog_SetLogLevel(int level)
{
	LogLevel = level;
}

/**
 * Returns the the log's logging level
 */
int SysLog_LogLevel(void)
{
	int syslevel = 0;

#ifdef MAKEABS
	syslevel = LogLevel;
#else
	_swix(SysLog_GetLogLevel, _IN(0)|_OUT(0), LogModule, &syslevel);
#endif

	return syslevel;
}

#ifdef USELOG

/**
 * Print buffer to the log
 */
static void SysLog_LogBuffer(int level)
{
#ifdef MAKEABS
	if (LogLevel >= level) fprintf(stderr, "%s\n", LogBuffer);
#else
	_swix(SysLog_LogMessage, _INR(0,2), LogModule, LogBuffer, level);
#endif
}

/**
 * Print to the log
 */
void SysLog_Log(int level, const char* pformat, ...)
{
	va_list arg;

	va_start(arg, pformat);
	vsnprintf(LogBuffer, sizeof(LogBuffer), pformat, arg);
	va_end(arg);

	SysLog_LogBuffer(level);
}

/**
 * Records song's filename if not yet done
 */
static void SysLog_SongFilename(SongHdr* pSong)
{
	static const int level = 126;

	if (SysLog_LogLevel() < level)
		return;

	if (!(pSong->Flags & Song_Flag_SysLogged_Filename))
	{
		pSong->Flags |= Song_Flag_SysLogged_Filename;
#ifndef MAKEABS
		SysLog_Log(level, "%s", pSong->pFilename);
#endif
	}
}

/**
 *
 */
void SysLog_PlayEvent(ISong* s, int channel)
{
	static const int level = 126;

	if (SysLog_LogLevel() < level)
		return;

	SysLog_SongFilename(&s->Hdr);
	if (s->PlayRange.Pattern >= 0)
		SysLog_Log
			( level, "Event [Seq: ---, %03d:%03d:%02d]"
	    	, s->PlayRange.Pattern, s->RowPos, channel
		    );
	else
		SysLog_Log
			( level, "Event [Seq: %03x, %03d:%03d:%02d]"
	    	, s->SeqPos, s->SubSongPtr->pSeqs[s->SeqPos], s->RowPos, channel
		    );
}

/**
 *
 */
static inline int print_row(char* pbuf, int size, int pattern, int row, int channel)
{
	return snprintf(pbuf, size, "%03d:%03d:%02d: ", pattern, row, channel);
}

/**
 *
 */
static inline int print_offset(char* pbuf, int size, SongHdr* pSong, int32_t file_offset)
{
	if (file_offset > 0)
		return snprintf(pbuf, size, "[offset %08x] ", file_offset);
	else if (file_offset < 0)
		return snprintf(pbuf, size, "[offset %08x] ", FileLoad_GetPos(pSong) + file_offset);

	return 0;
}

/**
 *
 */
void SysLog_VFileError(SongHdr* pSong, int32_t file_offset, const char* pformat, va_list arg)
{
	static const int level = 1;
	int len;

	if (SysLog_LogLevel() < level)
		return;

	SysLog_SongFilename(pSong);
	len = print_offset(LogBuffer, LogBuffer_Size, pSong, file_offset);
	vsnprintf(LogBuffer + len, LogBuffer_Size - len, pformat, arg);

	SysLog_LogBuffer(level);
}

/**
 *
 */
void SysLog_FileError(SongHdr* pSong, int32_t file_offset, const char* pformat, ...)
{
	va_list arg;

	va_start(arg, pformat);
	SysLog_VFileError(pSong, file_offset, pformat, arg);
	va_end(arg);
}

/**
 *
 */
void SysLog_VFileCorrupted(int level, SongHdr* pSong, int32_t file_offset, const char* pformat, va_list arg)
{
	int len;

	pSong->Flags |= Song_Flag_Corrupted;

	if (SysLog_LogLevel() < level)
		return;

	SysLog_SongFilename(pSong);
	len = print_offset(LogBuffer, LogBuffer_Size, pSong, file_offset);
	vsnprintf(LogBuffer + len, LogBuffer_Size - len, pformat, arg);

	SysLog_LogBuffer(level);
}

/**
 *
 */
void SysLog_FileCorrupted(int level, SongHdr* pSong, int32_t file_offset, const char* pformat, ...)
{
	va_list arg;

	va_start(arg, pformat);
	SysLog_VFileCorrupted(level, pSong, file_offset, pformat, arg);
	va_end(arg);
}

/**
 *
 */
static int SysLog_PrepareRow(SongHdr* pSong, int32_t file_offset)
{
	const LoaderData* pData = pSong->pLoaderData;
	int len;

	SysLog_SongFilename(pSong);
	len = print_row( LogBuffer, LogBuffer_Size
	               , pData->Pattern.Nr, pData->Pattern.Row, pData->Pattern.Channel
	               );
	len += print_offset(LogBuffer + len, LogBuffer_Size - len, pSong, file_offset);

	return len;
}

/**
 *  Keep track of an effect.
 */
void SysLog_WatchEffect(SongHdr* pSong, int32_t file_offset, uint32_t cmd, uint32_t value)
{
	static const int level = 127;
	int len;

	if (SysLog_LogLevel() < level)
		return;

	len = SysLog_PrepareRow(pSong, file_offset);
	snprintf( LogBuffer + len, LogBuffer_Size - len
	        , "Watch on effect %02x, Value %02x", cmd, value
	        );

	SysLog_LogBuffer(level);
}

/**
 *  Invalid note.
 */
void SysLog_BadNote(SongHdr* pSong, int32_t file_offset, uint32_t channel, uint32_t note)
{
	static const int level = 251;
	const LoaderData* pData = pSong->pLoaderData;
	int len;

	pSong->Flags |= Song_Flag_Corrupted;

	if (SysLog_LogLevel() < level)
		return;

	SysLog_SongFilename(pSong);
	len = print_row( LogBuffer, LogBuffer_Size
	               , pData->Pattern.Nr, pData->Pattern.Row, channel
	               );
	len += print_offset(LogBuffer + len, LogBuffer_Size - len, pSong, file_offset);

	snprintf( LogBuffer + len, LogBuffer_Size - len
	        , "Invalid note number %d", note
	        );

	SysLog_LogBuffer(level);
}

/**
 *  Effect outside of implemented effect list.
 */
void SysLog_SkipEffect(SongHdr* pSong, int32_t file_offset, uint32_t cmd, uint32_t value)
{
	static const int level = 252;
	int len;

	pSong->Flags |= Song_Flag_UnsupportedEffects;

	if (SysLog_LogLevel() < level)
		return;

	SysLog_SongFilename(pSong);

	len = SysLog_PrepareRow(pSong, file_offset);
	snprintf( LogBuffer + len, LogBuffer_Size - len
	        , "Unimplemented effect %02x, Value %02x", cmd, value
	        );

	SysLog_LogBuffer(level);
}

/**
 *  Effect value outside of implemented list/range of values.
 */
void SysLog_SkipEffectValue(SongHdr* pSong, int32_t file_offset, uint32_t cmd, uint32_t value)
{
	static const int level = 253;
	int len;

	pSong->Flags |= Song_Flag_UnsupportedEffects;

	if (SysLog_LogLevel() < level)
		return;

	len = SysLog_PrepareRow(pSong, file_offset);
	snprintf( LogBuffer + len, LogBuffer_Size - len
	        , "Effect %02x, Unimplemented value %02x", cmd, value
	        );

	SysLog_LogBuffer(level);
}

/**
 *  Effect outside of known effect list.
 */
void SysLog_BadEffect(SongHdr* pSong, int32_t file_offset, uint32_t cmd, uint32_t value)
{
	static const int level = 254;
	int len;

	pSong->Flags |= Song_Flag_UnsupportedEffects;

	if (SysLog_LogLevel() < level)
		return;

	len = SysLog_PrepareRow(pSong, file_offset);
	snprintf( LogBuffer + len, LogBuffer_Size - len
	        , "Unknown effect %02x, Value %02x", cmd, value
	        );

	SysLog_LogBuffer(level);
}

/**
 *  Effect value outside of known valid list/range of values.
 */
void SysLog_BadEffectValue(SongHdr* pSong, int32_t file_offset, uint32_t cmd, uint32_t value)
{
	static const int level = 255;
	int len;

	if (SysLog_LogLevel() < level)
		return;

	len = SysLog_PrepareRow(pSong, file_offset);
	snprintf( LogBuffer + len, LogBuffer_Size - len
	        , "Effect %02x, Incorrect value %02x", cmd, value
	        );

	SysLog_LogBuffer(level);
}

void SysLog_LogBinary(const char* desc, const uint8_t* pdata, int len)
{
#ifdef MAKEABS
	printf("Dump for %s\n", desc);
	for (int i = 0; i < len; i++)
		printf("%02x", *pdata++);
	printf("\nEnd of Dump\n");
#else
	SysLog_Log(0, "Dump for %s\n", desc);
	_swix(SysLog_LogData, _INR(0,4), LogModule, 0, pdata, len, -1);
	SysLog_Log(0, "End of Dump\n");
#endif
}

#endif
