/***** spin: pangen7.c *****/

/*
* This file is part of the public release of Spin. It is subject to the
* terms in the LICENSE file that is included in this source directory.
* Tool documentation is available at http://spinroot.com
*/

#include <stdlib.h>
#include <assert.h>
#include "spin.h"
#include "y.tab.h"
#include <assert.h>
#ifndef PC
#include <unistd.h>
#endif

extern ProcList *rdy;
extern Element *Al_El;
extern int nclaims, verbose, Strict;
extern short has_accept;

typedef struct Succ_List Succ_List;
typedef struct SQueue SQueue;
typedef struct OneState OneState;
typedef struct State_Stack State_Stack;
typedef struct Guard Guard;

struct Succ_List {
       SQueue  *s;
       Succ_List *nxt;
};

struct OneState {
       int     *combo; /* the combination of claim states */
       Succ_List       *succ;  /* list of ptrs to immediate successor states */
};

struct SQueue {
       OneState        state;
       SQueue  *nxt;
};

struct State_Stack {
       int *n;
       State_Stack *nxt;
};

struct Guard {
       Lextok *t;
       Guard *nxt;
};

static SQueue   *sq, *sd, *render;      /* states move from sq to sd to render to holding */
static SQueue   *holding, *lasthold;
static State_Stack *dsts;

static int nst;         /* max nr of states in claims */
static int *Ist;        /* initial states */
static int *Nacc;       /* number of accept states in claim */
static int *Nst;        /* next states */
static int **reached;   /* n claims x states */
static int unfolding;   /* to make sure all accept states are reached */
static int is_accept;   /* remember if the current state is accepting in any claim */
static int not_printing; /* set during explore_product */

static Element ****matrix;      /* n x two-dimensional arrays state x state */
static Element **Selfs; /* self-loop states at end of claims */

static void get_seq(int, Sequence *);
static void set_el(int n, Element *e);
static void gen_product(void);
static void print_state_nm(char *, int *, char *);
static SQueue *find_state(int *);
static SQueue *retrieve_state(int *);

static int
same_state(int *a, int *b)
{       int i;

       for (i = 0; i < nclaims; i++)
       {       if (a[i] != b[i])
               {       return 0;
       }       }
       return 1;
}

static int
in_stack(SQueue *s, SQueue *in)
{       SQueue *q;

       for (q = in; q; q = q->nxt)
       {       if (same_state(q->state.combo, s->state.combo))
               {       return 1;
       }       }
       return 0;
}

static void
to_render(SQueue *s)
{       SQueue *a, *q, *last; /* find in sd/sq and move to render, if not already there */
       int n;

       for (n = 0; n < nclaims; n++)
       {       reached[n][ s->state.combo[n] ] |= 2;
       }

       for (q = render; q; q = q->nxt)
       {       if (same_state(q->state.combo, s->state.combo))
               {       return;
       }       }
       for (q = holding; q; q = q->nxt)
       {       if (same_state(q->state.combo, s->state.combo))
               {       return;
       }       }

       a = sd;
more:
       for (q = a, last = 0; q; last = q, q = q->nxt)
       {       if (same_state(q->state.combo, s->state.combo))
               {       if (!last)
                       {       if (a == sd)
                               {       sd = q->nxt;
                               } else if (a == sq)
                               {       sq = q->nxt;
                               } else
                               {       holding = q->nxt;
                               }
                       } else
                       {       last->nxt = q->nxt;
                       }
                       q->nxt = render;
                       render = q;
                       return;
       }       }
       if (verbose)
       {       print_state_nm("looking for: ", s->state.combo, "\n");
       }
       (void) find_state(s->state.combo);      /* creates it in sq */
       if (a != sq)
       {       a = sq;
               goto more;
       }
       fatal("cannot happen, to_render", 0);
}

static void
wrap_text(char *pre, Lextok *t, char *post)
{
       printf(pre, (char *) 0);
       comment(stdout, t, 0);
       printf(post, (char *) 0);
}

static State_Stack *
push_dsts(int *n)
{       State_Stack *s;
       int i;

       for (s = dsts; s; s = s->nxt)
       {       if (same_state(s->n, n))
               {       if (verbose&64)
                       {       printf("\n");
                               for (s = dsts; s; s = s->nxt)
                               {       print_state_nm("\t", s->n, "\n");
                               }
                               print_state_nm("\t", n, "\n");
                       }
                       return s;
       }       }

       s = (State_Stack *) emalloc(sizeof(State_Stack));
       s->n = (int *) emalloc(nclaims * sizeof(int));
       for (i = 0; i < nclaims; i++)
               s->n[i] = n[i];
       s->nxt = dsts;
       dsts = s;
       return 0;
}

static void
pop_dsts(void)
{
       assert(dsts != NULL);
       dsts = dsts->nxt;
}

static void
complete_transition(Succ_List *sl, Guard *g)
{       Guard *w;
       int cnt = 0;

       printf("        :: ");
       for (w = g; w; w = w->nxt)
       {       if (w->t->ntyp == CONST
               &&  w->t->val == 1)
               {       continue;
               } else if (w->t->ntyp == 'c'
               &&  w->t->lft->ntyp == CONST
               &&  w->t->lft->val == 1)
               {       continue; /* 'true' */
               }

               if (cnt > 0)
               {       printf(" && ");
               }
               wrap_text("", w->t, "");
               cnt++;
       }
       if (cnt == 0)
       {       printf("true");
       }
       print_state_nm(" -> goto ", sl->s->state.combo, "");

       if (is_accept > 0)
       {       printf("_U%d\n", (unfolding+1)%nclaims);
       } else
       {       printf("_U%d\n", unfolding);
       }
}

static void
state_body(OneState *s, Guard *guard)
{       Succ_List *sl;
       State_Stack *y;
       Guard *g;
       int i, once;

       for (sl = s->succ; sl; sl = sl->nxt)
       {       once = 0;

               for (i = 0; i < nclaims; i++)
               {       Element *e;
                       e = matrix[i][s->combo[i]][sl->s->state.combo[i]];

                       /* if one of the claims has a DO or IF move
                          then pull its target state forward, once
                        */

                       if (!e
                       || e->n->ntyp == NON_ATOMIC
                       ||  e->n->ntyp == DO
                       ||  e->n->ntyp == IF)
                       {       s = &(sl->s->state);
                               y = push_dsts(s->combo);
                               if (!y)
                               {       if (once++ == 0)
                                       {       assert(s->succ != NULL);
                                               state_body(s, guard);
                                       }
                                       pop_dsts();
                               } else if (!y->nxt)     /* self-loop transition */
                               {       if (!not_printing) printf(" /* self-loop */\n");
                               } else
                               {       /* non_fatal("loop in state body", 0); ** maybe ok */
                               }
                               continue;
                       } else
                       {       g = (Guard *) emalloc(sizeof(Guard));
                               g->t = e->n;
                               g->nxt = guard;
                               guard = g;
               }       }

               if (guard && !once)
               {       if (!not_printing) complete_transition(sl, guard);
                       to_render(sl->s);
       }       }
}

static struct X {
       char *s;        int n;
} spl[] = {
       {"end",         3 },
       {"accept",      6 },
       {0,             0 },
};

static int slcnt;
extern Label *labtab;

static ProcList *
locate_claim(int n)
{       ProcList *p;
       int i;

       for (p = rdy, i = 0; p; p = p->nxt, i++) /* find claim name */
       {       if (i == n)
               {       break;
       }       }
       assert(p && p->b == N_CLAIM);

       return p;
}

static void
elim_lab(Element *e)
{       Label *l, *lst;

       for (l = labtab, lst = NULL; l; lst = l, l = l->nxt)
       {       if (l->e == e)
               {       if (lst)
                       {       lst->nxt = l->nxt;
                       } else
                       {       labtab = l->nxt;
                       }
                       break;
       }       }
}

static int
claim_has_accept(ProcList *p)
{       Label *l;

       for (l = labtab; l; l = l->nxt)
       {       if (strcmp(l->c->name, p->n->name) == 0
               &&  strncmp(l->s->name, "accept", 6) == 0)
               {       return 1;
       }       }
       return 0;
}

static void
prune_accept(void)
{       int n;

       for (n = 0; n < nclaims; n++)
       {       if ((reached[n][Selfs[n]->seqno] & 2) == 0)
               {       if (verbose)
                       {       printf("claim %d: selfloop not reachable\n", n);
                       }
                       elim_lab(Selfs[n]);
                       Nacc[n] = claim_has_accept(locate_claim(n));
       }       }
}

static void
mk_accepting(int n, Element *e)
{       ProcList *p;
       Label *l;
       int i;

       assert(!Selfs[n]);
       Selfs[n] = e;

       l = (Label *) emalloc(sizeof(Label));
       l->s = (Symbol *) emalloc(sizeof(Symbol));
       l->s->name = "accept00";
       l->c = (Symbol *) emalloc(sizeof(Symbol));
       l->uiid = 0;    /* this is not in an inline */

       for (p = rdy, i = 0; p; p = p->nxt, i++) /* find claim name */
       {       if (i == n)
               {       l->c->name = p->n->name;
                       break;
       }       }
       assert(p && p->b == N_CLAIM);
       Nacc[n] = 1;
       has_accept = 1;

       l->e = e;
       l->nxt = labtab;
       labtab = l;
}

static void
check_special(int *nrs)
{       ProcList *p;
       Label *l;
       int i, j, nmatches;
       int any_accepts = 0;

       for (i = 0; i < nclaims; i++)
       {       any_accepts += Nacc[i];
       }

       is_accept = 0;
       for (j = 0; spl[j].n; j++) /* 2 special label prefixes */
       {       nmatches = 0;
               for (p = rdy, i = 0; p; p = p->nxt, i++) /* check each claim */
               {       if (p->b != N_CLAIM)
                       {       continue;
                       }
                       /* claim i in state nrs[i], type p->tn, name p->n->name
                        * either the state has an accept label, or the claim has none,
                        * so that all its states should be considered accepting
                        * --- but only if other claims do have accept states!
                        */
                       if (Strict == 0 && j == 1 && Nacc[i] == 0 && any_accepts > 0)
                       {       if ((verbose&32) && i == unfolding)
                               {       printf("        /* claim %d pseudo-accept */\n", i);
                               }
                               goto is_accepting;
                       }
                       for (l = labtab; l; l = l->nxt) /* check its labels */
                       {       if (strcmp(l->c->name, p->n->name) == 0  /* right claim */
                               &&  l->e->seqno == nrs[i]                /* right state */
                               &&  strncmp(l->s->name, spl[j].s, spl[j].n) == 0)
                               {       if (j == 1)     /* accept state */
                                       {       char buf[32];
is_accepting:                                   if (strchr(p->n->name, ':'))
                                               {       sprintf(buf, "N%d", i);
                                               } else
                                               {       assert(strlen(p->n->name) < sizeof(buf));
                                                       strcpy(buf, p->n->name);
                                               }
                                               if (unfolding == 0 && i == 0)
                                               {       if (!not_printing)
                                                       printf("%s_%s_%d:\n",   /* true accept */
                                                               spl[j].s, buf, slcnt++);
                                               } else if (verbose&32)
                                               {       if (!not_printing)
                                                       printf("%s_%s%d:\n",
                                                               buf, spl[j].s, slcnt++);
                                               }
                                               if (i == unfolding)
                                               {       is_accept++; /* move to next unfolding */
                                               }
                                       } else
                                       {       nmatches++;
                                       }
                                       break;
               }       }       }
               if (j == 0 && nmatches == nclaims)      /* end-state */
               {       if (!not_printing)
                       {       printf("%s%d:\n", spl[j].s, slcnt++);
       }       }       }
}

static int
render_state(SQueue *q)
{
       if (!q || !q->state.succ)
       {       if (verbose&64)
               {       printf("        no exit\n");
               }
               return 0;
       }

       check_special(q->state.combo); /* accept or end-state labels */

       dsts = (State_Stack *) 0;
       push_dsts(q->state.combo);      /* to detect loops */

       if (!not_printing)
       {       print_state_nm("", q->state.combo, ""); /* the name */
               printf("_U%d:\n\tdo\n", unfolding);
       }

       state_body(&(q->state), (Guard *) 0);

       if (!not_printing)
       {       printf("\tod;\n");
       }
       pop_dsts();
       return 1;
}

static void
explore_product(void)
{       SQueue *q;

       /* all states are in the sd queue */

       q = retrieve_state(Ist);        /* retrieve from the sd q */
       q->nxt = render;                /* put in render q */
       render = q;
       do {
               q = render;
               render = render->nxt;
               q->nxt = 0;             /* remove from render q */

               if (verbose&64)
               {       print_state_nm("explore: ", q->state.combo, "\n");
               }

               not_printing = 1;
               render_state(q);        /* may add new states */
               not_printing = 0;

               if (lasthold)
               {       lasthold->nxt = q;
                       lasthold = q;
               } else
               {       holding = lasthold = q;
               }
       } while (render);
       assert(!dsts);

}

static void
print_product(void)
{       SQueue *q;
       int cnt;

       if (unfolding == 0)
       {       printf("never Product {\n");    /* name expected by iSpin */
               q = find_state(Ist);    /* should find it in the holding q */
               assert(q != NULL);
               q->nxt = holding;       /* put it at the front */
               holding = q;
       }
       render = holding;
       holding = lasthold = 0;

       printf("/* ============= U%d ============= */\n", unfolding);
       cnt = 0;
       do {
               q = render;
               render = render->nxt;
               q->nxt = 0;
               if (verbose&64)
               {       print_state_nm("print: ", q->state.combo, "\n");
               }
               cnt += render_state(q);

               if (lasthold)
               {       lasthold->nxt = q;
                       lasthold = q;
               } else
               {       holding = lasthold = q;
               }
       } while (render);
       assert(!dsts);

       if (cnt == 0)
       {       printf("        0;\n");
       }

       if (unfolding == nclaims-1)
       {       printf("}\n");
       }
}

static void
prune_dead(void)
{       Succ_List *sl, *last;
       SQueue *q;
       int cnt;

       do {    cnt = 0;
               for (q = sd; q; q = q->nxt)
               {       /* if successor is deadend, remove it
                        * unless it's a move to the end-state of the claim
                        */
                       last = (Succ_List *) 0;
                       for (sl = q->state.succ; sl; last = sl, sl = sl->nxt)
                       {       if (!sl->s->state.succ) /* no successor */
                               {       if (!last)
                                       {       q->state.succ = sl->nxt;
                                       } else
                                       {       last->nxt = sl->nxt;
                                       }
                                       cnt++;
               }       }       }
       } while (cnt > 0);
}

static void
print_raw(void)
{       int i, j, n;

       printf("#if 0\n");
       for (n = 0; n < nclaims; n++)
       {       printf("C%d:\n", n);
               for (i = 0; i < nst; i++)
               {       if (reached[n][i])
                       for (j = 0; j < nst; j++)
                       {       if (matrix[n][i][j])
                               {       if (reached[n][i] & 2) printf("+");
                                       if (i == Ist[n]) printf("*");
                                       printf("\t%d", i);
                                       wrap_text(" -[", matrix[n][i][j]->n, "]->\t");
                                       printf("%d\n", j);
       }       }       }       }
       printf("#endif\n\n");
       fflush(stdout);
}

void
sync_product(void)
{       ProcList *p;
       Element *e;
       int n, i;

       if (nclaims <= 1) return;

       (void) unlink("pan.pre");

       Ist  = (int *) emalloc(sizeof(int) * nclaims);
       Nacc = (int *) emalloc(sizeof(int) * nclaims);
       Nst  = (int *) emalloc(sizeof(int) * nclaims);
       reached = (int **) emalloc(sizeof(int *) * nclaims);
       Selfs   = (Element **) emalloc(sizeof(Element *) * nclaims);
       matrix  = (Element ****) emalloc(sizeof(Element ***) * nclaims); /* claims */

       for (p = rdy, i = 0; p; p = p->nxt, i++)
       {       if (p->b == N_CLAIM)
               {       nst = max(p->s->maxel, nst);
                       Nacc[i] = claim_has_accept(p);
       }       }

       for (n = 0; n < nclaims; n++)
       {       reached[n] = (int *) emalloc(sizeof(int) * nst);
               matrix[n] = (Element ***) emalloc(sizeof(Element **) * nst);    /* rows */
               for (i = 0; i < nst; i++)                                       /* cols */
               {       matrix[n][i] = (Element **) emalloc(sizeof(Element *) * nst);
       }       }

       for (e = Al_El; e; e = e->Nxt)
       {       e->status &= ~DONE;
       }

       for (p = rdy, n=0; p; p = p->nxt, n++)
       {       if (p->b == N_CLAIM)
               {       /* fill in matrix[n] */
                       e = p->s->frst;
                       Ist[n] = huntele(e, e->status, -1)->seqno;

                       reached[n][Ist[n]] = 1|2;
                       get_seq(n, p->s);
       }       }

       if (verbose)    /* show only the input automata */
       {       print_raw();
       }

       gen_product();  /* create product automaton */
}

static int
nxt_trans(int n, int cs, int frst)
{       int j;

       for (j = frst; j < nst; j++)
       {       if (reached[n][cs]
               &&  matrix[n][cs][j])
               {       return j;
       }       }
       return -1;
}

static void
print_state_nm(char *p, int *s, char *a)
{       int i;
       printf("%sP", p);
       for (i = 0; i < nclaims; i++)
       {       printf("_%d", s[i]);
       }
       printf("%s", a);
}

static void
create_transition(OneState *s, SQueue *it)
{       int n, from, upto;
       int *F = s->combo;
       int *T = it->state.combo;
       Succ_List *sl;
       Lextok *t;

       if (verbose&64)
       {       print_state_nm("", F, " ");
               print_state_nm("-> ", T, "\t");
       }

       /* check if any of the claims is blocked */
       /* which makes the state a dead-end */
       for (n = 0; n < nclaims; n++)
       {       from = F[n];
               upto = T[n];
               t = matrix[n][from][upto]->n;
               if (verbose&64)
               {       wrap_text("", t, " ");
               }
               if (t->ntyp == 'c'
               &&  t->lft->ntyp == CONST)
               {       if (t->lft->val == 0)   /* i.e., false */
                       {       goto done;
       }       }       }

       sl = (Succ_List *) emalloc(sizeof(Succ_List));
       sl->s = it;
       sl->nxt = s->succ;
       s->succ = sl;
done:
       if (verbose&64)
       {       printf("\n");
       }
}

static SQueue *
find_state(int *cs)
{       SQueue *nq, *a = sq;
       int i;

again:  /* check in nq, sq, and then in the render q */
       for (nq = a; nq; nq = nq->nxt)
       {       if (same_state(nq->state.combo, cs))
               {       return nq;      /* found */
       }       }
       if (a == sq && sd)
       {       a = sd;
               goto again; /* check the other stack too */
       } else if (a == sd && render)
       {       a = render;
               goto again;
       }

       nq = (SQueue *) emalloc(sizeof(SQueue));
       nq->state.combo = (int *) emalloc(nclaims * sizeof(int));
       for (i = 0; i < nclaims; i++)
       {       nq->state.combo[i] = cs[i];
       }
       nq->nxt = sq;   /* add to sq stack */
       sq = nq;

       return nq;
}

static SQueue *
retrieve_state(int *s)
{       SQueue *nq, *last = NULL;

       for (nq = sd; nq; last = nq, nq = nq->nxt)
       {       if (same_state(nq->state.combo, s))
               {       if (last)
                       {       last->nxt = nq->nxt;
                       } else
                       {       sd = nq->nxt;   /* 6.4.0: was sd = nq */
                       }
                       return nq;      /* found */
       }       }

       fatal("cannot happen: retrieve_state", 0);
       return (SQueue *) 0;
}

static void
all_successors(int n, OneState *cur)
{       int i, j = 0;

       if (n >= nclaims)
       {       create_transition(cur, find_state(Nst));
       } else
       {       i = cur->combo[n];
               for (;;)
               {       j = nxt_trans(n, i, j);
                       if (j < 0) break;
                       Nst[n] = j;
                       all_successors(n+1, cur);
                       j++;
       }       }
}

static void
gen_product(void)
{       OneState *cur_st;
       SQueue *q;

       find_state(Ist);        /* create initial state */

       while (sq)
       {       if (in_stack(sq, sd))
               {       sq = sq->nxt;
                       continue;
               }
               cur_st = &(sq->state);

               q = sq;
               sq = sq->nxt;   /* delete from sq stack */
               q->nxt = sd;    /* and move to done stack */
               sd = q;

               all_successors(0, cur_st);
       }
       /* all states are in the sd queue now */
       prune_dead();
       explore_product();      /* check if added accept-self-loops are reachable */
       prune_accept();

       if (verbose)
       {       print_raw();
       }

       /* PM: merge states with identical successor lists */

       /* all outgoing transitions from accept-states
          from claim n in copy n connect to states in copy (n+1)%nclaims
          only accept states from claim 0 in copy 0 are true accept states
          in the product

          PM: what about claims that have no accept states (e.g., restrictions)
       */

       for (unfolding = 0; unfolding < nclaims; unfolding++)
       {       print_product();
       }
}

static void
t_record(int n, Element *e, Element *g)
{       int from = e->seqno, upto = g?g->seqno:0;

       assert(from >= 0 && from < nst);
       assert(upto >= 0 && upto < nst);

       matrix[n][from][upto] = e;
       reached[n][upto] |= 1;
}

static void
get_sub(int n, Element *e)
{
       if (e->n->ntyp == D_STEP
       ||  e->n->ntyp == ATOMIC)
       {       fatal("atomic or d_step in never claim product", 0);
       }
       /* NON_ATOMIC */
       e->n->sl->this->last->nxt = e->nxt;
       get_seq(n, e->n->sl->this);

       t_record(n, e, e->n->sl->this->frst);

}

static void
set_el(int n, Element *e)
{       Element *g;

       if (e->n->ntyp == '@')  /* change to self-loop */
       {       e->n->ntyp = CONST;
               e->n->val = 1;  /* true */
               e->nxt = e;
               g = e;
               mk_accepting(n, e);
       } else

       if (e->n->ntyp == GOTO)
       {       g = get_lab(e->n, 1);
               g = huntele(g, e->status, -1);
       } else if (e->nxt)
       {       g = huntele(e->nxt, e->status, -1);
       } else
       {       g = NULL;
       }

       t_record(n, e, g);
}

static void
get_seq(int n, Sequence *s)
{       SeqList *h;
       Element *e;

       e = huntele(s->frst, s->frst->status, -1);
       for ( ; e; e = e->nxt)
       {       if (e->status & DONE)
               {       goto checklast;
               }
               e->status |= DONE;

               if (e->n->ntyp == UNLESS)
               {       fatal("unless stmnt in never claim product", 0);
               }

               if (e->sub)     /* IF or DO */
               {       Lextok *x = NULL;
                       Lextok *y = NULL;
                       Lextok *haselse = NULL;

                       for (h = e->sub; h; h = h->nxt)
                       {       Lextok *t = h->this->frst->n;
                               if (t->ntyp == ELSE)
                               {       if (verbose&64) printf("else at line %d\n", t->ln);
                                       haselse = t;
                                       continue;
                               }
                               if (t->ntyp != 'c')
                               {       fatal("product, 'else' combined with non-condition", 0);
                               }

                               if (t->lft->ntyp == CONST       /* true */
                               &&  t->lft->val == 1
                               &&  y == NULL)
                               {       y = nn(ZN, CONST, ZN, ZN);
                                       y->val = 0;
                               } else
                               {       if (!x)
                                               x = t;
                                       else
                                               x = nn(ZN, OR, x, t);
                                       if (verbose&64)
                                       {       wrap_text(" [", x, "]\n");
                       }       }       }
                       if (haselse)
                       {       if (!y)
                               {       y = nn(ZN, '!', x, ZN);
                               }
                               if (verbose&64)
                               {       wrap_text(" [else: ", y, "]\n");
                               }
                               haselse->ntyp = 'c';    /* replace else */
                               haselse->lft = y;
                       }

                       for (h = e->sub; h; h = h->nxt)
                       {       t_record(n, e, h->this->frst);
                               get_seq(n, h->this);
                       }
               } else
               {       if (e->n->ntyp == ATOMIC
                       ||  e->n->ntyp == D_STEP
                       ||  e->n->ntyp == NON_ATOMIC)
                       {       get_sub(n, e);
                       } else
                       {       set_el(n, e);
                       }
               }
checklast:      if (e == s->last)
                       break;
       }
}