/*
 * File: sctorCat.c
 * Author: goldmomo
 * Description:
 *   Small command-line utility that add files together with an defined sector size and track file sectors.
 *   Used to build Atari 8-Bit images
 */

#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>

#define MAX_FILES 1024

static void printHelp(const char *program_name)
{
	printf("Usage:\n");
	printf("  %s [options] -o <destination-file> <file1> [file2 ...]\n\n", program_name);
	printf("Options:\n");
	printf("  -help               Show this help message\n");
	printf("  -atr                Add atr header in front\n");
	printf("  -v                  Enable verbose output\n");
	printf("  -noSDefs            print no sector defines\n");
	printf("  -noWrite            don't write, process only \n");
	printf("  -sFill <integer>    fill output file to sectors (default off, use for example 720 for full disk)\n");
	printf("  -sSize <integer>    Set sector size (must be > 0) input files will be align, default 128\n");
	printf("  -sStart <integer>   Set sector start (must be >= 0), default is 0\n");
	printf("  -o <file>           Destination file (required)\n");
}

static bool parseInt(const char *text, int *out_value,int minValue)
{
	long value;
	char *endptr;

	errno = 0;
	value = strtol(text, &endptr, 10);
	if (errno != 0 || *text == '\0' || *endptr != '\0')
	{
		return false;
	}
	if (value < minValue || value > INT_MAX)
	{
		return false;
	}

	*out_value = (int)value;
	return true;
}

//

int gTotalSectorCounter = 0;
int gSectorSize = 128;
int gFillSector = 0;
bool gVerbose = false;
bool gDontWrite = false;
bool gAddAtrHeader = false;
bool gNoSDefs  = false;

//

static bool attachFile(FILE *writeFileHandle,const char *sourceFileName,int fileIndex)
{
	FILE *readFileHandle = NULL;
	uint8_t *readMemory = NULL;
	size_t fileSize = 0;
	
	if(!sourceFileName)
	{
		return false;
	}
	
	readFileHandle = fopen(sourceFileName,"rb");
	if(!sourceFileName)
	{
		fprintf(stderr, "Error: Can't open source file '%s'\n",sourceFileName);
		return false;
	}
	
	// get size
	
	fseek(readFileHandle,0,SEEK_END);
	fileSize = ftell(readFileHandle);
	fseek(readFileHandle,0,SEEK_SET);
	
	int divSector = ((int)fileSize)/gSectorSize;
	int remSector = ((int)fileSize)%gSectorSize;
	int totalSectors = divSector + (remSector?1:0);
	int totalBytes = totalSectors*gSectorSize;
	
	if(gVerbose)
	{
		printf("File '%s': Size %ld -> divSector %d, remSector %d, totalSecors %d (%d Bytes)\n",
		sourceFileName,fileSize,divSector,remSector,totalSectors,totalBytes);
	}
	
	readMemory = calloc((size_t)totalBytes,1);
	if(readMemory)
	{
		if(fileSize != fread(readMemory,1,fileSize,readFileHandle))
		{
			fprintf(stderr, "Error: Read issue at source file '%s'\n",sourceFileName);
		}
		
		if(!gNoSDefs)
		{
			printf(".define\tFILE_%d_SECTOR_START %d\n",fileIndex,gTotalSectorCounter);
		}
		
		if(!gDontWrite)
		{
			int gotTotalBytes = fwrite(readMemory,1,totalBytes,writeFileHandle);
			if(totalBytes != gotTotalBytes)
			{
				fprintf(stderr, "Error: Write issue in attachFile (exp %d, got %d)\n",totalBytes,gotTotalBytes);
			}
		}
		
		gTotalSectorCounter+=totalSectors;		
		free(readMemory);
	}
	else
	{
		fprintf(stderr, "Error: Can't read source file '%s'\n",sourceFileName);
	}
	
	
	fclose(readFileHandle);	
	return true;
}

int main(int argc, char *argv[])
{
		
	const char *destinationFile = NULL;
	const char *files[MAX_FILES];
	int fileCount = 0;
	int i;
	FILE *writeFileHandle = NULL;
	int mainReturn = 0;

	gTotalSectorCounter = 0;

	if (argc == 1)
	{
		printHelp(argv[0]);
		return 0;
	}

	for (i = 1; i < argc; ++i)
	{
		if (strcmp(argv[i], "-help") == 0 || strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0)
		{
			printHelp(argv[0]);
			return 0;
		}

		if (strcmp(argv[i], "-v") == 0)
		{
			gVerbose = true;
			continue;
		}
		
		if (strcmp(argv[i], "-noWrite") == 0)
		{
			gDontWrite = true;
			continue;
		}	
		
		if (strcmp(argv[i], "-atr") == 0)
		{
			gAddAtrHeader = true;
			continue;
		}
		
		if (strcmp(argv[i], "-noSDefs") == 0)
		{
			gNoSDefs = true;
			continue;
		}
		
		if (strcmp(argv[i], "-sSize") == 0)
		{
			if (i + 1 >= argc)
			{
				fprintf(stderr, "Error: Missing integer value after -s.\n");
				return 1;
			}
			if (!parseInt(argv[i + 1], &gSectorSize,1))
			{
				fprintf(stderr, "Error: Invalid sector size '%s'. Only positive integers are allowed.\n", argv[i + 1]);
				return 1;
			}
			++i;
			continue;
		}
		
		if (strcmp(argv[i], "-sFill") == 0)
		{
			if (i + 1 >= argc)
			{
				fprintf(stderr, "Error: Missing integer value after -f.\n");
				return 1;
			}
			if (!parseInt(argv[i + 1], &gFillSector,0))
			{
				fprintf(stderr, "Error: Invalid sector fill size '%s'. Only positive integers are allowed.\n", argv[i + 1]);
				return 1;
			}
			++i;
			continue;
		}
				
		if (strcmp(argv[i], "-sStart") == 0)
		{
			if (i + 1 >= argc)
			{
				fprintf(stderr, "Error: Missing integer value after -b.\n");
				return 1;
			}
			if (!parseInt(argv[i + 1], &gTotalSectorCounter,0))
			{
				fprintf(stderr, "Error: Invalid sector start '%s'. Only positive integers are allowed.\n", argv[i + 1]);
				return 1;
			}
			++i;
			continue;
		}

		if (strcmp(argv[i], "-o") == 0)
		{
			if (i + 1 >= argc)
			{
				fprintf(stderr, "Error: Missing destination file after -o.\n");
				return 1;
			}
			destinationFile = argv[i + 1];
			++i;
			continue;
		}

		if (argv[i][0] == '-')
		{
			fprintf(stderr, "Error: Unknown option '%s'.\n", argv[i]);
			return 1;
		}

		if (fileCount >= MAX_FILES)
		{
			fprintf(stderr, "Error: Too many files (max %d).\n", MAX_FILES);
			return 1;
		}
		files[fileCount++] = argv[i];
	}

	if (fileCount == 0)
	{
		fprintf(stderr, "Error: Please provide at least one file.\n");
		return 1;
	}

	if (destinationFile == NULL && gDontWrite==false)
	{
		fprintf(stderr, "Error: Destination file is required. Use -o <file>.\n");
		return 1;
	}

	if (gVerbose)
	{
		printf("Verbose: %s\n",gVerbose?"true":"false");
		printf("Don't write: %s\n",gDontWrite?"true":"false");
		printf("Sector Size: %d\n", gSectorSize);
		printf("Start Sector: %d\n", gTotalSectorCounter);
		printf("Fill Sector: %d\n", gFillSector);
		printf("Destination File: %s\n", destinationFile);
		printf("File Count: %d\n", fileCount);
	}

	if(!gDontWrite)
	{
		writeFileHandle = fopen(destinationFile,"wb");
		if(!writeFileHandle)
		{
			fprintf(stderr, "Error: Can't open destination file.\n");
			return 1;
		}
		
		if(gAddAtrHeader)
		{
			// reserve 16 bytes in front
			
			uint8_t atrEmpty[16] = {0};
			int gotTotalBytes = fwrite(atrEmpty,1,16,writeFileHandle);
			int expTotalBytes = 16;
			if(gotTotalBytes != expTotalBytes)
			{
				fprintf(stderr, "Error: Write issue in atr header (exp %d, got %d)\n",expTotalBytes,gotTotalBytes);
			}
			
		}
	}

	for (i = 0; i < fileCount; ++i)
	{
		const char *currentFileName = files[i];
		if(currentFileName)
		{	
			if (gVerbose)
			{
				printf("File %d: %s\n", i + 1, currentFileName);
			}
			if(!attachFile(writeFileHandle,currentFileName,i))
			{
				fprintf(stderr, "Error: Can't process file %s\n",currentFileName);
				mainReturn = 1;
				break;
			}
		}
	}
	
	int remainSectors = (gFillSector>0)?gFillSector-gTotalSectorCounter:0;
	if(!gDontWrite && gFillSector>0)
	{		
		if (gVerbose)
		{
			printf("Written sectors %d, Fillup to %d sectore, so fill %d sectors\n",gTotalSectorCounter,gFillSector,remainSectors);		
		}
		
		if(remainSectors>0)
		{

			int bytesToAppend = remainSectors * gSectorSize;

			if (gVerbose)
			{
				printf("Append %d bytes\n",bytesToAppend);
			}

			uint8_t *empty = calloc(bytesToAppend,0);
			if(empty)
			{

				int gotTotalBytes = fwrite(empty,1,bytesToAppend,writeFileHandle);
				if(bytesToAppend != gotTotalBytes)
				{
					fprintf(stderr, "Error: Write issue in sector append (exp %d, got %d)\n",bytesToAppend,gotTotalBytes);
				}
			}
			else
			{
				fprintf(stderr, "Error: Can't allocate '%d' bytes memory\n",bytesToAppend);
			}

			free(empty);
		}
	}
	
	if(!gDontWrite)
	{
		if(gAddAtrHeader)
		{
			uint8_t attrHeader[16] = {0};
			
			int dataSize = (gTotalSectorCounter + ((remainSectors>0)?remainSectors:0))*gSectorSize;
			int dataSizeD16 = dataSize/16;
			
			if (gVerbose)
			{
				printf("Make atr total length in bytes is %d (16 * %d)\n",dataSize,dataSizeD16);		
			}
			
			attrHeader[0] = 0x96;	// magic
			attrHeader[1] = 0x02;   // id
			attrHeader[2] = (uint8_t)dataSizeD16;		// data length / 16
			attrHeader[3] = (uint8_t)(dataSizeD16>>8);   
			attrHeader[4] = (uint8_t)gSectorSize;		// only 128 or 256 are defined	
			attrHeader[5] = (uint8_t)(gSectorSize>>8);   
			attrHeader[6] = (uint8_t)(dataSizeD16>>16); 	// next higher byte
			
			fseek(writeFileHandle,0,SEEK_SET);
			if(16 != fwrite(attrHeader,1,16,writeFileHandle))
			{
				fprintf(stderr, "Error: Write atr issue\n");
			}
		}
		
		fclose(writeFileHandle);
	}

	return mainReturn;
}
