#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdint.h>
#include <unistd.h>

typedef struct burm_state *STATEPTR_TYPE;

#include "gforth9.h"

void burm_trace(NODEPTR_TYPE p, int eruleno, int cost, int bestcost)
{
  printf("match: op=%d, rule=%d, cost=%d, bestcost=%d\n", p->op, eruleno, cost, bestcost);
}

unsigned short *input(char *filename, size_t *nelem)
{
  void *res;
  struct stat st;
  int fd=open(filename,O_RDONLY);
  if (fd==-1) {
    perror(filename);
    exit(1);
  }
  fstat(fd, &st);
  res=mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,fd,0);
  if (res==(short *)-1) {
    perror(filename);
    exit(1);
  }
  *nelem = st.st_size/sizeof(short);
  return (unsigned short *)res;
}

/* adapted from http://www.complang.tuwien.ac.at/ublu/burg.pdf */
int reduce(NODEPTR_TYPE p, int goalnt, unsigned short **rulep)
{
   int eruleno;
   short *nts;
   NODEPTR_TYPE kids[100];
   int i;
   int cost;

   eruleno=burm_rule(STATE_LABEL(p), goalnt); /* matching rule number */
   cost = burm_cost[eruleno][0];
   **rulep = eruleno;
   (*rulep)++;
   nts=burm_nts[eruleno];               /* subtree goal non-terminals */
   if (eruleno==0) {
     fprintf(stderr, "tree cannot be derived from start symbol");
     exit(1);
   }
   burm_kids(p, eruleno, kids); /* initialize subtree pointers     */
   for (i=0; nts[i]; i++)       /* traverse subtrees left-to-right */
     cost += reduce(kids[i], nts[i], rulep);   /* reduce kids            */
   return cost;
}

void process(unsigned short *seqs, size_t nseqs, int canonical_state,
             unsigned short **rulesp, size_t *nrulesp,
             unsigned short **costsp, size_t *ncostsp)
{
  size_t i, nroots;
  //  unsigned short *s;
  struct tree leaf = {s0+canonical_state,{NULL},NULL};
  NODEPTR_TYPE nodes = calloc(nseqs,sizeof(struct tree));
  NODEPTR_TYPE *roots = calloc(nseqs,sizeof(NODEPTR_TYPE));
  NODEPTR_TYPE root = nodes;
  unsigned short *rules = calloc(nseqs*2,sizeof(short));
  unsigned short *lastrule = rules;
  unsigned short *costs = calloc(nseqs,sizeof(short));
  unsigned short *lastcosts = costs;
  
  for (i=0, nroots=0; i<nseqs; i++) {
    NODEPTR_TYPE n = nodes+i;
    if (seqs[i]!=0) {
      OP_LABEL(n) = seqs[i];
      LEFT_CHILD(n) = n+1; /* point to next node */
    } else {
      /* tree ends */
      LEFT_CHILD(n-1) = &leaf; /* redirect child of previous tree to leaf */
      roots[nroots++] = root;
      root = n+1; /* root of next tree */
    }
  }
  /* Label and reduce all trees */
  for (i=0; i<nroots; i++) {
    int cost;
    unsigned short *oldlastrule;
    NODEPTR_TYPE r=roots[i];
    STATEPTR_TYPE state=burm_label(r);
    if (state==0) {
      fprintf(stderr, "Could not label %ld-th tree\n",(long)i);
      exit(1);
    }
    oldlastrule=lastrule;
    cost = reduce(r,canonical_state+1,&lastrule);
    *(lastcosts++) = cost;
    (void)oldlastrule; /* suppress unused warning */
#ifdef DEBUG
    printf("cost: %d; rules: ", cost);
    for (; oldlastrule<lastrule; oldlastrule++)
      printf("%d ",*oldlastrule);
    printf("\n");
#endif
  }
  *rulesp  = rules;
  *nrulesp = lastrule-rules;
  *costsp  = costs;
  *ncostsp = lastcosts-costs;
}

void writefile(char *filename, void *buf, size_t n)
{
  int fd=creat(filename,0666);
  if (fd==-1) {
    perror(filename);
    exit(1);
  }
  if (write(fd,buf,n)==-1) {
    perror(filename);
    exit(1);
  }
  if (close(fd)==-1) {
    perror(filename);
    exit(1);
  }
}

int main(int argc, char *argv[])
{
  int canonical_state;
  unsigned short *rules, *costs;
  size_t nseqs, nrules, ncosts;
  unsigned short *seqs;
  if (argc!=5) {
    fprintf(stderr,"Usage: %s <infile> <rulesfile> <costsfile> [0-8]\n", argv[0]);
    exit(1);
  }
  seqs=input(argv[1],&nseqs);
  canonical_state = atoi(argv[4]);
  if (canonical_state<0 || canonical_state>8) {
    fprintf(stderr, "You cannot use state %d\n",canonical_state);
    exit(1);
  }
  process(seqs,nseqs,canonical_state,&rules,&nrules,&costs,&ncosts);
  writefile(argv[2],rules,nrules*sizeof(short));
  writefile(argv[3],costs,ncosts*sizeof(short));
  return 0;
}
