/*
  Teem: Tools to process and visualize scientific data and images             .
  Copyright (C) 2013, 2012, 2011, 2010, 2009  University of Chicago
  Copyright (C) 2008, 2007, 2006, 2005  Gordon Kindlmann
  Copyright (C) 2004, 2003, 2002, 2001, 2000, 1999, 1998  University of Utah

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public License
  (LGPL) as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  The terms of redistributing and/or modifying this software also
  include exceptions to the LGPL that facilitate static linking.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License
  along with this library; if not, write to Free Software Foundation, Inc.,
  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include "air.h"

/*
** Until Teem has its own printf implementation, this will have to do;
** it is imperfect because these are not functionally identical.
*/
#if defined(WIN32) || defined(_WIN32)
#  define snprintf _snprintf
#endif

/*
******** airEnumUnknown
**
** return the value representing "unknown" in an enum
*/
int
airEnumUnknown(const airEnum *enm) {

  if (enm && enm->val) {
    return enm->val[0];
  } else {
    return 0;
  }
}

/*
** _airEnumIndex()
**
** given an enum "enm" and value "val", return the index into enm->str[]
** and enm->desc[] which correspond to that value.  To be safe, when
** given an invalid enum value, we return zero.
*/
static unsigned int
_airEnumIndex(const airEnum *enm, int val) {
  unsigned int ii, ret;

  ret = 0;
  if (enm->val) {
    for (ii=1; ii<=enm->M; ii++) {
      if (val == enm->val[ii]) {
        ret = ii;
        break;
      }
    }
  } else {
    unsigned int uval;
    uval = AIR_UINT(val);
    ret = (0 <= val && uval <= enm->M) ? uval : 0;
  }
  return ret;
}

/*
** returns non-zero if there is an error: given "val" is *not*
** a valid value of the airEnum "enm"
*/
int
airEnumValCheck(const airEnum *enm, int val) {

  return (0 == _airEnumIndex(enm, val));
}

const char *
airEnumStr(const airEnum *enm, int val) {
  unsigned int idx;

  idx = _airEnumIndex(enm, val);
  return enm->str[idx];
}

const char *
airEnumDesc(const airEnum *enm, int val) {
  unsigned int idx;

  idx = _airEnumIndex(enm, val);
  return enm->desc[idx];
}

int
airEnumVal(const airEnum *enm, const char *str) {
  char *strCpy, test[AIR_STRLEN_SMALL];
  unsigned int ii;

  if (!str) {
    return airEnumUnknown(enm);
  }

  strCpy = airStrdup(str);
  if (!enm->sense) {
    airToLower(strCpy);
  }

  if (enm->strEqv) {
    /* want strlen and not airStrlen here because the strEqv array
       should be terminated by a non-null empty string */
    for (ii=0; strlen(enm->strEqv[ii]); ii++) {
      airStrcpy(test, AIR_STRLEN_SMALL, enm->strEqv[ii]);
      if (!enm->sense) {
        airToLower(test);
      }
      if (!strcmp(test, strCpy)) {
        free(strCpy);
        return enm->valEqv[ii];
      }
    }
  } else {
    /* enm->strEqv NULL */
    for (ii=1; ii<=enm->M; ii++) {
      airStrcpy(test, AIR_STRLEN_SMALL, enm->str[ii]);
      if (!enm->sense) {
        airToLower(test);
      }
      if (!strcmp(test, strCpy)) {
        free(strCpy);
        return enm->val ? enm->val[ii] : (int)ii; /* HEY scrutinize cast */
      }
    }
  }

  /* else we never matched a string */
  free(strCpy);
  return airEnumUnknown(enm);
}

/*
******** airEnumFmtDesc()
**
** Formats a description line for one element "val" of airEnum "enm",
** and puts the result in a NEWLY ALLOCATED string which is the return
** of this function.  The formatting is done via sprintf(), as governed
** by "fmt", which should contain to "%s" conversion sequences, the
** first for the string version "val", and the second for the
** description If "canon", then the canonical string representation
** will be used (the one in enm->str[]), otherwise the shortest string
** representation will be used (which differs from the canonical one
** when there is a strEqv[]/valEqv[] pair defining a shorter string)
*/
char *
airEnumFmtDesc(const airEnum *enm, int val, int canon, const char *fmt) {
  const char *desc;
  char *buff, ident[AIR_STRLEN_SMALL];
  const char *_ident;
  int i;
  size_t len;

  if (!(enm && enm->desc && fmt)) {
    return airStrdup("(airEnumDesc: invalid args)");
  }
  if (airEnumValCheck(enm, val)) {
    val = airEnumUnknown(enm);
  }
  _ident = airEnumStr(enm, val);
  if (!canon && enm->strEqv) {
    len = airStrlen(_ident);
    for (i=0; airStrlen(enm->strEqv[i]); i++) {
      if (val != enm->valEqv[i]) {
        /* this isn't a string representing the value we care about */
        continue;
      }
      if (airStrlen(enm->strEqv[i]) < len) {
        /* this one is shorter */
        len = airStrlen(enm->strEqv[i]);
        _ident = enm->strEqv[i];
      }
    }
  }
  airStrcpy(ident, AIR_STRLEN_SMALL, _ident);
  if (!enm->sense) {
    airToLower(ident);
  }
  desc = enm->desc[_airEnumIndex(enm, val)];
  buff = AIR_CALLOC(airStrlen(fmt) + airStrlen(ident) +
                    airStrlen(desc) + 1, char);
  if (buff) {
    sprintf(buff, fmt, ident, desc);
  }
  return buff;
}

static void
_enumPrintVal(FILE *file, const airEnum *enm, int ii) {

  if (enm->desc) {
    fprintf(file, "desc: %s\n", enm->desc[ii]);
  }
  if (enm->strEqv) {
    unsigned int jj;
    fprintf(file, "eqv:"); fflush(file);
    jj = 0;
    while (airStrlen(enm->strEqv[jj])) {
      if (enm->valEqv[jj] == (enm->val
                              ? enm->val[ii]
                              : ii)) {
        fprintf(file, " \"%s\"", enm->strEqv[jj]);
      }
      jj++;
    }
    fprintf(file, "\n");
  }
}

void
airEnumPrint(FILE *file, const airEnum *enm) {
  int ii; /* this should arguable be unsigned int, but
             airEnum values were kept as "int", even after
             the great unsigned conversion */

  if (!(file && enm)) {
    return;
  }

  if (airStrlen(enm->name)) {
    fprintf(file, "airEnum \"%s\":\n", enm->name);
  } else {
    fprintf(file, "airEnum (NO NAME!):\n");
  }
  fprintf(file, "(%s case sensitive)\n", (enm->sense ? "yes, is" : "is not"));
  if (enm->val) {
    fprintf(file, "Values (%u valid) given explicitly\n", enm->M);
    fprintf(file, "--- (0) %d: \"%s\"\n", enm->val[0], enm->str[0]);
    for (ii=1; ii<=AIR_CAST(int, enm->M); ii++) {
      fprintf(file, "--- (%d) %d: \"%s\" == \"%s\"\n", ii,
              enm->val[ii], enm->str[ii],
              airEnumStr(enm, enm->val[ii]));
      _enumPrintVal(file, enm, ii);
    }
  } else {
    /* enm->val NULL */
    fprintf(file, "Values implicit; [1,%u] valid\n", enm->M);
    fprintf(file, "--- 0: \"%s\"\n", enm->str[0]);
    for (ii=1; ii<=AIR_CAST(int, enm->M); ii++) {
      fprintf(file, "--- %d: %s == %s\n", ii, enm->str[ii],
              airEnumStr(enm, ii));
      _enumPrintVal(file, enm, ii);
    }
  }
  return;
}

/* ---- BEGIN non-NrrdIO */
/*
******** airEnumCheck
**
** tries various things to check on the construction and internal
** consistency of an airEnum; returns 1 if there is a problem, and 0
** if all is well.  we're in air, so there's no biff, but we sprintf a
** description of the error into "err", if given
**
** The requirement that the strings have strlen <= AIR_STRLEN_SMALL-1
** is a reflection of the cheap implementation of the airEnum
** functions in this file, rather than an actual restriction on what an
** airEnum could be.
*/
int
airEnumCheck(char err[AIR_STRLEN_LARGE], const airEnum *enm) {
  static const char me[]="airEnumCheck";
  unsigned int ii, jj;
  size_t slen, ASL;

  ASL = AIR_STRLEN_LARGE;
  if (!enm) {
    if (err) {
      snprintf(err, ASL, "%s: got NULL enm", me);
    }
    return 1;
  }
  if (!enm->name) {
    if (err) {
      snprintf(err, ASL, "%s: enm->name NULL", me);
    }
    return 1;
  }
  if (0 == enm->M) {
    if (err) {
      snprintf(err, ASL, "%s(%s): enm->M zero; no values in enum",
               me, enm->name);
    }
    return 1;
  }
  for (ii=0; ii<=enm->M; ii++) {
    if (!enm->str[ii]) {
      if (err) {
        snprintf(err, ASL, "%s(%s): enm->str[%u] NULL", me, enm->name, ii);
      }
      return 1;
    }
    slen = airStrlen(enm->str[ii]);
    if (!( slen >= 1 && slen <= AIR_STRLEN_SMALL-1 )) {
      if (err) {
        char stmp[AIR_STRLEN_SMALL];
        snprintf(err, ASL, "%s(%s): strlen(enm->str[%u] \"%s\") "
                 "%s not in range [1,%u]", me,
                 enm->name, ii, enm->str[ii],
                 airSprintSize_t(stmp, slen), AIR_STRLEN_SMALL-1);
      }
      return 1;
    }
    /* make sure there are no duplicates among the strings,
       including remapping the case in case of case insensitivity */
    for (jj=ii+1; jj<=enm->M; jj++) {
      if (!strcmp(enm->str[ii], enm->str[jj])) {
        if (err) {
          snprintf(err, ASL, "%s(%s): str[%d] and [%u] both \"%s\"",
                   me, enm->name, ii, jj, enm->str[jj]);
        }
        return 1;
      }
      if (!enm->sense) {
        char bb1[AIR_STRLEN_SMALL], bb2[AIR_STRLEN_SMALL];
        strcpy(bb1, enm->str[ii]);
        airToLower(bb1);
        strcpy(bb2, enm->str[jj]);
        airToLower(bb2);
        if (!strcmp(bb1, bb2)) {
          if (err) {
            snprintf(err, ASL, "%s(%s): after case-lowering, "
                     "str[%d] and [%u] both \"%s\"",
                     me, enm->name, ii, jj, bb1);
          }
          return 1;
        }
      }
    }
  }
  if (enm->val) {
    for (ii=1; ii<=enm->M; ii++) {
      if (enm->val[0] == enm->val[ii]) {
        if (err) {
          snprintf(err, ASL, "%s(%s): val[%u] %u same as "
                   "unknown/invalid val[0] %u",
                   me, enm->name, ii, enm->val[ii], enm->val[0]);
        }
        return 1;
      }
      for (jj=ii+1; jj<=enm->M; jj++) {
        if (enm->val[jj] == enm->val[ii]) {
          if (err) {
            snprintf(err, ASL, "%s(%s): val[%u] %u same as val[%u] %u", me,
                     enm->name, ii, enm->val[ii], jj, enm->val[jj]);
          }
          return 1;
        }
      }
    }
  }
  if (enm->desc) {
    for (ii=0; ii<=enm->M; ii++) {
      if (!enm->desc[ii]) {
        if (err) {
          snprintf(err, ASL, "%s(%s): enm->desc[%u] NULL", me, enm->name, ii);
        }
        return 1;
      }
      /* we don't really care about slen, but learning it will
         hopefully produce some memory error if the array is not valid */
      slen = airStrlen(enm->desc[ii]);
      if (!( slen > 0 )) {
        if (err) {
          snprintf(err, ASL, "%s(%s): enm->desc[%u] empty", me, enm->name, ii);
        }
        return 1;
      }
    }
  }
  if (enm->strEqv) {
    /* the strEqv array is one of the easiest ways to mess up an
       airEnum definition; it deserves these tests and maybe more */
    if (!enm->valEqv) {
      if (err) {
        snprintf(err, ASL, "%s(%s): non-NULL strEqv but NULL valEqv",
                 me, enm->name);
      }
      return 1;
    }
    if (!( airStrlen(enm->strEqv[0]) )) {
      if (err) {
        snprintf(err, ASL, "%s(%s): strEqv[0] is NULL or empty",
                 me, enm->name);
      }
      return 1;
    }
    /* check length and see if any string maps to an invalid value */
    for (ii=0; (slen = strlen(enm->strEqv[ii])); ii++) {
      if (!( slen <= AIR_STRLEN_SMALL-1 )) {
        if (err) {
          char stmp[AIR_STRLEN_SMALL];
          snprintf(err, ASL, "%s(%s): strlen(enm->strEqv[%u] \"%s\") "
                   "%s not <= %u", me, enm->name, ii, enm->strEqv[ii],
                   airSprintSize_t(stmp, slen), AIR_STRLEN_SMALL-1);
        }
        return 1;
      }
      /* see if a string maps to an invalid value */
      if (airEnumValCheck(enm, enm->valEqv[ii])) {
        if (err) {
          snprintf(err, ASL, "%s(%s): valEqv[%u] %u (with strEqv[%u] \"%s\")"
                   " not valid",
                   me, enm->name, ii, enm->valEqv[ii], ii, enm->strEqv[ii]);
        }
        return 1;
      }
    }
    /* make sure eqv strings contain the canonical string */
    for (ii=1; ii<=enm->M; ii++) {
      int eval, rval;
      eval = (enm->val ? enm->val[ii] : AIR_CAST(int, ii));
      rval = airEnumVal(enm, enm->str[ii]);
      if (eval != rval) {
        if (err) {
          snprintf(err, ASL, "%s(%s): ii=%u->eval=%d->str=\"%s\"->%d != %d "
                   "(i.e. canonical string didn't map to its own value)",
                   me, enm->name, ii, eval, enm->str[ii], rval, eval);
        }
        return 1;
      }
    }
    /* make sure there are no duplicates among the strEqv,
       including remapping the case in case of case insensitivity */
    for (ii=0; strlen(enm->strEqv[ii]); ii++) {
      for (jj=ii+1; strlen(enm->strEqv[jj]); jj++) {
        if (!strcmp(enm->strEqv[ii], enm->strEqv[jj])) {
          if (err) {
            snprintf(err, ASL, "%s(%s): strEqv[%d] and [%u] both \"%s\"",
                     me, enm->name, ii, jj, enm->strEqv[jj]);
          }
          return 1;
        }
        if (!enm->sense) {
          char bb1[AIR_STRLEN_SMALL], bb2[AIR_STRLEN_SMALL];
          strcpy(bb1, enm->strEqv[ii]);
          airToLower(bb1);
          strcpy(bb2, enm->strEqv[jj]);
          airToLower(bb2);
          if (!strcmp(bb1, bb2)) {
            if (err) {
              snprintf(err, ASL, "%s(%s): after case-lowering, "
                       "strEqv[%d] and [%u] both \"%s\"",
                       me, enm->name, ii, jj, bb1);
            }
            return 1;
          }
        }
      }
    }
  }
  return 0;
}
/* ---- END non-NrrdIO */
/* this is the end */
