#include <string.h>
#include <stdlib.h>
#include <math.h>
#include "calpgm.h"

/*   Copyright (C) 1989, California Institute of Technology */
/*   All rights reserved.  U. S. Government Sponsorship under */
/*   NASA Contract NAS7-918 is acknowledged. */

/*   Herbert M. Pickett, 20 Mar 1989 */
/*   Revised version in c, 22 March 1999 */
/*   fixed bug in that freed structure before data needed */
/*   funny comments with @ are modifiers for splint syntax checker */
/*   8/12/04 fixed format for big frequencies */
/**************************************************************************/
#define NDQN  20
#define NHASH 100
#define KFRQ  13
#define PERR  KFRQ
#define KERR  8
#define PSTR  PERR+KERR
#define KSTR  8
#define PEGY  PSTR+KSTR+2
#define KEGY  10
#define PDGN  PEGY+KEGY
#define KDGN  3
#define PTAG  PDGN+KDGN
#define KTAG  7
#define PQNF  PTAG+KTAG
#define KQNF  4
#define PQNU  PQNF+KQNF

typedef struct expt {
  /*@owned@*/ /*@null@*/ struct expt *next;
  double frqx;
  float errx;
  short iqn[NDQN], nbad;
} EXPT;
typedef /*@owned@*/ /*@null@*/ EXPT *PEXPT;
typedef /*@dependent@*/ /*@null@*/ EXPT *PDEXPT;

void prline(FILE *lu, int nqn, short *pqn, double frq, double err, double st,
            char *label);
int lposn(const short *ikey, const int nkey, const int *k12,
          PEXPT *head, /*@out@*/ PDEXPT *ppexpt);
void dolink(PEXPT *head, PDEXPT pexpt,
            const double xfrq, const double xerr, const short *iqn);
void unlink(PEXPT *head, /*@notnull@*/ EXPT *pexpt, 
            /*@out@*/ int *jmin,/*@out@*/ int *jmax);
BOOL mrglin(char *line2, char *line);

int main(argc, argv)
int argc;
char *argv[];
{
#define NFILE 6
#define NDLINE 130
  static PEXPT jhash[NHASH + 1];
  static const char *cext[NFILE] = {"cat","lin","mrg","new","log","opt"};
  enum efile {ecat, elin, emrg, enew, elog, eopt};
  static const double clight = 29979.2458;
  static const double big = 9999.9999;
  static int fmtlen[] = { KFRQ, KERR, KSTR };
  static const int blank = (int)' ';
  static int fmtqnf = KQNF;
  static short iqn1[NDQN], iqn2[NDQN], iqmap[NDQN], iqmaplin[NDQN];
  static char *cfil[NFILE+1];
  static char line1[NDLINE], line2[NDLINE], tmpstr[NDLINE];
  static char qnstr[NDQN+NDQN+1], tagstr[8];
  short *pqn, tqn;
  double cfrq, cfrq0, errc, xerr, st, st0, xfrq, frqdif, xwt, ratio, dtmp[3];
  int j, k, nbad, nout, nline, iqfmt, jmax, nqn2, nline2, catqn, pqnlo, jx;
  int minb, k1, k2, k12[2], nqn, nmatch, nmerge, icmp, nqnlin, nqnlin2;
  int jmax0, jmin, jmin0, nmap, kcmp, nxhash;
  BOOL match, noeof;
  FILE *lucat, *lunew, *lulog, *lumrg, *lulin, *luopt;
  EXPT *pexpt, *pexptp, *plast, *pnext;
  nmerge = nmatch = nout = nqn = nqn2 = 0;
  filget(argc, argv, NFILE, cfil, cext);
  rqexit(-1);
  for (k = 0; k <= NHASH; ++k)
    jhash[k] = NULL;
  lulin = fopenq(cfil[elin], "r");
  lulog = fopenq(cfil[elog], "w");
  lunew = fopenq(cfil[enew], "w");
  lucat = fopenq(cfil[ecat], "r");
  /* read qn format */
  nline2 = fgetstr(line2, NDLINE, lucat);
  iqfmt = 1;
  if (nline2 > PQNU) {
    memcpy(tagstr, line2 + PTAG, KTAG);
    tagstr[KTAG] = '\0';
    for (k = 1; k < KTAG; k++) {
      if (tagstr[k] == '-')
        break;
      if (tagstr[k] != ' ') {
        tagstr[k - 1] = '-';
        break;
      }
    }
    pcard(line2 + PQNF, dtmp, 1, &fmtqnf);
    iqfmt = (int) dtmp[0];
  }
  nqn = deflin(iqfmt, iqn1);
  nqn2 = nqn + nqn;
  catqn = 12;
  if (nqn > 6) catqn = nqn2;
  pqnlo = PQNU + catqn;
  minb  = pqnlo + nqn2;
  if (nline2 < minb) {
    puts("Bad CAT File");
    exit(EXIT_FAILURE);
  }
  /* read options */
  ratio = 0.; nqnlin = nqn; 
  luopt = fopen(cfil[eopt],"r");
  if (luopt == NULL) {
    printf("enter MAX (obs-calc)/(exp. error), no. quanta for lin (%2d) "
           , nqn);
    fflush(stdout);
    luopt = stdin;
  }
  if (fgetstr(tmpstr, NDLINE, luopt) > 0) {
    dtmp[0] = 100.; dtmp[1] = (double)nqn;
    pcard(tmpstr, dtmp, 2, NULL);
    ratio = dtmp[0]; nqnlin = (int) dtmp[1];
    if (nqnlin > 10) nqnlin = 10;
  }
  nmap = 0;
  if (nqnlin != nqn) {
    if (nqnlin < 0) {
      nqnlin = -nqnlin;
      if (nqnlin > nqn)
        nqnlin = nqn;
      --nqnlin;
      iqmap[0] = (short)nqnlin; iqmaplin[0] = (short)nqnlin;
      nmap = 1; nqnlin = nqn;
    } else {
      nmap = (nqnlin < nqn)? nqnlin: nqn;
      nmap = nmap - 2;
      for (k = 0; k < nmap; ++k) {
        iqmap[k] = (short)k; iqmaplin[k] = (short)k;
      }
      iqmap[nmap] = (short)(nqn - 1); 
      iqmaplin[nmap] = (short)(nqnlin - 1);
      ++nmap;
    }
    for (k = 0; k < nmap; ++k) {
      iqmap[k + nmap] = (short)(iqmap[k] + nqn); 
      iqmaplin[k + nmap] = (short)(iqmaplin[k] + nqnlin);
    }
    nmap = nmap << 1;
  }     
  nqnlin2 = nqnlin << 1;
  if (luopt != stdin) fclose(luopt);
  if (nqn <= 6) {
    memset(line1 + pqnlo, blank, 13);
  }
  k1 = 1; k2 = k1 + nqn;
  k12[0] = -1; k12[1] = k2;
  /*  read and sort experimental lines */
  nline = 0; jmax = 0; jmin = NHASH;
  for (;;) {
    if (getlin(lulin, nqnlin, iqn1, iqn2, &xfrq, &xerr, &xwt,
               tmpstr, NDLINE) < 0) {
      fclose(lulin);
      break;
    }
    if (iqn2[nqnlin] < 0)
      continue;
    errc = xerr;
    if (xerr < 0.) {
      xfrq *= clight;
      errc *= clight;
      xerr = -errc;
    }
    if (xerr > 999.999)
      continue;
    if (xfrq < 0.) { 
      /* swap quanta so frequency is positive */
      for (k = 0; k < nqnlin; ++k) {
        tqn = iqn2[k];
        iqn2[k] = iqn2[k + nqnlin];
        iqn2[k + nqnlin] = tqn;
      }
      xfrq = -xfrq;
    }
    /* set up HASH table */
    j = (int)iqn2[0]; 
    if (j > jmax)
      jmax = j;
    if (j < jmin)
      jmin = j;
    icmp = lposn(iqn2, nqnlin2, k12, jhash, &pexpt);
    if (icmp == 0) {
      /* lines have identical quanta */
      xwt = pexpt->errx;
      if (xwt < 0.)
        xwt = -xwt;
      if (xerr < xwt) {
        pexpt->frqx = xfrq;
        pexpt->errx = (float) xerr;
      }
      continue;
    } else {
      dolink(jhash, pexpt, xfrq, xerr, iqn2);
    }
    ++nline;
  }
  if (nline == 0) ratio = 0.;
  /* open .mrg output file */
  lumrg = fopenq(cfil[emrg], "w");
  printf(" %d  experimental lines read\n", nline);
  fflush(stdout);
  memcpy(qnstr, line2 + PQNU, (size_t) nqn2);
  memcpy(qnstr + nqn2, line2 + pqnlo, (size_t) nqn2);
  readqn(qnstr, iqn2, nqn2);
  noeof = TRUE;
  while (noeof) {
    memcpy(line1, line2, (size_t) minb);
    memcpy(iqn1, iqn2, sizeof(iqn2));
    if (rqexit(0) != 0)
      noeof = FALSE;
    nline2 = fgetstr(line2, NDLINE, lucat);
    if (nline2 >= minb) {
      memcpy(qnstr, line2 + PQNU, (size_t) nqn2);
      memcpy(qnstr + nqn2, line2 + pqnlo, (size_t) nqn2);
      readqn(qnstr, iqn2, nqn2);
    } else {                    /* set NOEOF = false for end of file */
      noeof = FALSE;
    }
    if (strncmp(line1 + PTAG, tagstr, KTAG) == 0) {
      /* write through experimental lines */
      ++nout;
      fputs(line1, lumrg); fputc('\n',lumrg);
      continue;
    }
    match = noeof;
    /* ...test for equality of frequency and quanta except K */
    if (match)
      match = (strncmp(line1, line2, KFRQ) == 0);
    if (match) {
      icmp = 0; kcmp = k1;
      for (k = 0; k < nqn2; ++k) {
        icmp = iqn1[k] - iqn2[k];
        if (k == kcmp) {
          if (icmp != 0) 
            icmp = iqn1[k] + iqn2[k];
          kcmp = k2;
        }
        if (icmp != 0)
          break;
      }
      match = (icmp == 0);
    }
    if (match)
      match = mrglin(line1, line2);
    if (match) {                /*  K's are equivalent */
      if (iqn2[k1] < 0) {
        line2[PQNU+2] = line1[PQNU+2];
        iqn2[k1] = iqn1[k1];
      }
      if (iqn2[k2] < 0) {
        line2[pqnlo+2] = line1[pqnlo+2];
        iqn2[k2] = iqn1[k2];
      }
      k12[0] = k1;
      ++nmerge; 
    } else {                    /* ready to ouput line */
      j = (int)iqn1[0];         /* test for quantum match */
      if (j >= jmin && j <= jmax && nqn == nqnlin && 
          lposn(iqn1, nqn2, k12, jhash, &pexpt) == 0) {
        pcard(line1, dtmp, 3, fmtlen);
        cfrq = dtmp[0];
        errc = dtmp[1];
        st   = dtmp[2];
        xfrq = pexpt->frqx;
        xerr = pexpt->errx;
        if (xfrq < 99999999.) {
          sprintf(tmpstr,"%13.4f%8.4f", xfrq, xerr);
        } else {
          sprintf(tmpstr,"%13.3f%8.3f", xfrq, xerr);
        }
        memcpy(tmpstr + PSTR, line1 + PSTR, PTAG - PSTR);
        memcpy(tmpstr + PTAG, tagstr, KTAG);
        strcpy(tmpstr + PQNF, line1 + PQNF);
        frqdif = xfrq - cfrq;
        if (frqdif > big) {
          frqdif = big;
        } else if (frqdif < -big) {
          frqdif = -big;
        }
        fprintf(lulog, "%.25s %14.4f%10.4f%10.4f%10.4f\n", line1 + PQNU,
                xfrq, frqdif, xerr, errc);
        if (fabs(frqdif) < xerr * ratio) {
          st = pow(10., st);
          prline(lunew, nqn2, iqn1, xfrq, xerr, st, "q");
          strcpy(line1, tmpstr);
          ++nmatch;
          unlink(jhash, pexpt, &jmin, &jmax);
          if (k12[0] >= 0 && lposn(iqn1, nqn2, k12, jhash, &pexpt) == 0) {
            ++nmatch;
            unlink(jhash, pexpt, &jmin, &jmax);
          }
        } else {
          fputs(" Above Experiment not used\n", lulog);
        }
      }
      ++nout; k12[0] = -1;
      fputs(line1, lumrg); fputc('\n',lumrg);
    }
  }                             /* end loop over calculated lines */
  printf(       "%d  lines written %d experimental lines \n", nout, nmatch);
  fprintf(lulog,"%d  lines written %d experimental lines \n", nout, nmatch);
  if (nmerge > 0) {
    printf(       " %d lines merged\n", nmerge);
    fprintf(lulog," %d lines merged\n", nmerge);
  }
  fclose(lucat);
  fclose(lumrg);
  nbad = nline - nmatch;
  nxhash = jmax;
  if (nxhash > NHASH)
    nxhash = NHASH;
  if (nbad > 0) {     /* find bad lines */
    printf(        " %d  lines to be matched by frequency\n", nbad);
    fprintf(lulog, " %d  lines to be matched by frequency\n", nbad);
  }
  nmatch = 0;
  lumrg = fopenq(cfil[emrg], "r");
  cfrq0 = -999999.; st0 = -500.; 
  if (ratio < 0.001)
    nbad = 0;
  while (nbad > 0) {
    if (jmin > jmax) break;
    if (fgetstr(line1, NDLINE, lumrg) < minb) break;
    if (strncmp(line1 + PTAG, tagstr, KTAG) == 0) 
      continue;  /* line already assigned */
    memcpy(qnstr, line1 + PQNU, (size_t) nqn2);
    memcpy(qnstr + nqn2, line1 + pqnlo, (size_t) nqn2);
    readqn(qnstr, iqn1, nqn2);
    jmin0 = jmin; jmax0 = nxhash;
    if (nmap >  0) {
      if (iqmap[0] == 0) {
        jmin0 = iqn1[0];
        if (jmin0 < jmin) continue;
        if (jmin0 > jmax) continue;
        if (jmin0 > NHASH)
          jmin0 = NHASH;
        jmax0 = jmin0;
      }
      for (k = 0; k < nmap; ++k) 
        iqn2[k] = iqn1[iqmap[k]];
    } 
    pcard(line1, dtmp, 3, fmtlen);
    //    cfrq0 = cfrq; st0 = st; 
    cfrq = dtmp[0];
    errc = dtmp[1];
    st = dtmp[2];
    //    if (fabs(cfrq - cfrq0) < 0.00015 && fabs(st - st0) < 0.00015) continue;
    st = pow(10., st);
    for (jx = jmin0; jx <= jmax0; ++jx) {
      for (pexpt = jhash[jx]; pexpt != NULL; pexpt = pexpt->next) {
        xfrq = pexpt->frqx;
        frqdif = cfrq - xfrq;
        xerr = pexpt->errx;
        if (fabs(frqdif) < ratio * xerr) {
          pqn = pexpt->iqn;
          if (nmap > 0) {
            icmp = 0;
            for (k = 0; k < nmap; ++k) {
              icmp = (int) (iqn2[k] - pqn[iqmaplin[k]]);
              if (icmp != 0) break;
            }
            if (icmp != 0) continue;
          }
          if (nqn2 != nqnlin2) {
            prline(lunew, nqnlin2,  pqn, xfrq, xerr, st, "f");
          } else {
            prline(lunew, nqnlin2, iqn1, xfrq, xerr, st, "f");
            pexptp = pexpt;
            for(;;) {
              plast = pexptp; pexptp = pexptp->next;
              if (pexptp == NULL) break;
              if (fabs(xfrq - pexptp->frqx) < 0.00015) {
                pnext = pexptp->next; 
                free(plast->next);
                plast->next = pnext; pnext = NULL; 
                pexptp = plast;
              }
            }
          }
          pexpt->nbad += (short) 1;
          ++nmatch;
        }
      } 
    }
  }
  if (nbad > 0) {
    printf(       "%d  lines found\n", nmatch);
    fprintf(lulog,"%d  lines found\n", nmatch);
    st = 0.;
    for (jx = jmin; jx <= nxhash; ++jx) {
      pexpt = jhash[jx];
      while (pexpt != NULL) {
        pexptp = pexpt->next;
        if (pexpt->nbad == (short) 0) {
          pqn = pexpt->iqn;
          xfrq = pexpt->frqx;
          xerr = pexpt->errx;
          prline(lunew, nqnlin2, pqn, xfrq, xerr, st, "u");
          pqn = NULL;
        }
        unlink(jhash, pexpt, &jmin, &jmax);
        pexpt = pexptp;
      }
    }
    pexptp = NULL;
  }
  fclose(lumrg);
  fclose(lunew);
  fclose(lulog);
  return 0;
}                               /* main */

void prline(FILE *lu, int nqn, short *pqn, double frq, double err, double st,
            char *label)
{
  int k;
  for (k = 0; k < nqn; ++k) {
    fprintf(lu, "%3d", (int) pqn[k]);
  }
  for (k = nqn; k < 12; ++k) {
    fputs("   ", lu);
  }
  fprintf(lu, "%15.4f %10.4f %10.3E %s\n", frq, err, st, label);
}

int lposn(ikey, nkey, k12, head, ppexpt)
const short *ikey;
const int nkey, *k12;
PEXPT *head;
PDEXPT *ppexpt;
{
  EXPT *now, *last;
  short *iqnx;
  int k, icmp, kcmp, jx;
  last = NULL;
  jx = ikey[0];
  if (jx > NHASH)
    jx = NHASH;
  now = head[jx];
  icmp = -1; 
  while (now != NULL) {
    iqnx = now->iqn; 
    icmp = iqnx[0] - ikey[0];
    if (icmp == 0) {
      kcmp = k12[0];
      for (k = 1; k < nkey; ++k) {
        icmp = iqnx[k] - ikey[k];
        if (k == kcmp) {
          if (icmp != 0)
            icmp = -iqnx[k] - ikey[k];
          kcmp = k12[1];
        }
        if (icmp != 0) break;
      }
    }
    if (icmp >= 0)
      break;
    last = now;
    now = now->next;
  }
  if (icmp != 0){ 
    *ppexpt = last;
  } else {
    *ppexpt = now;
  }
  return icmp;
}                               /* lposn */

void dolink(head, pexpt, xfrq, xerr, iqn)
PEXPT *head;
PDEXPT pexpt;
const double xfrq, xerr;
const short *iqn;
{
  /* insert new link after pexpt */
  EXPT *now;
  int jx;
  now = (EXPT *) mallocq(sizeof(EXPT));
  now->frqx = xfrq;
  now->errx = (float) xerr;
  memmove(now->iqn, iqn, (size_t) NDQN * sizeof(short));
  now->nbad = 0;
  if (pexpt == NULL) {
    jx = (int)iqn[0];
    if (jx > NHASH)
      jx = NHASH;
    now->next = head[jx];
    head[jx] = now;
  } else {
    now->next = pexpt->next;
    pexpt->next = now;
  }
}

void unlink(head, pexpt, jmin, jmax)
PEXPT *head;
EXPT *pexpt;
int *jmin, *jmax;
{
  EXPT *now, *last;
  int jx, jj;
  jx = (int)(pexpt->iqn[0]);
  if (jx > NHASH)
    jx = NHASH;
  now = head[jx];
  if (now == NULL) return;
  if (now == pexpt) { /* pexpt == head */
    head[jx] = now->next;
    if (head[jx] == NULL) {
      if (*jmax == jx) {
        jj = (*jmax) - 1;
        while(*jmin < jj && head[jj] == NULL) --jj;
        *jmax = jj;
      } else if (*jmin == jx) {
        jj = (*jmin) + 1;
        while(*jmax > jj && head[jj] == NULL) ++jj;
        *jmin = jj;
      }
    }
    free(pexpt);
    return;
  }
  do { /* find pexpt */
    last = now; now = now->next;
    if (now == pexpt) {
      now = pexpt->next;
      if (now == NULL && jx == NHASH) {
        *jmax = (int)(last->iqn[0]);
      }
      last->next = now;
      free(pexpt);
      return;
    } 
  } while (now != NULL);
}

BOOL mrglin(line2, line)
char *line2, *line;
{
  static double cnv = 2.3025851; /* 1 / log10(e) */
  static int fmtstr = KSTR;
  static char tmpstr[20];
  short ig1, ig2;
  int igup;
  double dif, str1, str2;

  pcard(line + PSTR, &str1, 1, &fmtstr);
  readqn(line + PDGN, &ig1, 0);
  igup = ig1;
  if (strncmp(line + PSTR, line2 + PSTR, KSTR) == 0) {
    str1 += 0.30103;
    igup += ig1;
  } else {
    pcard(line2 + PSTR, &str2, 1, &fmtstr);
    readqn(line2 + PDGN, &ig2, 0);
    if (str2 < str1) {
      dif = str2 - str1;
    } else {
      dif = str1 - str2;
      str1 = str2;
    }
    if (dif > -5.) {
      dif = exp(dif * cnv) + 1.;
      str1 += log10(dif);
    }
    igup += ig2;
  }
  if (igup >= 3600)
    return FALSE;
  sprintf(tmpstr,"%8.4f", str1);
  memcpy(line + PSTR, tmpstr, KSTR);
  gupfmt(igup, line + PDGN);
  return TRUE;
}                               /* mrglin */