/*

Description:
  C routines and functions which are meant to either be called
  from FORTRAN or are used to help C routines interface to FORTRAN.

Author:
  DTJ	 5/ 1/92

  Modifications:
    10-Nov-2005 CJB DR 504967 - Modified strf2c for keywords C++ conversion
    26-Jun-2020 JTM NXM-3531 - Added to NextMidas baseline

*/

/* Include files which are somewhat global */

#include <cstdio>
#include <cstring>
#include <cctype>		/* toupper, tolower */
#include <algorithm>            /* min() */

#include "midaslib.h"
#include "longstring.hh"
#include "fcinterface.h"

using namespace std;
using namespace midas;


/****************************** CODE *****************************/

// ///// fstring_buf Methods

std::string fstring_buf::str () const
{
  if (buf) {
    if (convFlags_ & F2C_FLAG_LONGSTRING) {
      return longstring_::str(buf, nchar);
    } else {
      return strf2cpp(buf, nchar);
    }
  } else {
    return std::string();
  }
}

std::string::size_type
fstring_buf::adjust_bufsize(int bufsize)
{
    if (bufsize < 0)
    {
        m_warning(std::string("fstring_buf created with negative string"
			      "length - clipping to zero"));
	bufsize = 0;
    }
    return bufsize;
}

// ///// Global Functions

/* strf2c

Description:
  Converts FORTRAN strings to C strings.  When a FORTRAN program calls
  a C function with a string argument, the function gets a char pointer
  to an array that is NOT null-terminated, and the allocated bytes
  for the string.

  Therefore this routine does the following:
    1) copies as many bytes as possible from the FORTRAN to the C string;
    2) makes sure the C string terminates with a null;

  If the type of conversion is F2C_UNIXFILE or F2C_TRIM, it also:
    3) effectively trims off any trailing white space that may have come
       in with the FORTRAN string.

  If the type of conversion is F2C_UNIXFILE, it also:
    4) converts every character in the C string to lowercase.

  If the type of conversion is F2C_SUBROUTINE, it also:
    5) appends an underscore.

Author:  DTJ 2/26/92

Arguments:
  fchars (IN)		-- pointer to the FORTRAN character array.
  flen (IN)		-- allocated bytes for fchars
  cstr (OUT)		-- pointer to C string
  cmax (IN)		-- allocated bytes for cstr
  convtype (IN)		-- type of conversion

Returns:
  a pointer to the C string.

*/

extern "C"
char *strf2c(const char *fchars, int flen, char *cstr, int cmax, int convtype)

{
  int copied = 0;

  if (cmax <= 0)
    return cstr;

  // Depending on the conversion type what appears to be a Fortran
  // string may actually contain an encoded longstring.
  // Save one character for the null, for the C string.
  if (convtype & F2C_FLAG_LONGSTRING) {
    string s = longstring_::str(fchars, flen);
    copied = min(string::size_type(cmax - 1), s.size());
    (void)memcpy(cstr, s.c_str(), copied);
    cstr[copied] = '\0';
  } else {
    copied = min(cmax - 1, flen);
    (void)memmove(cstr, fchars, copied);
    cstr[copied] = '\0';
  }

  if (convtype & F2C_FLAG_TRIM) {
    while (copied >= 0  &&  cstr[copied] <= ' ') {
      cstr[copied--] = '\0';
    }
    copied++; // Point to the null
  }

  if (convtype & F2C_FLAG_LOWERCASE) {
    for (int i = 0; cstr[i] != '\0'; i++) {
      cstr[i] = tolower(cstr[i]);
    }
  }

  if (convtype & F2C_FLAG_APPEND_) {
    // Ensure there is room for the extra character required.
    // At this point we know that cstr is correctly null-terminated,
    // and that 'copied' points to that null.
    // What we need to check is whether or not there is at least one
    // character beyond the null into which the requested underscore
    // can be appended.
    if (copied < (cmax - 1)) {
      cstr[copied++] = '_';
      cstr[copied] = '\0';
    } else {
      ostringstream out;
      out << "Not enough room to append an underscore to the string "
	  << "\"" << cstr << "\" in response to the F2C_FLAG_APPEND_ "
	  << "flag to strf2c";
      throw logic_error(out.str());
    }
  }

  return cstr;

} // strf2c




/* strc2f

Description:
  Converts C strings to FORTRAN strings.  When a C program needs to fill
  in the contents of a string that will be used in FORTRAN programs the
  null-terminator must be removed.

  Therefore this routine does the following:
    1) copies as many bytes as possible from the C to the FORTRAN string;
    2) makes sure the FORTRAN string does not terminate with a null;

  If the type of conversion is C2F_FLAG_FILL:
    3) appends trailing spaces to the FORTRAN string if necessary.

  If the type of conversion is C2F_FLAG_UPPERCASE, it also:
    4) converts all characters copied from the C string to uppercase.

Author:  SES 2/26/92

Arguments:
  cstr (IN)		-- pointer to C string
  cmax (IN)		-- number of chars to copy, if <0 until '/0' encountered
  fchars (OUT)		-- pointer to the FORTRAN character array.
  flen (IN)		-- allocated bytes for fchars
  convtype (IN)		-- type of conversion

Returns:
  a pointer to the Fortran character array.

*/

extern "C"
char *strc2f (const char *cstr, int cmax, char *fchars, int flen, int convtype)

{
  int             i, copied;

  // Depending on the conversion type we may be required to treat
  // this the target as possibly encoding a longstring.
  if (convtype & C2F_FLAG_LONGSTRING) {
    if (cmax < 0)
      longstring_::assign(fchars, flen, string(cstr));
    else
      longstring_::assign(fchars, flen, string(cstr, cmax));
    return fchars;
  }

  /* DR 504967. X-Midas frequently passes a Fortran string constant
     into a routine that technically takes an I/O argument, secure
     in the knowledge that it won't get clobbered because of some
     other 'mode' parameter (c.f. M$GET_KEYDATA etc.)

     In these cases, we must not attempt to write to the memory,
     particularly if the contents are already the same as what
     we would have written.  This case is represented by the flag
     C2F_FLAG_FCONST being present.  (Its presence does not
     mean that ``fchars`` points to a constant, only that it *might*.)
     The downside to making this check is that, although it
     ensures correctness, it makes valgrind flag a read of
     uninitialized memory.

     There are other cases, however, in which the caller knows that
     the output cannot be a constant, and in these cases, when
     C2F_FLAG_FCONST is not set, this routine can freely write to
     ``fchars`` without checking, which is faster and simpler.
  */

  if (cmax >= 0) {
    if (!(convtype & C2F_FLAG_FCONST) || cmax != flen) {
      for (i = 0; i < cmax && i < flen; i++)
	fchars[i] = cstr[i];
    } else {
      for (i = 0; i < cmax && i < flen; i++)
	if (fchars[i] != cstr[i])
	  fchars[i] = cstr[i];
    }
  } else {
    if (!(convtype & C2F_FLAG_FCONST)) {
      for (i = 0; i < flen  &&  cstr[i] != '\0'; i++)
	fchars[i] = cstr[i];
    } else {
      for (i = 0; i < flen  &&  cstr[i] != '\0'; i++)
	if (fchars[i] != cstr[i])
	  fchars[i] = cstr[i];
    }
  }

  copied = i;
  
  if (convtype & C2F_FLAG_FILL) {
    if (convtype & C2F_FLAG_FCONST) {
      while (i < flen) {
	if (fchars[i] != ' ') fchars[i] = ' ';
	i++;
      }
    } else {
      memset(&fchars[i], ' ', flen-i);
    }
  }
  
  if (convtype & C2F_FLAG_UPPERCASE)
    for (i = 0; i < copied; i++)
      fchars[i] = toupper(fchars[i]);

  return (fchars);
}


/*******************************************************************************
Description: adds a C string null-terminator character ('\0') at the specified
             position. This is a quick and efficient way to convert a Fortran
             string to C string when the output length is known. This WILL
             replace the last character with the null char when (pos == maxlen).

Author: Neon Ngo  2010-11-11 (original version on Veterans Day)

Arguments:
  str    (I/O) -- pointer to Fortan/C string (char*) to add null-terminator
  maxlen (IN)  -- max length of str (if <= 0 then cstr is NOT changed)
  pos    (IN)  -- position in str to insert null-terminator character (when >= 0),
                  if this is greater than maxlen
  flags  (IN)  -- flags to modify default behavior:
                  FLAG_NONE (0x0)      - default behavior
                  FLAG_IGNORE_ZERO_POS - no change to str when (pos == 0).

Returns:
  a pointer to the str (char *)
*******************************************************************************/
char * addnullterminator (char* str, int maxlen, int pos, int flags)
{
  if (maxlen <= 0) {       // negative len or empty str
    return str;            // return str unchanged
  }

  // flag to ignore zero pos (to support APIs like m_get_uswitch())
  if ((pos == 0) && (flags & FLAG_IGNORE_ZERO_POS)) {
    return str;            // return str unchanged
  }

  if ((pos >= 0) && str) { // make sure pos (positive) and str (not null) are valid
    if (pos >= maxlen) {
      pos = (maxlen - 1);  // truncate last character (no space left) in str
                           // TODO: print warning message?
    }
    str[pos] = '\0';       // insert null terminator
  }

  return str;
} // end addnullterminator()
