/*
 * This source file is Copyright 1995 by Evan Scott.
 * All rights reserved.
 * Permission is granted to distribute this file provided no
 * fees beyond distribution costs are levied.
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/alerts.h>

#include <dos/dos.h>
#include <dos/dosextens.h>
#include <dos/dostags.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/graphics.h>
#include <proto/intuition.h>
#include <proto/locale.h>

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

#include "evtypes.h"
#include "verify.h"
#include "tcp.h"

#include "site.h"
#include "ftp.h"
#include "local.h"
#include "request.h"

#define DECLARE_GLOBALS_HERE 1
#include "globals.h"
#include "strings.h"

struct DosPacket *fh_listen(void);
void fh_ignore(void);

boolean launch_tcp_handler(void);
boolean launch_local(void);
void shutdown_tcp_handler(void);
void shutdown_local(void);

boolean open_libraries(void);
void close_libraries(void);

boolean make_gims(void);
void free_gims(void);

void startup_error(b8 *s);

boolean get_anon_login(void);

boolean create_volume(void);
void destroy_volume(void);

boolean launch_status(void);
void shutdown_status(void);

void setup_strings(void);
void cleanup_strings(void);

void __saveds start(void)
{
	struct Process *me;
	struct Message *msg;
	struct DosPacket *dp;
	struct MsgPort *reply;
	struct DateTime dtime;
	b8 temp[15];

	SysBase = *(struct ExecBase **)4;

	me = (struct Process *)FindTask(0l);
	
	ftp_port = &me->pr_MsgPort;
	
	WaitPort(ftp_port);	/* wait for startup packet */
	msg = GetMsg(ftp_port);
	
	dp = (struct DosPacket *)msg->mn_Node.ln_Name;
	reply = dp->dp_Port;
	dp->dp_Port = ftp_port;
	
	ftp_device = (struct DosList *)(dp->dp_Arg3 << 2);
	ftp_device->dol_Task = ftp_port;	/* fill in our message port */
	
	/* get down to initializing everything */
	
	sites = nil;
	orphaned_locks = nil;
	
	if (open_libraries()) {
		ftpdir_lock = Lock("FTPMountDir:", SHARED_LOCK);
		if (ftpdir_lock) {
			UnLock(CurrentDir(ftpdir_lock));
			UnLock(SetProgramDir(ftpdir_lock));
			
			/* setup PROGDIR: so we can open the catalog asap */
			
			setup_strings();

			mem_tracking_on();
		
			DateStamp(&dtime.dat_Stamp);
		
			dtime.dat_StrDate = temp;
			dtime.dat_StrDay = nil;
			dtime.dat_StrTime = nil;
			dtime.dat_Flags = 0;
			dtime.dat_Format = FORMAT_INT;
		
			DateToStr(&dtime);
		
			year = atoi(temp);
		
			if (year >= 78) year += 1900;
			else year += 2000;
		
			if (get_anon_login()) {
				if (make_gims()) {
					if (launch_tcp_handler()) {
						if (launch_local()) {
							ftphosts_lock = Lock(strings[MSG_HOSTS], SHARED_LOCK);
							if (ftphosts_lock) {
								if (launch_status()) {
									if (create_volume()) {
										/* initialization is complete */
										dp->dp_Res1 = DOSTRUE;
										dp->dp_Res2 = 0;
								
										PutMsg(reply, dp->dp_Link);
								
										dp = fh_listen();
								
										/* only comes back on a DIE */
										
										shutdown_sites();

										shutdown_status();
										shutdown_local();
										shutdown_tcp_handler();
				
										UnLock(ftphosts_lock);
										UnLock(ftpdir_lock);
								
										free_gims();
									
										if (anon_login) 
											deallocate(anon_login, V_cstr);
									
										destroy_volume();

										check_memory();
								
										close_libraries();

										if (dp) {
											dp->dp_Res1 = DOSTRUE;
											dp->dp_Res2 = 0;
									
											reply = dp->dp_Port;
											dp->dp_Port = ftp_port;
									
											PutMsg(reply, dp->dp_Link);
										}
									
										fh_ignore();	/* never comes back */
									} else dp->dp_Res2 = ERROR_NO_FREE_STORE;
									shutdown_status();
								} else dp->dp_Res2 = ERROR_NO_FREE_STORE;
								UnLock(ftphosts_lock);
							} else {
								startup_error(strings[MSG_CANT_FIND_HOSTS]);
								dp->dp_Res2 = ERROR_DIR_NOT_FOUND;
							}
							shutdown_local();
						} else dp->dp_Res2 = ERROR_NO_FREE_STORE;
						shutdown_tcp_handler();
					} else dp->dp_Res2 = ERROR_NO_FREE_STORE;
					free_gims();
				} else dp->dp_Res2 = ERROR_NO_FREE_STORE;
				if (anon_login) deallocate(anon_login, V_cstr);
			} else dp->dp_Res2 = ERROR_REQUIRED_ARG_MISSING;
		
			check_memory();
			
			cleanup_strings();
			
			SetProgramDir(0);
			CurrentDir(0);
		
			UnLock(ftpdir_lock);
		} else dp->dp_Res2 = ERROR_DIR_NOT_FOUND;
		close_libraries();
	} else dp->dp_Res2 = ERROR_INVALID_RESIDENT_LIBRARY;

	ftp_device->dol_Task = 0;
	
	dp->dp_Res1 = DOSFALSE;
	
	Forbid();	/* this is so they can't unloadseg us until we have finished */
	PutMsg(reply, dp->dp_Link);
	return;
}

/* has to be after the startup function */
#include "verify_code.h"

void startup_error(b8 *s)
{
	struct EasyStruct es;
	
	es.es_StructSize = sizeof(struct EasyStruct);
	es.es_Flags = 0;
	es.es_Title = strings[MSG_FTPM_STARTUP_ERROR];
	es.es_GadgetFormat = strings[MSG_OK];
	es.es_TextFormat = s;
	
	EasyRequest(nil, &es, nil);
}

boolean launch_tcp_handler(void)
{
	struct Process *child;
	tcpmessage *tm;
	
	unique_name(FindTask(0), ": FTPMount", unique_buffer);
	
	startup_sync = CreatePort(unique_buffer, 0);
	if (startup_sync) {
		child = CreateNewProcTags(
			NP_Entry,	tcp_handler,
			NP_Arguments,	unique_buffer,
			NP_Name,	strings[MSG_TCP_HANDLER],
			NP_StackSize,	6000,
			TAG_END,	0
		);
		if (child) {
			Wait(1 << startup_sync->mp_SigBit);
			tm = (tcpmessage *)GetMsg(startup_sync);
			
			if (tm) {
				verify(tm, V_tcpmessage);
				
				if (tm->result) {
					tcp = tm->data;
					ReplyMsg(&tm->header);
					
					WaitPort(startup_sync);
					prime = (tcpmessage *)GetMsg(startup_sync);
					
					/* ok!  off we trundle */
					
					prime->command = TCP_SERVICE;
					prime->data = strings[MSG_SERVICE];
					
					PutMsg(tcp, &prime->header);
					WaitPort(startup_sync); GetMsg(startup_sync);
					
					if (prime->result) {
						ftp_port_number = prime->port.w;
					} else {
						ftp_port_number = 0;	/* fill it in at connect time */
					}
					
					return true;
				}
				ReplyMsg(&tm->header);
				
				/* whether child is still alive here??? */
			}
		}
		DeletePort(startup_sync);
	}
	
	startup_error(strings[MSG_CANT_LAUNCH_TCP]);
	
	return false;
}

void shutdown_tcp_handler(void)
{
	prime->command = TCP_DIE;
	prime->header.mn_ReplyPort = startup_sync;
	
	PutMsg(tcp, &prime->header);
	
	Wait(1 << startup_sync->mp_SigBit);	/* Wait til the child signals it is dead */
	
	DeletePort(startup_sync);
	return;
}

boolean launch_local(void)
{
	struct StandardPacket *sp;
	struct Process *child;
	
	sp = (struct StandardPacket *)allocate_flags(sizeof(*sp), MEMF_PUBLIC, V_StandardPacket);
	if (!sp) return false;
	
	local_msg = &sp->sp_Msg;
	local_msg->mn_Node.ln_Name = (char *)&sp->sp_Pkt;
	sp->sp_Pkt.dp_Link = local_msg;
	
	sp->sp_Pkt.dp_Type = ACTION_DIE;	/* for startup it should ignore this :) */
	
	sp->sp_Pkt.dp_Port = startup_sync;	/* this is bad programming ... increases linkage */
	
	child = CreateNewProcTags(
		NP_Entry,	local_handler,
		NP_Name,	strings[MSG_LOCAL_HANDLER],
		NP_StackSize,	6000,
		TAG_END,	0
	);
	if (child) {
		local_port = &child->pr_MsgPort;
		
		PutMsg(local_port, local_msg);
		WaitPort(startup_sync); GetMsg(startup_sync);
		
		if (sp->sp_Pkt.dp_Res1) {
			return true;
		}
	}

	deallocate(sp, V_StandardPacket);

	startup_error(strings[MSG_CANT_LAUNCH_LOCAL]);
	
	return false;
}

void shutdown_local(void)
{
	struct StandardPacket *sp;
	
	sp = (struct StandardPacket *)local_msg;
	
	sp->sp_Pkt.dp_Port = startup_sync;
	
	PutMsg(local_port, local_msg);
	WaitPort(startup_sync); GetMsg(startup_sync);
	
	deallocate(sp, V_StandardPacket);
	
	return;
}

boolean open_libraries(void)
{
	IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 36);
	if (IntuitionBase) {
		DOSBase = (struct DosLibrary *)OpenLibrary("dos.library", 36);
		if (DOSBase) {
			GfxBase = (struct GfxBase *)OpenLibrary("graphics.library", 0);
			if (GfxBase) {
				IconBase = OpenLibrary("icon.library", 0);
				LocaleBase = OpenLibrary("locale.library", 0);
				
				return true;
			} else startup_error("FTPMount cannot open graphics.library");
			CloseLibrary((struct Library *)DOSBase);
		} else startup_error("FTPMount requires V36 dos.library");
		CloseLibrary((struct Library *)IntuitionBase);
	}
	
	return false;
}

void close_libraries(void)
{
	if (LocaleBase) CloseLibrary(LocaleBase);
	if (IconBase) CloseLibrary(IconBase);
	CloseLibrary((struct Library *)GfxBase);
	CloseLibrary((struct Library *)DOSBase);
	CloseLibrary((struct Library *)IntuitionBase);
}

boolean make_gims(void)
{
	struct Screen *s;
	struct DrawInfo *drawinfo;
	
	s = LockPubScreen(nil);
	if (!s) return false;
	
	lightpen = 2;
	darkpen = 1;
	textpen = 1;
	fillpen = 3;
	
	drawinfo = GetScreenDrawInfo(s);
	if (drawinfo) {
		if (drawinfo->dri_NumPens > SHADOWPEN) {
			lightpen = drawinfo->dri_Pens[SHINEPEN];
			darkpen = drawinfo->dri_Pens[SHADOWPEN];
			textpen = drawinfo->dri_Pens[TEXTPEN];
			fillpen = drawinfo->dri_Pens[FILLPEN];
		}
		FreeScreenDrawInfo(s, drawinfo);
	}
	
	cancel_gim = make_gim(strings[MSG_CANCEL], textpen, lightpen, darkpen, s, IntuitionBase, GfxBase);
	if (cancel_gim) {
		abort_gim = make_gim(strings[MSG_ABORT], textpen, lightpen, darkpen, s, IntuitionBase, GfxBase);
		if (abort_gim) {
			disconnect_gim = make_gim(strings[MSG_DISCONNECT], textpen, lightpen, darkpen, s, IntuitionBase, GfxBase);
			if (disconnect_gim) {
				login_gim = make_gim(strings[MSG_LOGIN], textpen, lightpen, darkpen, s, IntuitionBase, GfxBase);
				if (login_gim) {
					UnlockPubScreen(nil, s);
					return true;
				}
				free_gim(disconnect_gim, IntuitionBase, GfxBase);
			}
			free_gim(abort_gim, IntuitionBase, GfxBase);
		}
		free_gim(cancel_gim, IntuitionBase, GfxBase);
	}
	
	UnlockPubScreen(nil, s);
	
	return false;
}

void free_gims(void)
{
	free_gim(login_gim, IntuitionBase, GfxBase);
	free_gim(disconnect_gim, IntuitionBase, GfxBase);
	free_gim(abort_gim, IntuitionBase, GfxBase);
	free_gim(cancel_gim, IntuitionBase, GfxBase);
}

#define BUFF_SIZE 100

boolean get_anon_login(void)
{
	b8 user[BUFF_SIZE], host[BUFF_SIZE];
	sb32 i, j;
	struct EasyStruct es;
	
	es.es_StructSize = sizeof(struct EasyStruct);
	es.es_Flags = 0;
	es.es_Title = strings[MSG_FTPM_STARTUP_ERROR];
	es.es_GadgetFormat = strings[MSG_CONTINUE_EXIT];
	
	i = GetVar(strings[MSG_USER], user, BUFF_SIZE, 0);
	j = GetVar(strings[MSG_HOST], host, BUFF_SIZE, 0);
	
	/* four cases here */
	if (i >= 0 && j >= 0) {
		anon_login = (b8 *)allocate(i + j + 2, V_cstr);
		if (!anon_login) return false;
		
		strcpy(anon_login, user);
		anon_login[i] = '@';
		strcpy(anon_login + i + 1, host);
	} else if (i < 0 && j >= 0) {
		anon_login = (b8 *)allocate(j + 9, V_cstr);
		if (!anon_login) return false;

		strcpy(anon_login, "unknown@");
		strcat(anon_login, host);

		es.es_TextFormat = strings[MSG_USER_NOT_SET];
		if (!EasyRequest(nil, &es, 0, anon_login)) {
			deallocate(anon_login, V_cstr);
			return false;
		}
	} else if (i >= 0 && j < 0) {
		anon_login = (b8 *)allocate(i + 9, V_cstr);
		if (!anon_login) return false;

		strcpy(anon_login, user);
		strcat(anon_login, "@unknown");

		es.es_TextFormat = strings[MSG_HOST_NOT_SET];
		if (!EasyRequest(nil, &es, 0, anon_login)) {
			deallocate(anon_login, V_cstr);
			return false;
		}
	} else {
		anon_login = (b8 *)allocate(16, V_cstr);
		if (!anon_login) return false;

		strcpy(anon_login, "unknown@unknown");
			
		es.es_TextFormat = strings[MSG_USER_HOST_NOT_SET];
		if (!EasyRequest(nil, &es, 0, anon_login)) {
			deallocate(anon_login, V_cstr);
			return false;
		}
	}
	
	return true;
}

boolean create_volume(void)
{
	b32 vlen;

	ftp_volume = (struct DosList *)allocate_flags(sizeof(struct DosList), MEMF_PUBLIC, V_DosList);
	if (ftp_volume) {
		ftp_volume->dol_Type = DLT_VOLUME;
		ftp_volume->dol_Task = ftp_port;
		ftp_volume->dol_Lock = 0;
		DateStamp(&ftp_volume->dol_misc.dol_volume.dol_VolumeDate);
		ftp_volume->dol_misc.dol_volume.dol_LockList = 0;
		ftp_volume->dol_misc.dol_volume.dol_DiskType = ID_DOS_DISK;
	
		vlen = strlen(strings[MSG_VOLUME]);
		volume_name = (b8 *)allocate_flags(vlen + 2, MEMF_PUBLIC, V_bstr);
		if (volume_name) {
			volume_name[0] = vlen;
			strcpy(&volume_name[1], strings[MSG_VOLUME]);

			ftp_volume->dol_Name = (b32)volume_name >> 2;
			if (AddDosEntry(ftp_volume)) {
				return true;
			}
			
			deallocate(volume_name, V_bstr);
		}
		deallocate(ftp_volume, V_DosList);
	}
	
	return false;
}

void destroy_volume(void)
{
	struct DosList *dllock;
	
	dllock = LockDosList(LDF_VOLUMES | LDF_DELETE | LDF_WRITE);
	
	if (RemDosEntry(ftp_volume)) {
		deallocate(volume_name, V_bstr);
		deallocate(ftp_volume, V_DosList);
	}
	
	UnLockDosList(LDF_VOLUMES | LDF_DELETE | LDF_WRITE);
	
	return;
}

boolean launch_status(void)
{
	struct Process *child;
	
	status_mess = (status_message *)allocate(sizeof(*status_mess), V_status_message);
	if (!status_mess) return false;
	
	status_mess->header.mn_Length = sizeof(*status_mess);
	status_mess->header.mn_Node.ln_Name = "status startup message";
	status_mess->header.mn_Node.ln_Type = NT_MESSAGE;
	status_mess->header.mn_Node.ln_Pri = 0;
	
	ensure(status_mess, V_status_message);
	
	status_control = CreatePort(0, 0);
	if (status_control) {
		child = CreateNewProcTags(
			NP_Entry,	status_handler,
			NP_Name,	strings[MSG_STATUS_HANDLER],
			NP_StackSize,	6000,
			TAG_END,	0
		);
	
		if (child) {
			status_port = &child->pr_MsgPort;
		
			status_mess->header.mn_ReplyPort = startup_sync;
			PutMsg(status_port, &status_mess->header);
		
			WaitPort(startup_sync); GetMsg(startup_sync);
		
			if (status_mess->command != SM_KILL) {
				return true;
			}
		}
		
		DeletePort(status_control);
	}
	
	deallocate(status_mess, V_status_message);
	
	startup_error(strings[MSG_CANT_LAUNCH_STATUS]);
	
	return false;
}

void shutdown_status(void)
{
	status_message *sm;
	
	status_mess->command = SM_KILL;
	status_mess->header.mn_ReplyPort = startup_sync;
	
	PutMsg(status_port, &status_mess->header);
	
	while (1) {
		Wait ((1 << status_control->mp_SigBit) | (1 << startup_sync->mp_SigBit));
		
		while (sm = (status_message *)GetMsg(status_control)) {
			verify(sm, V_status_message);
			
			ReplyMsg(&sm->header);
		}
		
		if (sm = (status_message *)GetMsg(startup_sync)) {
			deallocate(status_mess, V_status_message);
			DeletePort(status_control);
			return;
		}
	}
}

void setup_strings(void)
{
	int i;

	my_locale = nil;
	cat = nil;

	if (LocaleBase) {
		my_locale = OpenLocale(0);
		if (my_locale) {
			cat = OpenCatalog(my_locale, "FTPMount.catalog",
				OC_BuiltInLanguage, "english",
				TAG_END
			);
		}
	}
	
	if (cat) {
		for (i = 0; i < NUM_MSGS; i++) {
			strings[i] = GetCatalogStr(cat, i, strings[i]);
		}
	}
}

void cleanup_strings(void)
{
	if (cat) CloseCatalog(cat);
	if (my_locale) CloseLocale(my_locale);
}
