// CNGSOFT wrote this horrible tool to make DSK files 2020/10/04 18:00-19:40 //
// for the future: other types than DATA, 42 tracks C1-C9, directory at track 0
char my_caption[]="CPC2DSK 20201010"; // plus further tweaks as time went on //

#include <stdio.h>
#include <string.h>

// ------------------------------------------------------------------------- //

int fread1(char *t,int l,FILE *f) { int i=0,j; while (i<l&&(j=fread(&t[i],1,l-i,f))) i+=j; return i; }
int fwrite1(char *t,int l,FILE *f) { int i=0,j; while (i<l&&(j=fwrite(&t[i],1,l-i,f))) i+=j; return i; }

int fputcc(int i,FILE *f) { fputc(i,f); return fputc(i>>8,f); }
int fputcccc(int i,FILE *f) { fputc(i,f); fputc(i>>8,f); fputc(i>>16,f); return fputc(i>>24,f); }

int mgetcc(unsigned char *t) { return *t+(t[1]<<8); }
int mgetcccc(unsigned char *t) { return *t+(t[1]<<8)+(t[2]<<16)+(t[3]<<24); }

int ucase(int i) { return (i>='a'&&i<='z')?i-32:i; }
int lcase(int i) { return (i>='A'&&i<='Z')?i+32:i; }

// ------------------------------------------------------------------------- //

unsigned char disc_string[48]="MV - CPC Disk-File\015\012",track_string[16]="Track-Info\015\012",scratch[256]="";
unsigned char usedblocks[256]="",directory[4*512],buffer[254<<10]; // max valid size!;
unsigned char xlats[9]={ 0xC1,0xC6,0xC2,0xC7,0xC3,0xC8,0xC4,0xC9,0xC5 }; // interleaved
unsigned char *source[64],*pseudo[64]; int sources=0;
int tracks,sector_min=0xC1,sector_max=0xC9; FILE *tgt;

int seek_sector(int track,int sector_id)
{
	if (track<0||track>=tracks||sector_id<sector_min||sector_id>sector_max||!tgt)
		return 1;
	int i=0; while (xlats[i]!=sector_id) ++i;
	fseek(tgt,256+(256+9*512)*track+256+512*i,SEEK_SET);
}

int rd_sector(char *t,int track,int sector_id)
{
	return !t||seek_sector(track,sector_id)||(fread1(t,512,tgt)!=512);
}
int wr_sector(char *t,int track,int sector_id)
{
	return !t||seek_sector(track,sector_id)||(fwrite1(t,512,tgt)!=512);
}

// ------------------------------------------------------------------------- //

int main(int argc,char *argv[])
{
	int i=0;
	char *target=NULL,flag_m=0,flag_n=0,flag_h=0,flag_r=0;
	while (++i<argc)
	{
		if (!strcmp(argv[i],"-m"))
			flag_m=1;
		else if (!strcmp(argv[i],"-n"))
			flag_n=1;
		else if (!strcmp(argv[i],"+h"))
			flag_h=1;
		else if (!strcmp(argv[i],"+r"))
			flag_r=1;
		else if (!strcmp(argv[i],"-h"))
			i=argc; // HELP!
		else if (!strcmp(argv[i],"-r"))
			i=argc; // HELP!
		else if (!target)
			target=argv[i];
		else
		{
			source[sources]=argv[i];
			if (!flag_m&&i+1<argc)
			{
				pseudo[sources]=argv[++i];
				if (!*pseudo[sources]||!strcmp(pseudo[sources],"-"))
					pseudo[sources]=NULL;
			}
			else
				pseudo[sources]=NULL;
			++sources;
		}
	}
	if (!target||i>argc)
		return printf("%s (C) Cesar Nicolas-Gonzalez / CNGSOFT\n" "usage:\tcpc2dsk [-n] [+h] [+r] target.dsk [[source pseudo/-] ..] source [pseudo]\n"
			"\t-n\tcreate new disc image\n"
			"\t-m\tmulti-file mode: no pseudos\n"
			"\t+h\ttag file as invisible\n"
			"\t+r\ttag file as read-only\n",my_caption),1;

	if (flag_n)
	{
		if (!(tgt=fopen(target,"w+b")))
			return fprintf(stderr,"error: cannot create target '%s'!\n",target),1;
		strcpy(&disc_string[20],my_caption);
		strcat(&disc_string[20],"\015\012\032");
		fwrite1(disc_string,48,tgt);
		fputc(tracks=42,tgt); fputc(1,tgt); // 42 tracks, 1 side
		fputcc(256+9*512,tgt); // 256-byte header + 9 512-byte sectors
		fwrite1(scratch,256-48-4,tgt);
		for (i=0;i<tracks;++i)
		{
			fwrite1(track_string,16,tgt);
			fputcccc(i,tgt); // track ID
			fputcccc(0xE5520902,tgt); // format type
			for (int j=0;j<9;++j)
			{
				fputcccc(i+(xlats[j]<<16)+(2<<24),tgt); // sector format
				fputcccc(512<<16,tgt); // sector attributes and length
			}
			fwrite1(scratch,256-24-9*8,tgt);
			memset(scratch,0xE5,sizeof(scratch));
			for (int j=0;j<9*2;++j)
				fwrite1(scratch,256,tgt);
			memset(scratch,0,sizeof(scratch));
		}
	}
	else
	{
		if (!(tgt=fopen(target,"r+b")))
			return fprintf(stderr,"error: cannot open target '%s'!\n",target),1;
		fread1(scratch,256,tgt);
		if (memcmp(scratch,disc_string,8)||scratch[48]<2||scratch[49]!=1||scratch[50]!=0||scratch[51]!=19)
			return fclose(tgt),fprintf(stderr,"error: wrong target header!\n"),1;
		tracks=scratch[48];
		for (int i=0;i<tracks;++i)
		{
			fread1(scratch,256,tgt);
			if (memcmp(scratch,track_string,8)||mgetcccc(&scratch[16])!=i||mgetcc(&scratch[20])!=0x0902)
				return fclose(tgt),fprintf(stderr,"error: wrong target track %i!\n",i),1;
			for (int j=0;j<9;++j)
				if (scratch[24+j*8]!=i||scratch[25+j*8]||scratch[26+j*8]!=xlats[j]||scratch[27+j*8]!=2)
					return fclose(tgt),fprintf(stderr,"error: wrong target track:sector sector %i:%i!\n",i,j),1;
			fseek(tgt,9*512,SEEK_CUR);
		}
	}

	// read old directory
	for (i=0;i<4;++i)
		if (rd_sector(&directory[i*512],0,sector_min+i))
			return fclose(tgt),fprintf(stderr,"error: cannot read directory!\n"),1;

	for (int x=0;x<sizeof(directory);x+=32)
		if (directory[x]!=0xE5)
			for (int y=x+16;y<x+32;++y)
				usedblocks[directory[y]]=1;
	usedblocks[0]=usedblocks[1]=1; // just in case...

	for (int z=0;z<sources;++z)
	{
		FILE *src=fopen(source[z],"rb");
		if (!src)
			return fprintf(stderr,"error: cannot open source '%s'!\n",source[z]),1;
		int filesize=fread1(buffer,sizeof(buffer),src);
		i=(filesize+127)&-128;
		memset(&buffer[filesize],0,i-filesize); // ZEROES in the 128-byte segment
		memset(&buffer[i],0xE5,sizeof(buffer)-i); // 0xE5 everywhere else
		fclose(src);

		// send data proper!
		i=0; int dir_count=0;
		while (i<filesize)
		{
			// look for on available directory entry
			int dir_index=0;
			while (dir_index<sizeof(directory)&&(directory[dir_index]!=0xE5))
				dir_index+=32;
			if (dir_index>=sizeof(directory))
				return fclose(tgt),fprintf(stderr,"error: directory full!\n"),1;
			// build filename
			char r=0,*s=pseudo[z]?pseudo[z]:source[z],*t;
			#ifdef _WIN32
				while (t=strchr(s,'\\')) s=&t[1]; // strip path!
			#else
				while (t=strchr(s,'/')) s=&t[1]; // strip path!
			#endif
			t=&directory[dir_index];
			*t++=0; // USER code
			while (*s>32&&r<11)
				if ((*t=ucase(*s++))=='.')
				{
					while (r<8)
						*t++=' ',++r;
					t-=r-8; r=8; // go back, if required
				}
				else
					++t,++r;
			while (r<11)
				*t++=' ',++r;
			if (flag_r)
				t[-3]|=128;
			if (flag_h)
				t[-2]|=128;
			*t++=dir_count++;
			*t++=0;
			*t++=0;
			// send blocks to disc
			int j=16*2*512,k=0,l=dir_index+16,m,n;
			while (j>0&&i<filesize)
			{
				// find an available block
				for (m=2;m<256;++m) // blocks 0 and 1 are reserved (directory)
					if (!usedblocks[m])
						break;
				if (m>=256)
					return fclose(tgt),fprintf(stderr,"error: too many blocks!\n"),1;
				// write such block
				usedblocks[m]=1; directory[l++]=m; // update and store block tables
				int dir_track=m*2/9,dir_sector=sector_min+((m*2)%9);
				wr_sector(&buffer[i],dir_track,dir_sector);
				n=(filesize-i+128-1)/128;
				k+=n>4?4:n; n-=4; i+=512; j-=512;
				if (i<filesize)
				{
					if (++dir_sector>sector_max)
					{
						if (++dir_track>=tracks)
							return fclose(tgt),fprintf(stderr,"error: too much data!\n"),1;
						dir_sector=sector_min;
					}
					wr_sector(&buffer[i],dir_track,dir_sector);
					k+=n>4?4:n; n-=4; i+=512; j-=512;
				}
			}
			while (l&15) directory[l++]=0; // pad block list with zeroes
			*t=k; // `*t` was where we'll store a value between 1 and 128 (128-byte entries)
		}

	}

	// write new directory
	if (sources>0)
		for (i=0;i<4;++i)
			if (wr_sector(&directory[i*512],0,sector_min+i))
				return fclose(tgt),fprintf(stderr,"error: cannot write directory!\n"),1;

	fclose(tgt);

	return 0;
}
