/* ``The contents of this file are subject to the Erlang Public License,
 * Version 1.1, (the "License"); you may not use this file except in
 * compliance with the License. You should have received a copy of the
 * Erlang Public License along with this software. If not, it can be
 * retrieved via the world wide web at http://www.erlang.org/.
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Initial Developer of the Original Code is Ericsson Utvecklings AB.
 * Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
 * AB. All Rights Reserved.''
 * 
 *     $Id$
 */

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

static ErlDrvData monitor_drv_start(ErlDrvPort, char *);
static int monitor_drv_control(ErlDrvData, unsigned int,
				char *, int, char **, int);
static void handle_monitor(ErlDrvData drv_data, ErlDrvMonitor *monitor);

#define OP_I_AM_IPID 1
#define OP_MONITOR_ME 2
#define OP_DEMONITOR_ME 3
#define OP_MONITOR_ME_LATER 4
#define OP_DO_DELAYED_MONITOR 5

typedef struct one_monitor {
    ErlDrvTermData pid;
    int later_id;
    ErlDrvMonitor mon;
    struct one_monitor *next;
} OneMonitor;


typedef struct {
    ErlDrvPort port;
    ErlDrvTermData ipid;
    int later_counter;
    OneMonitor *first;
} MyDrvData;


static ErlDrvEntry monitor_drv_entry = { 
    NULL /* init */,
    monitor_drv_start,
    NULL /* stop */,
    NULL /* output */,
    NULL /* ready_input */,
    NULL /* ready_output */,
    "monitor_drv",
    NULL /* finish */,
    NULL /* handle */,
    monitor_drv_control,
    NULL /* timeout */,
    NULL /* outputv */,
    NULL /* ready_async */,
    NULL /* flush */,
    NULL /* call */,
    NULL /* event */,
    ERL_DRV_EXTENDED_MARKER,
    ERL_DRV_EXTENDED_MAJOR_VERSION,
    ERL_DRV_EXTENDED_MINOR_VERSION,
    0,
    NULL, /* handle2 */
    handle_monitor
};

DRIVER_INIT(monitor_drv)
{
    return &monitor_drv_entry;
}

static ErlDrvData
monitor_drv_start(ErlDrvPort port, char *command) {
    MyDrvData *data = driver_alloc(sizeof(MyDrvData));
    data->port = port;
    data->ipid = driver_term_nil;
    data->first = NULL;
    data->later_counter = 0;
    return (ErlDrvData) data;
}

static void monitor_drv_stop(ErlDrvData data)
{
    driver_free((void *) data);
}

static void handle_monitor(ErlDrvData drv_data, ErlDrvMonitor *monitor)
{
    
    MyDrvData *data = (MyDrvData *) drv_data;
    OneMonitor *p,*o;
    for (p = data->first, o = NULL; 
	 p != NULL && driver_compare_monitors(&p->mon,monitor); 
	 o = p, p = p->next)
	;
    if (!p) {
	fprintf(stderr,"Spooky Monitor executed!\r\n");
    } else {
	 ErlDrvTermData spec[] = {
	      ERL_DRV_ATOM, driver_mk_atom("monitor_fired"),
	      ERL_DRV_PORT, driver_mk_port(data->port),
	      ERL_DRV_PID, p->pid,
	      ERL_DRV_TUPLE, TERM_DATA(3)
	 };
	if (!o) {
	    data->first = p->next;
	} else {
	    o->next = p->next;
	}
	driver_free(p);
	driver_send_term(data->port, data->ipid, spec, sizeof(spec)/sizeof(ErlDrvTermData));
    }
    
    return;
}

static int
monitor_drv_control(ErlDrvData drv_data,
		     unsigned int command,
		     char *ibuf, int ilen,
		     char **rbuf, int rlen)
{
    MyDrvData *data = (MyDrvData *) drv_data;
    char *answer = NULL;
    char buff[64];
    int alen;

    switch (command) {
    case OP_I_AM_IPID:
	data->ipid = driver_caller(data->port);
	answer = "ok";
	break;
    case OP_MONITOR_ME:
	{
	    int res;
	    OneMonitor *om = driver_alloc(sizeof(OneMonitor));
	    om->pid = driver_caller(data->port);
	    om->later_id = 0;
	    res = driver_monitor_process(data->port,om->pid,&(om->mon));
	    if (res < 0) {
		answer = "error";
		driver_free(om);
	    } else if (res > 0) {
		answer = "noproc";
		driver_free(om);
	    } else {
		om->next = data->first;
		data->first = om;
		answer = "ok";
	    }
	    break;
	}
    case OP_DEMONITOR_ME:
	{
	    int res;
	    OneMonitor *p,*q = NULL;
	    int found = 0;
	    ErlDrvTermData pid = driver_caller(data->port);
	    for (p = data->first; p != NULL; p = p->next) {
		if (p->pid == pid) {
		    q = p;
		    ++found;
		}
	    }
	    if (q == NULL) {
		answer = "not_monitored";
	    } else {
		if (q->later_id > 0) {
		    if (found > 1) {
			answer = "delayd_but_more";
		    } else {
			answer = "delayed";
		    }
		} else {
		    res = driver_demonitor_process(data->port, &(q->mon));
		    if (res < 0) {
			answer = "error";
		    } else if (res > 0) {
			if (found > 1) {
			    answer = "gone_but_more";
			} else {
			    answer = "gone";
			}
		    } else {
			if (found > 1) {
			    answer = "ok_but_more";
			} else {
			    answer = "ok";
			}
		    }
		}
		if (data->first == q) {
		    data->first = q->next;
		} else {
		    for (p = data->first; p != NULL; p = p->next) {
			if (p->next == q) {
			    p->next = q->next;
			    break;
			}
		    }
		}
		driver_free(q);
	    }
	    break;
	}
    case OP_MONITOR_ME_LATER:
	{
	    int res;
	    OneMonitor *om = driver_alloc(sizeof(OneMonitor));
	    om->pid = driver_caller(data->port);
	    om->later_id = (++(data->later_counter));
	    om->next = data->first;
	    data->first = om;
	    sprintf(buff,"ok:%d",om->later_id);
	    answer = buff;
	    break;
	}
    case OP_DO_DELAYED_MONITOR:
	{
	    int id = 0, sign = 1, in_number = 0;
	    OneMonitor *p, *q;
	    char *bp;
	    for (bp = ibuf; bp < (ibuf + ilen); ++bp) {
		if (*bp <= '9' && *bp >= '0') {
		    int x = *bp - '0';
		    in_number++;
		    id *= 10;
		    id += x;
		} else if (*bp == '-') {
		    if (in_number) {
			break;
		    } 
		    sign = -1;
		    ++in_number;
		} else {
		    if (in_number) {
			break;
		    }
		}
	    }
	    id *= sign;
	    q = NULL;
	    for (p = data->first; p != NULL; q = p, p = p->next) {
		if (p->later_id != 0 && p->later_id == id) {
		    break;
		}
	    }
	    if (p == NULL) {
		answer = "not_found";
	    } else {
		int res = driver_monitor_process(data->port,p->pid,&(p->mon));
		if (res != 0) {
		    if (res < 0) {
			answer = "error";
		    } else {
			answer = "noproc";
		    }
		    if (q == NULL) {
			data->first = p->next;
		    } else {
			q->next = p->next;
		    }
		    driver_free(p);
		} else {
		    p->later_id = 0;
		    answer = "ok";
		}
	    }
	    break;
	}
    default:
	answer = "unknown_op";
    }
    if (answer == NULL) {
	answer = "internal_error";
    }
    alen = strlen(answer);
    if (alen >= rlen) {
	*rbuf = driver_alloc(alen+1);
    }
    strcpy(*rbuf,answer);
    return alen;
}


