/*
 * 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));
         /* setup PROGDIR: so we can open the catalog asap */
         UnLock(SetProgramDir(ftpdir_lock));
         
         setup_strings();

         mem_tracking_on();
      
         // aktuelles Jahr ermitteln
         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;
      
         // globale Var. "anon_login" auf String "user@host" setzen
         if (get_anon_login())
         {
            // Buttons (Abort, Disconnect, etc.) erstellen
            if (make_gims())
            {
               if (launch_tcp_handler())
               {
                  if (launch_local())
                  {
                     ftphosts_lock = Lock("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;
   
   if (IntuitionBase)
      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 = "ftp";
               
               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)
         {
            // nicht kontrollieren, da auch ohne diese Libs läuft
            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);
   } // kein startup_error(), da es Intuitionase benötigt
   
   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);
}



boolean get_anon_login(void)
{
   #define BUFF_SIZE  100
   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("USER", user, BUFF_SIZE, 0);
   j = GetVar("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("FTPMount");
      volume_name = (b8 *)allocate_flags(vlen + 2, MEMF_PUBLIC, V_bstr);
      if (volume_name)
      {
         volume_name[0] = vlen;
         strcpy(&volume_name[1], "FTPMount");

         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)
// strings[] auf Catalog-Werte setzen
{
   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);
}

