#include        "mk.h"

typedef struct Event
{
       int pid;
       Job *job;
} Event;
static Event *events;
static int nevents, nrunning, nproclimit;

typedef struct Process
{
       int pid;
       int status;
       struct Process *b, *f;
} Process;
static Process *phead, *pfree;
static void sched(void);
static void pnew(int, int), pdelete(Process *);

int pidslot(int);

void
run(Job *j)
{
       Job *jj;

       if(jobs){
               for(jj = jobs; jj->next; jj = jj->next)
                       ;
               jj->next = j;
       } else
               jobs = j;
       j->next = 0;
       /* this code also in waitup after parse redirect */
       if(nrunning < nproclimit)
               sched();
}

static void
sched(void)
{
       char *flags;
       Job *j;
       Bufblock *buf;
       int slot;
       Node *n;
       Envy *e;

       if(jobs == 0){
               usage();
               return;
       }
       j = jobs;
       jobs = j->next;
       if(DEBUG(D_EXEC))
               fprint(1, "firing up job for target %s\n", wtos(j->t, ' '));
       slot = nextslot();
       events[slot].job = j;
       buf = newbuf();
       e = buildenv(j, slot);
       shprint(j->r->recipe, e, buf);
       if(!tflag && (nflag || !(j->r->attr&QUIET)))
               Bwrite(&bout, buf->start, (long)strlen(buf->start));
       freebuf(buf);
       if(nflag||tflag){
               for(n = j->n; n; n = n->next){
                       if(tflag){
                               if(!(n->flags&VIRTUAL))
                                       touch(n->name);
                               else if(explain)
                                       Bprint(&bout, "no touch of virtual '%s'\n", n->name);
                       }
                       n->time = time((long *)0);
                       MADESET(n, MADE);
               }
       } else {
               if(DEBUG(D_EXEC))
                       fprint(1, "recipe='%s'\n", j->r->recipe);       /**/
               Bflush(&bout);
               if(j->r->attr&NOMINUSE)
                       flags = 0;
               else
                       flags = "-e";
               events[slot].pid = execsh(flags, j->r->recipe, 0, e);
               usage();
               nrunning++;
               if(DEBUG(D_EXEC))
                       fprint(1, "pid for target %s = %d\n", wtos(j->t, ' '), events[slot].pid);
       }
}

int
waitup(int echildok, int *retstatus)
{
       Envy *e;
       int pid;
       int slot;
       Symtab *s;
       Word *w;
       Job *j;
       char buf[ERRMAX];
       Bufblock *bp;
       int uarg = 0;
       int done;
       Node *n;
       Process *p;
       extern int runerrs;

       /* first check against the proces slist */
       if(retstatus)
               for(p = phead; p; p = p->f)
                       if(p->pid == *retstatus){
                               *retstatus = p->status;
                               pdelete(p);
                               return(-1);
                       }
again:          /* rogue processes */
       pid = waitfor(buf);
       if(pid == -1){
               if(echildok > 0)
                       return(1);
               else {
                       fprint(2, "mk: (waitup %d) ", echildok);
                       perror("mk wait");
                       Exit();
               }
       }
       if(DEBUG(D_EXEC))
               fprint(1, "waitup got pid=%d, status='%s'\n", pid, buf);
       if(retstatus && pid == *retstatus){
               *retstatus = buf[0]? 1:0;
               return(-1);
       }
       slot = pidslot(pid);
       if(slot < 0){
               if(DEBUG(D_EXEC))
                       fprint(2, "mk: wait returned unexpected process %d\n", pid);
               pnew(pid, buf[0]? 1:0);
               goto again;
       }
       j = events[slot].job;
       usage();
       nrunning--;
       events[slot].pid = -1;
       if(buf[0]){
               e = buildenv(j, slot);
               bp = newbuf();
               shprint(j->r->recipe, e, bp);
               front(bp->start);
               fprint(2, "mk: %s: exit status=%s", bp->start, buf);
               freebuf(bp);
               for(n = j->n, done = 0; n; n = n->next)
                       if(n->flags&DELETE){
                               if(done++ == 0)
                                       fprint(2, ", deleting");
                               fprint(2, " '%s'", n->name);
                               delete(n->name);
                       }
               fprint(2, "\n");
               if(kflag){
                       runerrs++;
                       uarg = 1;
               } else {
                       jobs = 0;
                       Exit();
               }
       }
       for(w = j->t; w; w = w->next){
               if((s = symlook(w->s, S_NODE, 0)) == 0)
                       continue;       /* not interested in this node */
               update(uarg, s->u.ptr);
       }
       if(nrunning < nproclimit)
               sched();
       return(0);
}

void
nproc(void)
{
       Symtab *sym;
       Word *w;

       if(sym = symlook("NPROC", S_VAR, 0)) {
               w = sym->u.ptr;
               if (w && w->s && w->s[0])
                       nproclimit = atoi(w->s);
       }
       if(nproclimit < 1)
               nproclimit = 1;
       if(DEBUG(D_EXEC))
               fprint(1, "nprocs = %d\n", nproclimit);
       if(nproclimit > nevents){
               if(nevents)
                       events = (Event *)Realloc((char *)events, nproclimit*sizeof(Event));
               else
                       events = (Event *)Malloc(nproclimit*sizeof(Event));
               while(nevents < nproclimit)
                       events[nevents++].pid = 0;
       }
}

int
nextslot(void)
{
       int i;

       for(i = 0; i < nproclimit; i++)
               if(events[i].pid <= 0) return i;
       assert(/*out of slots!!*/ 0);
       return 0;       /* cyntax */
}

int
pidslot(int pid)
{
       int i;

       for(i = 0; i < nevents; i++)
               if(events[i].pid == pid) return(i);
       if(DEBUG(D_EXEC))
               fprint(2, "mk: wait returned unexpected process %d\n", pid);
       return(-1);
}


static void
pnew(int pid, int status)
{
       Process *p;

       if(pfree){
               p = pfree;
               pfree = p->f;
       } else
               p = (Process *)Malloc(sizeof(Process));
       p->pid = pid;
       p->status = status;
       p->f = phead;
       phead = p;
       if(p->f)
               p->f->b = p;
       p->b = 0;
}

static void
pdelete(Process *p)
{
       if(p->f)
               p->f->b = p->b;
       if(p->b)
               p->b->f = p->f;
       else
               phead = p->f;
       p->f = pfree;
       pfree = p;
}

void
killchildren(char *msg)
{
       Process *p;

       kflag = 1;      /* to make sure waitup doesn't exit */
       jobs = 0;       /* make sure no more get scheduled */
       for(p = phead; p; p = p->f)
               expunge(p->pid, msg);
       while(waitup(1, (int *)0) == 0)
               ;
       Bprint(&bout, "mk: %s\n", msg);
       Exit();
}

static long tslot[1000];
static long tick;

void
usage(void)
{
       long t;

       time(&t);
       if(tick)
               tslot[nrunning] += (t-tick);
       tick = t;
}

void
prusage(void)
{
       int i;

       usage();
       for(i = 0; i <= nevents; i++)
               fprint(1, "%d: %ld\n", i, tslot[i]);
}