/*****
 * runstring.in
 *
 * Runtime functions for string operations.
 *
 *****/

stringarray2* => stringArray2()
Intarray*  => IntArray()

#include <cfloat>
#include <cstdint>
#include <cstring>
#include <iomanip>
#include <ctime>
#include <chrono>
#include <algorithm>

#include "array.h"
#include "hashing.h"
#include "random.h"

using namespace camp;
using namespace vm;
using namespace settings;

typedef array stringarray;
typedef array stringarray2;
typedef array Intarray;

using types::stringArray;
using types::stringArray2;
using types::IntArray;

namespace types {
extern const char *names[];
}

namespace run {
extern string emptystring;
}

static const string defaulttimeformat=string("%a %b %d %T %Z %Y");
#ifdef HAVE_STRFTIME
static const size_t nTime=256;
static char Time[nTime];
#endif

void checkformat(const char *ptr, bool intformat)
{
  while(*ptr != '\0') {
    if(*ptr != '%') /* While we have regular characters, print them.  */
      ptr++;
    else { /* We've got a format specifier. */
      ptr++;

      while(*ptr && strchr ("-+ #0'I", *ptr)) /* Move past flags.  */
        ptr++;

      if(*ptr == '*')
        ptr++;
      else while(isdigit(*ptr)) /* Handle explicit numeric value.  */
             ptr++;

      if(*ptr == '.') {
        ptr++; /* Go past the period.  */
        if(*ptr == '*') {
          ptr++;
        } else
          while(isdigit(*ptr)) /* Handle explicit numeric value.  */
            ptr++;
      }
      while(*ptr && strchr ("hlL", *ptr))
        ptr++;

      if(*ptr == '%') {++ptr; continue;}
      else if(*ptr != '\0') {
        if(intformat) {
          switch(*ptr) {
            case 'd':
            case 'i':
            case 'o':
            case 'u':
            case 'x':
            case 'X':
            case 'c':
              break;
            default:
              ostringstream buf;
              buf << "Invalid format '" << *ptr << "' for type "
                  << types::names[types::ty_Int];
              error(buf);
              break;
          }
        } else {
          switch(*ptr) {
            case 'f':
            case 'F':
            case 'e':
            case 'E':
            case 'g':
            case 'G':
              break;
            default:
              ostringstream buf;
              buf << "Invalid format '" << *ptr << "' for type "
                  << types::names[types::ty_real];
              error(buf);
              break;
          }
        }
      }
      break; // Only one argument is allowed.
    } /* End of else statement */
  }
}
  
// Autogenerated routines:


// String operations

string :emptyString()
{
  return emptystring;
}

Int length(string *s)
{
  return (Int) s->length();
}

Int :stringHashHelper(string s)
{
  span<const char> sSpan = {s.data(), s.length()};
  return hashing::hashSpan(sSpan);
}

callable* :stringHash(string s)
{
  return new thunk(new bfunc(stringHashHelper), s);
}

Int find(string *s, string t, Int pos=0)
{
  size_t n=s->find(t,pos);
  return n == string::npos ? (Int) -1 : (Int) n;
}

Int rfind(string *s, string t, Int pos=-1)
{
  size_t n=s->rfind(t,pos);
  return n == string::npos ? (Int) -1 : (Int) n;
}

string reverse(string s)
{
  reverse(s.begin(),s.end());
  return s;
}

string insert(string s, Int pos, string t)
{
  if ((size_t) pos < s.length())
    return s.insert(pos,t);
  return s;
}

string substr(string* s, Int pos, Int n=-1)
{
  if ((size_t) pos < s->length())
    return s->substr(pos,n);
  return emptystring;
}

string erase(string s, Int pos, Int n)
{
  if ((size_t) pos < s.length())
    return s.erase(pos,n);
  return s;
}

string downcase(string s)
{
  std::transform(s.begin(),s.end(),s.begin(),tolower);
  return s;
}

string upcase(string s)
{
  std::transform(s.begin(),s.end(),s.begin(),toupper);
  return s;
}

// returns a string constructed by translating all occurrences of the string
// from in an array of string pairs {from,to} to the string to in string s.
string replace(string *S, stringarray2 *translate)
{
  size_t size=checkArray(translate);
  for(size_t i=0; i < size; i++) {
    array *a=read<array*>(translate,i);
    checkArray(a);
  }
  size_t pos=0;
  ostringstream buf;
  size_t Len=S->length();

  while(pos < Len) {
    for(size_t i=0; i < size;) {
      array *a=read<array*>(translate,i);
      size_t size2=checkArray(a);
      if(size2 != 2)
        error("translation table entry must be an array of length 2");
      string* from=read<string*>(a,0);
      size_t len=from->length();
      if(len == 0 || S->compare(pos,len,*from,0,len) != 0) {i++; continue;}
      buf << read<string>(a,1);
      pos += len;
      if(pos == Len) return buf.str();
      i=0;
    }
    buf << S->substr(pos,1);
    ++pos;
  }
  return buf.str();
}

string format(string *format, Int x, string locale=emptystring)
{
  ostringstream out;
  const char *p0=format->c_str();
  checkformat(p0,true);

  const char *p=p0;
  const char *start=NULL;
  while(*p != 0) {
    char curr=*p;
    if(curr == '%') {
      p++;
      if(*p != '%') {start=p-1; break;}
    }
    out << *(p++);
  }

  if(!start) return out.str();

  // Allow at most 1 argument
  while(*p != 0) {
    if(*p == '*' || *p == '$') return out.str();
    if(isupper(*p) || islower(*p)) {p++; break;}
    p++;
  }

  string f=format->substr(start-p0,p-start);
  f.insert(p-start-1,"ll");

  const char *oldlocale=NULL;

  if(!locale.empty()) {
    oldlocale=setlocale(LC_ALL,NULL);
    if(oldlocale) oldlocale=StrdupNoGC(oldlocale);
    setlocale(LC_ALL,locale.c_str());
  }

  Int size=snprintf(NULL,0,f.c_str(),x)+1;
  if(size < 1) size=255; // Workaround for non-C99 compliant systems.
  char *buf=new char[size];
  snprintf(buf,size,f.c_str(),x);

  out << string(buf);
  out << p;

  delete[] buf;

  if(oldlocale) {
    setlocale(LC_ALL,oldlocale);
    delete[] oldlocale;
  }

  return out.str();
}

string format(string *format, bool forcemath=false, string separator, real x,
              string locale=emptystring)
{
  if(*format == "%") return ""; // Temporary workaround for github Issue #29.

  bool tex=getSetting<string>("tex") != "none";
  bool texify=forcemath;
  ostringstream out;

  const char *p0=format->c_str();
  checkformat(p0,false);

  const char *phantom="\\phantom{+}";

  const char *p=p0;
  const char *start=NULL;
  char prev=0;
  while(*p != 0) {
    char curr=*p;
    if(tex && curr == '$' && prev != '\\') texify=true;
    prev=curr;
    if(curr == '%') {
      p++;
      if(*p != '%') {start=p-1; break;}
    }
    out << *(p++);
  }

  if(!start) return out.str();

  // Allow at most 1 argument
  while(*p != 0) {
    if(*p == '*' || *p == '$') return out.str();
    if(isupper(*p) || islower(*p)) {p++; break;}
    p++;
  }

  const char *tail=p;
  string f=format->substr(start-p0,tail-start);

  const char *oldlocale=NULL;
  if(!locale.empty()) {
    oldlocale=setlocale(LC_ALL,NULL);
    if(oldlocale) oldlocale=StrdupNoGC(oldlocale);
    setlocale(LC_ALL,locale.c_str());
  }

  Int size=snprintf(NULL,0,f.c_str(),x)+1;
  if(size < 1) size=255; // Workaround for non-C99 compliant systems.
  char *buf=new char[size];
  snprintf(buf,size,f.c_str(),x);

  bool trailingzero=f.find("#") < string::npos;
  bool plus=f.find("+") < string::npos;
  bool space=f.find(" ") < string::npos;

  char *q=buf; // beginning of formatted number

  if(*q == ' ' && texify) {
    out << phantom;
    q++;
  }

  const char decimal=*(localeconv()->decimal_point);

  // Remove any spurious sign
  if(*q == '-' || *q == '+') {
    p=q+1;
    bool zero=true;
    while(*p != 0) {
      if(!isdigit(*p) && *p != decimal) break;
      if(isdigit(*p) && *p != '0') {zero=false; break;}
      p++;
    }
    if(zero) {
      q++;
      if((plus || space) && texify) out << phantom;
    }
  }

  const char *r=p=q;
  bool dp=false;
  while(*r != 0 && (isspace(*r) || isdigit(*r) || *r == decimal \
                    || *r == '+' || *r == '-')) {
    if(*r == decimal) dp=true;
    r++;
  }
  if(dp) { // Remove trailing zeros and/or decimal point
    r--;
    unsigned n=0;
    while(r > q && *r == '0') {r--; n++;}
    if(*r == decimal) {r--; n++;}
    while(q <= r) out << *(q++);
    if(!trailingzero) q += n;
  }

  bool zero=(r == p && *r == '0') && !trailingzero;

  // Translate "E+/E-/e+/e-" exponential notation to TeX
  while(*q != 0) {
    if(texify && (*q == 'E' || *q == 'e') &&
       (*(q+1) == '+' || *(q+1) == '-')) {
      if(!zero) out << separator << "10^{";
      bool plus=(*(q+1) == '+');
      q++;
      if(plus) q++;
      if(*q == '-') out << *(q++);
      while(*q == '0' && (zero || isdigit(*(q+1)))) q++;
      while(isdigit(*q)) out << *(q++);
      if(!zero)
        out << "}";
      break;
    }
    out << *(q++);
  }

  while(*tail != 0)
    out << *(tail++);

  delete[] buf;

  if(oldlocale) {
    setlocale(LC_ALL,oldlocale);
    delete[] oldlocale;
  }

  return out.str();
}

Int hex(string s)
{
  istringstream is(s);
  is.setf(std::ios::hex,std::ios::basefield);
  Int value;
  if(is && is >> value && ((is >> std::ws).eof())) return value;
  ostringstream buf;
  buf << "invalid hexadecimal cast from string \"" << s << "\"";
  error(buf);
}

Int ascii(string s)
{
  return s.empty() ? -1 : (unsigned char) s[0];
}

Int hash(string s)
{
  span<const char> sSpan = {s.data(), s.length()};
  return hashing::hashSpan(sSpan);
}

string string(Int x)
{
  ostringstream buf;
  buf << x;
  return buf.str();
}

string string(real x, Int digits=DBL_DIG)
{
  ostringstream buf;
  buf.precision(digits);
  buf << x;
  return buf.str();
}

string time(string format=defaulttimeformat)
{
#ifdef HAVE_STRFTIME
  const time_t bintime=time(NULL);
  if(!strftime(Time,nTime,format.c_str(),localtime(&bintime))) return "";
  return Time;
#else
  return format;
#endif
}

string time(Int seconds, string format=defaulttimeformat)
{
#ifdef HAVE_STRFTIME
  const time_t bintime=seconds;
  if(!strftime(Time,nTime,format.c_str(),localtime(&bintime))) return "";
  return Time;
#else
// Avoid unused variable warning messages
  unused(&seconds);
  return format;
#endif
}

Int seconds(string t=emptystring, string format=emptystring)
{
  if (t == "")
  {
    auto clock = std::chrono::system_clock::now();
    return static_cast<Int>(
            std::chrono::duration_cast<std::chrono::seconds>(
                    clock.time_since_epoch()
                    ).count()
            );
  }

  std::tm tmObj = {};
  istringstream instream(t);
  instream.imbue(std::locale(""));
  instream >> std::get_time(&tmObj,format.c_str());

  if(instream.fail())
  {
    return -1;
  }

  return static_cast<Int>(std::mktime(&tmObj));
}

string randString(Int len, string *chars) {
  string result(len, ' ');
  const string *charsConst = chars;
  static const string *alphabet = new string(
      "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");

  if (charsConst == nullptr || charsConst->size() == 0) {
    charsConst = alphabet;
  }
  Int maxIndex = charsConst->size() - 1;
  for (Int i = 0; i < len; ++i) {
    char toAdd = (*charsConst)[camp::randInt(0, maxIndex)];
    result[i] = toAdd;
  }
  return result;
}
