/*
ToolsMenu - Lets you add tools to the Workbench Tools menu
Copyright (C) 2015  Kim Fastrup Larsen

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

The author can be contacted on <kimflarsen@hotmail.com>.
*/

#include <dos/dostags.h>
#include <exec/memory.h>
#include <workbench/startup.h>

#include <clib/dos_protos.h>
#include <clib/exec_protos.h>
#include <clib/icon_protos.h>

#include <pragmas/dos_pragmas.h>
#include <pragmas/exec_pragmas.h>
#include <pragmas/icon_pragmas.h>

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

#include "toolrun.h"
#include "util.h"

#define MIN_STACK_SIZE 4096

extern struct Library *DOSBase;
extern struct Library *IconBase;

/* We send this extended WBStartup to any tool that we launch. It will
   contain the Workbench arguments passed to run_tool(). Once the tool quits,
   it will reply to this message, and at that point we call the clean_up
   function (also passed to run_tool()) to let it clean up the arguments. */
typedef struct {
    struct WBStartup wb_startup;
    Clean_up_cb *clean_up;
    void *user_data;
} Startmsg;

/* The tools we run will be told to reply to this port when they quit. */
static struct MsgPort *reply_port;
/* The number of tools started that haven't quit yet. */
static int running;

static void free_startmsg(Startmsg *msg)
{
    FreeVec(msg->wb_startup.sm_ToolWindow);
    FreeMem(msg, sizeof *msg);
}

/* Create the reply port and return its signal bit as a mask. */
ULONG set_up_tool_runner(void)
{
    return ((reply_port = CreateMsgPort()) != NULL) ?
     1UL << reply_port->mp_SigBit : 0;
}

BOOL run_tool(struct WBArg *arg_list, LONG num_args, Clean_up_cb *clean_up,
 void *user_data)
{
    BPTR old_dir;
    struct DiskObject *disk_obj;
    Startmsg *startmsg = NULL;
    LONG stack_size;
    BPTR home_dir = 0;
    struct Process *process;

    /* Load the tool's icon and check that it really is a Workbench tool. */
    old_dir = CurrentDir(arg_list[0].wa_Lock);
    if ((disk_obj = GetDiskObject(arg_list[0].wa_Name)) == NULL ||
     disk_obj->do_Type != WBTOOL)
        goto run_tool_failed;
    /* Use the stacksize specified in the icon unless it's too small. */
    if ((stack_size = disk_obj->do_StackSize) < MIN_STACK_SIZE)
        stack_size = MIN_STACK_SIZE;
    /* Set up startup message. */
    if ((startmsg=AllocMem(sizeof *startmsg,MEMF_PUBLIC|MEMF_CLEAR)) == NULL)
        goto run_tool_failed;
    startmsg->wb_startup.sm_ArgList = arg_list;
    startmsg->wb_startup.sm_NumArgs = num_args;
    startmsg->clean_up = clean_up;
    startmsg->user_data = user_data;
    /* We copy the icon's ToolWindow if it isn't NULL. */
    if (disk_obj->do_ToolWindow != NULL &&
     (startmsg->wb_startup.sm_ToolWindow = copy_of(disk_obj->do_ToolWindow))
     == NULL)
        goto run_tool_failed;
    /* Load the tool. */
    if ((startmsg->wb_startup.sm_Segment = NewLoadSegTags(
     arg_list[0].wa_Name, TAG_DONE)) == NULL)
        goto run_tool_failed;
    /* Get a lock for PROGDIR. */
    if ((home_dir = DupLock(arg_list[0].wa_Lock)) == 0)
        goto run_tool_failed;
    /* Run the tool as a non-CLI process. */
    process = CreateNewProcTags(
     NP_Seglist, startmsg->wb_startup.sm_Segment,
     NP_FreeSeglist, TRUE,
     NP_Input, NULL,
     NP_CloseInput, FALSE,
     NP_Output, NULL,
     NP_CloseOutput, FALSE,
     NP_Error, NULL,
     NP_CloseError, FALSE,
     NP_CurrentDir, NULL,
     NP_StackSize, stack_size,
     NP_Name, arg_list[0].wa_Name,
     NP_Priority, 0,
     NP_ConsoleTask, NULL,
     NP_WindowPtr, NULL,
     NP_HomeDir, home_dir,
     TAG_DONE);
    if (process == NULL)
        goto run_tool_failed;
    /* We have started the process, so we mustn't do anything after this point
       that might fail. Send the startup message to the tool. */
    startmsg->wb_startup.sm_Process = &process->pr_MsgPort;
    startmsg->wb_startup.sm_Message.mn_ReplyPort = reply_port;
    PutMsg(startmsg->wb_startup.sm_Process, (struct Message *) startmsg);
    ++running;
    FreeDiskObject(disk_obj);
    CurrentDir(old_dir);
    return TRUE;
run_tool_failed:
    UnLock(home_dir);
    if (startmsg != NULL) {
        if (startmsg->wb_startup.sm_Segment != NULL)
            UnLoadSeg(startmsg->wb_startup.sm_Segment);
        free_startmsg(startmsg);
    }
    if (disk_obj != NULL)
        FreeDiskObject(disk_obj);
    CurrentDir(old_dir);
    return FALSE;
}

/* When a tool started by run_tool() quits, we receive a reply to our
   startup message. At this point we call the clean up function provided,
   then clean up the message itself. */
void handle_tool_reply(void)
{
    Startmsg *msg;

    while ((msg = (Startmsg *) GetMsg(reply_port)) != NULL) {
        (*msg->clean_up)(msg->wb_startup.sm_ArgList,
         msg->wb_startup.sm_NumArgs, msg->user_data);
        free_startmsg(msg);
        --running;
    }
}

/* Wait for all currently running tools to quit before cleaning up the
   reply port and returning control to the caller. */
void clean_up_tool_runner(void)
{
    while (running > 0 ) {
        WaitPort(reply_port);
        handle_tool_reply();
    }
    DeleteMsgPort(reply_port);    
}
