#include <string.h>
#include <dos/dos.h>
#include <utility/date.h>
#include <dos/datetime.h>
#include <exec/memory.h>
#include <clib/dos_protos.h>
#include <clib/exec_protos.h>
#include <clib/utility_protos.h>

#define REG(x) register __ ## x
#ifdef __SASC
#include <pragmas/dos_pragmas.h>
#include <pragmas/utility_pragmas.h>
#include <pragmas/exec_sysbase_pragmas.h>
#define VERDATE __AMIGADATE__
#define ASM __asm
#define ALIGNED __aligned
#define NDS __saveds
#define STACK __stdargs
#else
#include <pragma/dos_lib.h>
#include <pragma/exec_lib.h>
#include <pragma/utility_lib.h>
#define VERDATE "("__DATE2__")"
#define ASM
#define ALIGNED static
#define NDS
#define STACK
#endif

#include <lowlevel.h>

struct  {
		BYTE *from;
		BYTE *to;
		BYTE *name;
		BYTE *header;
		LONG *lines;
		LONG *sections;
		LONG *bytes;
		LONG *section;
		LONG msdos;
		LONG checksum;
		LONG linesum;
		LONG filetime;
		LONG testvalues;
		LONG quiet;
} arg = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

#define TEMPLATE "FROM/A,TO/K,NAME/K,HEADER/K,LINES/N,SECTIONS/N,BYTES/N,SECTION/N,MSDOS/S,CHECKSUM/S,LINESUM/S,FILETIME/S,TESTVALUES/S,QUIET/S"

#define MINSIZE 250
#define MINBUFFER 0x4000

BYTE *msg[] = {
	"Error in arguments",
	"Conflicting options",
	"Can't open source file",
	"Can't open destination file",
	"Can't read data",
	"Can't write data",
	"Can't process empty file",
	"Can't examine file",
	"Invalid SECTION value",
	"SECTION not specified",
	"TO not specified",
	"LINES value must not be zero!",
	"SECTIONS value not admissible",
	"BYTES value must be 1000 or more",
	"v36+ OS required",
	"User break"
};
enum {
	ERR_ARG,
	ERR_CONF,
	ERR_SRC,
	ERR_DEST,
	ERR_READ,
	ERR_WRITE,
	ERR_EMPTY,
	ERR_EXAMINE,
	ERR_SECTION,
	ERR_NOSECTION,
	ERR_TO,
	ERR_LINES,
	ERR_SECTIONS,
	ERR_BYTES,
	ERR_OSVER,
	ERR_BREAK
};

struct Library *UtilityBase;
struct Library *DOSBase;
struct Library *SysBase;
int rc;

BYTE *cutmsdosname(BYTE *,BYTE *,int,int);
BYTE *copymsdosname(BYTE *, BYTE *, int);
LONG STACK testbreak(VOID);
VOID error(BYTE *,int);

ASM NDS main(REG(a0) BYTE *string)
{
	SysBase = *((struct Library **)4L);
	if(DOSBase = OpenLibrary("dos.library",0))
	{
		if(UtilityBase = OpenLibrary("utility.library",36))
		{
			struct RDArgs *rda;
			SetIoErr(0);
			if(*string=='?')
			{
				Printf("\n%.19s, "__TIME__") © 1999 Russian Digital Computing, http://rdc.cch.pmc.ru\n\n",
					6+"$VER: uuepro 3.9 "VERDATE);
			}
	 		if(rda = ReadArgs(TEMPLATE,(LONG *)&arg,NULL))
			{
				if((arg.lines!=0)+(arg.sections!=0)+(arg.bytes!=0)<=1)
				{
					BPTR infh, outfh;
					if(infh = Open(arg.from,MODE_OLDFILE))
					{
						ALIGNED struct FileInfoBlock fib;
						if(ExamineFH(infh,&fib))
						{
							if(fib.fib_Size)
							{
								ULONG linesum = UDivMod32((fib.fib_Size-1),45), lines = linesum+1;
								ULONG sections = 1, bytesperline = arg.linesum?63:62;
								if(arg.lines)
								{
									if(*arg.lines)
									{
										if(*arg.lines<lines)
										{
											lines = *arg.lines;
										}
										sections = UDivMod32(linesum,lines)+1;
									}
									else
									{
										error(msg[ERR_LINES],10);
									}
								}
								if(arg.sections)
								{
									if((*arg.sections)&&(*arg.sections<=lines))
									{
										lines = UDivMod32((lines-1),(*arg.sections))+1;
										sections = UDivMod32(linesum,lines)+1;
										if(sections!=*arg.sections)
										{
											error(msg[ERR_SECTIONS],10);
										}
									}
									else
									{
										error(msg[ERR_SECTIONS],10);
									}
								}
								if(arg.bytes)
								{
									if(*arg.bytes>1000)
									{
										LONG byteline = UDivMod32((*arg.bytes-MINSIZE),bytesperline);
										if(byteline<lines)
										{
											lines=byteline;
										}
										sections = UDivMod32(linesum,lines)+1;
									}
									else
									{
										error(msg[ERR_BYTES],10);
									}
								}
								if(arg.section)
								{
									if(!*arg.section||*arg.section>sections)
									{
										error(msg[ERR_SECTION],10);
									}
								}
								else
								{
									if(!arg.testvalues&&sections>1)
									{
										error(msg[ERR_NOSECTION],10);
									}
								}
								if(!rc)
								{
									BYTE *inbuf, *from = arg.name?arg.name:fib.fib_FileName, *uuename = from, msdosname[14];
									LONG secdatasize = UMult32(lines,bytesperline), inbuflen = UMult32(lines,45);
									LONG realinbuflen = inbuflen>(MINBUFFER-3)?inbuflen+3:MINBUFFER;
									if(arg.msdos&&(arg.testvalues||arg.header||sections==1||*arg.section==1))
									{
										BYTE *in, *out, *point = NULL;
										LONG namelen = strlen(from);
										for(in=from;*in;in++)
										{
											if(*in=='.')
											{
												point = in;
											}
										}
										out = cutmsdosname(from,msdosname,point?point-from:namelen,8);
										if(point)
										{
											int extlen;
											point++;
											if(extlen = from+namelen-point)
											{
												*out++ = '.';
												out = cutmsdosname(point,out,extlen,3);
											}
										}
										*out = 0;
										uuename = msdosname;
									}
									if(arg.testvalues)
									{
										Printf("%ld\n%ld\n%ld\n%s\n",sections,lines,secdatasize+MINSIZE,uuename);
									}
									else
									{
										if(arg.to)
										{
											LONG uuenamebuflen = strlen(uuename)+1;
											if(inbuf = AllocVec(realinbuflen+uuenamebuflen+secdatasize+1000,0))
											{
												LONG section, sizecount, sum = 0;
												if(arg.section)
												{
													LONG offset = UMult32(((section = *arg.section)-1),inbuflen);
													if(offset)
													{
														if(section==sections&&arg.checksum)
														{
															LONG sumbuf = realinbuflen&0xfffff800;
															while(offset)
															{
																LONG sumlen = offset>sumbuf?sumbuf:offset;
																if(0<Read(infh,inbuf,sumlen))
																{
																	sum = checksum(inbuf,sumlen,sum);
																	offset -= sumlen;
																	if(testbreak())
																	{
																		break;
																	}
																}
																else
																{
																	error(msg[ERR_READ],10);
																	break;
																}
															}
														}
														else
														{
															Seek(infh,offset,OFFSET_BEGINNING);
														}
													}
												}
												else
												{
													section = 1;
												}
												if(!rc)
												{
													if(0<(sizecount=Read(infh,inbuf,inbuflen)))
													{
														if(!testbreak())
														{
															BYTE *uuenamebuf = inbuf+realinbuflen;
															BYTE *to = uuenamebuf, *in = inbuf;
															BYTE *outbuf = uuenamebuf+uuenamebuflen;
															UBYTE symbol;
															inbuf[sizecount] = 0;
															inbuf[sizecount+1] = 0;
															while(symbol = *uuename++)
															{
																*to++ = (symbol>' ')?symbol:'_';
															}
															*to = 0;
															if(outfh = Open(arg.to,MODE_NEWFILE))
															{
																LONG secsize, lastsecsize = sizecount;
																BYTE *secfrom;
																to = outbuf;
																if(arg.header)
																{
																	relspf(&to,"section %ld of %ld of file %s%s%s\n\n",section,sections,uuenamebuf,*arg.header?" ":NULL,arg.header);
																}
																if(arg.filetime&&section==1)
																{
																	struct ClockData ds;
																	Amiga2Date(UMult32(fib.fib_Date.ds_Days,86400)+UMult32(fib.fib_Date.ds_Minute,60)+UDivMod32(fib.fib_Date.ds_Tick,50),&ds);
																	relspf(&to,"filetime %ld\n",ds.sec>>1|ds.min<<5|ds.hour<<11|ds.mday<<16|ds.month<<21|(ds.year-1980)<<25);
																}
																secfrom = to;
																if(section==1)
																{
																	relspf(&to,"begin %s44 %s\n",fib.fib_Protection&FIBF_EXECUTE?"6":"7",uuenamebuf);
																}
																to = uuencode(in,to,lines,sizecount,arg.linesum);
																if(!rc)
																{
																	if(section==sections)
																	{
																		relspf(&to,1+"``\nend\n"+arg.linesum);
																	}
																	if(arg.checksum)
																	{
																		secsize = to - secfrom;
																		if(section!=sections)
																		{
																			*to++ = '\n';
																		}
																		relspf(&to,"sum -r/size %ld/%ld section (from %s",checksum(secfrom,secsize,0),secsize,(section==1)?"\"begin\"":"first");
																		if(section==sections&&section!=1)
																		{
																			relspf(&to," encoded line");
																		}
																		relspf(&to," to %s)\n",(section==sections)?"\"end\"":"last encoded line");
																		if(section==sections)
																		{
																			relspf(&to,"sum -r/size %ld/%ld entire input file\n",checksum(inbuf,lastsecsize,sum),fib.fib_Size);
																		}
																	}
																	else
																	{
																		if(section==sections)
																		{
																			relspf(&to,"size %ld\n",fib.fib_Size);
																		}
																	}
																	if(!testbreak())
																	{
																		if(Write(outfh,outbuf,to-outbuf)<0)
																		{
																			error(msg[ERR_WRITE],10);
																		}
																	}
																}
																Close(outfh);
																if(testbreak())
																{
																	DeleteFile(arg.to);
																}
															}
															else
															{
																error(msg[ERR_DEST],10);
															}
														}
													}
													else
													{
														error(msg[ERR_READ],10);
													}
												}
												FreeVec(inbuf);
											}
											else
											{
												error(msg[ERR_READ],10);
											}
										}
										else
										{
											error(msg[ERR_TO],20);
										}
									}
								}
							}
							else
							{
								error(msg[ERR_EMPTY],10);
							}
						}
						else
						{
							error(msg[ERR_EXAMINE],10);
						}
						Close(infh);
					}
					else
					{
						error(msg[ERR_SRC],10);
					}
					FreeArgs(rda);
				}
				else
				{
					error(msg[ERR_CONF],20);
				}
			}
			else
			{
				error(msg[ERR_ARG],20);
			}
			CloseLibrary(UtilityBase);
		}
		else
		{
			error(msg[ERR_OSVER],20);
		}
		CloseLibrary(DOSBase);
	}
	return(rc);
}

LONG STACK testbreak(VOID)
{
	Forbid();
	if(CheckSignal(SIGBREAKF_CTRL_C))
	{
		error(msg[ERR_BREAK],10);
	}
	Permit();
	return(rc);
}

VOID error(BYTE *msg,int err)
{
	if(!arg.quiet)
	{
		int doserr = IoErr();
		if(doserr)
		{
			PrintFault(doserr,msg);
		}
		else
		{
			Printf("%s\n",msg);
		}
	}
	rc = err;
}

BYTE *cutmsdosname(BYTE *in,BYTE *out,int len,int limit)
{
	return(len>limit?copymsdosname(in+len-1,copymsdosname(in,out,limit-1),1):copymsdosname(in,out,len));
}

BYTE *copymsdosname(BYTE *in, BYTE *out, int len)
{
	int count;
	if(len)
	{
		for(count=len;count>0;count--)
		{
			BYTE symbol = *in++;
			if(strchr("*?.,:;+=¦|/\[]<>\"",symbol))
			{
				symbol = '_';
			}
			*out++ = ToLower(symbol);
		}
	}
	return(out);
}
