#include <math.h>
#include <stdio.h>
#include <assert.h>
#include "myc_err.h"
#include "myc_mem.h"
#include "myc_str.h"
#include "myc_fortran.h"

#define NAME_LEN 32
#define STR_SIZE 2048
#define STD_FORMAT 8230

typedef struct Itm_ {
  struct Itm_ *next;
  char *str;
  int cnt, level, fmt, allsize;
  float vmin, vmax, value;
  char name[NAME_LEN], lowName[NAME_LEN];
} Itm;

static Itm *head, *lend;

static int level, min_level=99999, max_level=-99999;
static int format=STD_FORMAT; /* width*1000+fixlen*100+digits*10+trunc) */

Itm *meta_find(char *name) {
  Itm *p, *res;
  char lowName[NAME_LEN];
  static int init=1;

  res=NULL;
  p=NULL;
  if (init) {
    init=0;
    head=NULL;
    lend=NULL;
  }
  str_lowcase(lowName, name);
  p=head;
  while (p!=NULL) {
    if (0==strcmp(p->lowName, lowName)) {
      if (0==strcmp(p->name, name)) return p;
      res=p;
    }
    p=p->next;
  }
  return res;
}


Itm *meta_find_create(char *name) {
  Itm *p;

  p=meta_find(name);
  if (p==NULL) {
    NEW(p, Itm);
    assert(p->str==NULL);
    str_copy(p->name, name);
    p->cnt=0;
    str_lowcase(p->lowName, name);
    if (lend==NULL) {
      head=p;
    } else {
      lend->next=p;
    }
    lend=p;
  }
  p->level=level;
  if (level>max_level) max_level=level;
  if (level<min_level) min_level=level;
  return p;
  OnError: assert("no more memory");
    return NULL;
}

int F_FUN(meta_lim_level)(int *this) {
  if (*this<min_level) return min_level;
  if (*this>max_level) return max_level;
  return *this;
}
 
void F_FUN(meta_set_level)(int *this) {
  level=*this;
}
 
void F_FUN(meta_get_str)(F_CHAR(name), F_CHAR(value) F_CLEN(name) F_CLEN(value)) {
  char nam[NAME_LEN];
  Itm *p;
  static char buf[32];

  STR_TO_C(nam, name);
  p=meta_find(nam);
  if (p==NULL) return;
  if (p->str!=NULL) {
    STR_TO_F(value, p->str);
  } else {
    sprintf(buf, "%f", p->value);
    STR_TO_F(value, buf);
  }
}

void F_FUN(meta_get_real)(F_CHAR(name), float *value F_CLEN(name)) {
  char nam[NAME_LEN];
  Itm *p;

  STR_TO_C(nam, name);
  p=meta_find(nam);
  if (p!=NULL) {
    *value=p->value;
  }
}

void F_FUN(meta_real_range)(F_CHAR(name), float *vmin, float *vmax F_CLEN(name)) {
  char nam[NAME_LEN];
  Itm *p;

  STR_TO_C(nam, name);
  p=meta_find(nam);
  if (p!=NULL) {
    *vmin=p->vmin;
    *vmax=p->vmax;
  }
}

void F_FUN(meta_put_str)(F_CHAR(name), F_CHAR(value), int *overwrite F_CLEN(name) F_CLEN(value)) {
  char nam[NAME_LEN], val[STR_SIZE];
  Itm *p;
  int l;

  STR_TO_C(nam, name);
  STR_TO_C(val, value);

  p=meta_find_create(nam);
  l=(strlen(val)/16+1)*16;
  if (p->str==NULL) {
    p->allsize=l;
    p->str=MALLOC(l);
    p->str[0]='\0';
    p->cnt=0;
  } else if (l>p->allsize) {
    free(p->str);
    p->allsize=l;
    p->str=MALLOC(l);
    p->str[0]='\0';
    p->cnt=0;
  } else if (*overwrite) {
    p->cnt=0;
  }
  if (p->cnt > 0) {
    if (0==strcmp(p->str, val)) return;
  }
  p->cnt++;
  str_ncpy(p->str, val, p->allsize);
}

void F_FUN(meta_put_real)(F_CHAR(name), float *value, int *overwrite F_CLEN(name)) {
  Itm *p;
  char nam[NAME_LEN];

  STR_TO_C(nam, name);
  p=meta_find_create(nam);
  if (p->str!=NULL) {
    FREE(p->str);
    p->str=NULL;
    p->cnt=0;
  } else if (*overwrite>0) {
    p->cnt=0;
  }
  if (p->cnt <= 0) {
    p->vmin=*value;
    p->vmax=*value;
  } else {
    if (*value < p->vmin) p->vmin=*value;
    if (*value > p->vmax) p->vmax=*value;
  }
  p->value=*value;
  p->cnt++;
  p->fmt=format;
}

void F_FUN(meta_purge)(int *from, int *to) {
  Itm *p, *p0, *pn;

  p0=NULL;
  p=head;
  while (p!=NULL) {
    if (*from <= p->level && p->level <= *to) {
      pn=p->next;
      if (p->str!=NULL) FREE(p->str);
      FREE(p);
      if (p0!=NULL) {
        p0->next=pn;
      } else {
        head=pn;
      }
      p=pn;
    } else {
      p0=p;
      p=p->next;
    }
  }
  lend=p0;
}

void F_FUN(cvt_real_str)(F_CHAR(fstr), int *l, float *f, int *w, int *d, int*g, int *t F_CLEN(fstr));

int meta_cvt_real(char *str, int fmt, float value) {
  int fdig, gdig, wid, trc, l;
  char buf[12];
  F_DCHAR(fstr,10);

  wid=fmt/1000; fmt=fmt % 1000;
  fdig=fmt/100; fmt=fmt % 100;
  gdig=fmt/10;
  trc=fmt % 10;
  F_FUN(cvt_real_str)(fstr, &l, &value, &wid, &fdig, &gdig, &trc F_ALEN(fstr));
  STR_TO_C(buf, fstr);
  strcpy(str, buf);
  return l;
}

int meta_fmt_item(Itm *p, char *line, int lineLen, int list_mode) {
  int l, fmt, trc;
  
  str_ncpy(line, p->name, lineLen);
  if (p->str) {
    str_ncat(line, "='", lineLen);
    str_ncat(line, p->str, lineLen);
    l=strlen(line);
    if (l < lineLen-2) {
      if (p->cnt > 1 && list_mode < 2 && line[l-1]!='?') {
        line[l]='?'; l++;
      }
      line[l]='\''; l++;
      line[l]='\0';
    }
  } else {
    if (p->fmt==0) {
      fmt=format;
    } else {
      fmt=p->fmt;
    }
    if (fmt % 10 == 3) {
      trc=3;
    } else {
      trc=1;
    }
    str_ncat(line,"=", lineLen);
    l=strlen(line);
    if (l < lineLen - 24) {
      if (list_mode==0) {
        l+=meta_cvt_real(line+l, 1060+trc, p->vmin);
      } else if (list_mode==1) {
        l+=meta_cvt_real(line+l, fmt, p->vmin);
      } else {
        l+=meta_cvt_real(line+l, fmt, p->value);
      }
      if (p->vmax != p->vmin && list_mode < 2) {
        if (list_mode==0) {
          line[l]=' '; l++;
          l+=meta_cvt_real(line+l, 1060+trc, p->vmax);
        } else {
          line[l]='.'; l++;
          line[l]='.'; l++;
          l+=meta_cvt_real(line+l, fmt, p->vmax);
        }
      }
    }
  }
  return l;
}

void F_FUN(meta_show)(F_CHAR(name) F_CLEN(name)) {
  char nam[NAME_LEN];
  Itm *p;
  char line[256];

  STR_TO_C(nam, name);
  p=meta_find(nam);
  if (p!=NULL) {
    meta_fmt_item(p, line, sizeof line, 0);
    printf(" %s\n", line);
  } else {
    printf(" %s not found\n", nam);
  }
}

void F_FUN(meta_list)(int *levl, int *listmode, int *str_select, void (*outrtn)(void *arg, F_CHAR(str) F_CLEN(str))
  , void *outarg, F_CHAR(except) F_CLEN(except)) {
  Itm *p;
  char line[256], lowName[NAME_LEN+2], exc[256];
  int l,lev,list_str, list_mode;
  F_DCHAR(fline, 256);

  list_mode=*listmode; /* 0 compact (on file), 1: for info, 2: don't show min/max */
  STR_TO_C(exc, except);
  str_append(exc, " ");
  lev=*levl;
  list_str=*str_select;
  p=head;
  if (lev<min_level || lev>max_level) return;
  while (p!=NULL) {
    if (p->level==lev && (list_str!=0) == (p->str!=NULL)) { /* check if level and type matches */
      str_copy(lowName, " ");
      str_append(lowName, p->lowName);
      str_append(lowName, " ");
      if (NULL==strstr(exc, p->lowName)) {
        l=meta_fmt_item(p, line, sizeof line, list_mode);
        F_LEN(fline)=l;
        STR_TO_F(fline, line);
        outrtn(outarg, fline F_ALEN(fline));
      }
    }
    p=p->next;
  }
}

void F_FUN(meta_format)(int *fmt) {
  if (*fmt==0) {
    format=STD_FORMAT;
  } else {
    format=*fmt;
  }
}

void F_FUN(meta_set_format)(F_CHAR(name), int *fmt F_CLEN(name)) {
  Itm *p;
  char nam[NAME_LEN];
  STR_TO_C(nam, name);
  p=meta_find(nam);
  if (p!=NULL) {
    p->fmt=*fmt;
  }
}
