// Copyright(C) 1999-2021 National Technology & Engineering Solutions
// of Sandia, LLC (NTESS).  Under the terms of Contract DE-NA0003525 with
// NTESS, the U.S. Government retains certain rights in this software.
//
// See packages/seacas/LICENSE for details

/***
   NAME
     init
   PURPOSE
     Initialize variables and functions Aprepro
***/
#include "apr_builtin.h"
#include "apr_symrec.h"
#include "apr_tokenize.h"
#include "aprepro.h"      // for symrec, Aprepro, etc
#include "init_structs.h" // for array_a_init, array_c_init, etc
#include <string>         // for string

namespace SEAMS {
  init arith_0_fncts[] = {
      {"seconds", do_time, "seconds()", "Seconds since epoch (useful for srand())."},
      {nullptr, nullptr, nullptr, nullptr}};

  init_d arith_fncts[] = {
      {"abs", do_fabs, "abs(x)", "Absolute value of x. |x|."},
      {"acos", do_acos, "acos(x)", "Inverse cosine of x, returns radians."},
      {"acosd", do_acosd, "acosd(x)", "Inverse cosine of x, returns degrees."},
      {"acosh", do_acosh, "acosh(x)", "Inverse hyperbolic cosine of x."},
      {"asin", do_asin, "asin(x)", "Inverse sine of x, returns radians."},
      {"asind", do_asind, "asin(x)", "Inverse sine of x, returns degrees."},
      {"asinh", do_asinh, "asinh(x)", "Inverse hyperbolic sine of x."},
      {"atan", do_atan, "atan(x)", "Inverse tangent of x, returns radians."},
      {"atand", do_atand, "atand(x)", "Inverse tangent of x, returns degrees."},
      {"atanh", do_atanh, "atanh(x)", "Inverse hyperbolic tangent of x."},
      {"cbrt", do_cbrt, "cbrt(x)", "Cube root of x. "},
      {"ceil", do_ceil, "ceil(x)", "Smallest integer not less than x."},
      {"cos", do_cos, "cos(x)", "Cosine of x, with x in radians"},
      {"cosd", do_cosd, "cosd(x)", "Cosine of x, with x in degrees"},
      {"cosh", do_cosh, "cosh(x)", "Hyperbolic cosine of x."},
      {"d2r", do_d2r, "d2r(x)", "Degrees to radians."},
      {"erf", do_erf, "erf(x)", "Error Function of x"},
      {"erf", do_erfc, "erfc(x)", "Complementary Error Function of x"},
      {"exp", do_exp, "exp(x)", "Exponential: e^x"},
      {"expm1", do_expm1, "expm1(x)", "Exponential: Accurate version of e^x - 1.0 for small x"},
      {"floor", do_floor, "floor(x)", "Largest integer not greater than x."},
      {"int", do_int, "int(x), [x]", "Integer part of x truncated toward 0."},
      {"lgamma", do_lgamma, "lgamma(x)", "log(Gamma(x))."},
      {"tgamma", do_tgamma, "tgamma(x)", "Gamma(x)."},
      {"ln", do_log, "ln(x)", "Natural (base e) logarithm of x."},
      {"log", do_log, "log(x)", "Natural (base e) logarithm of x."},
      {"log10", do_log10, "log10(x)", "Base 10 logarithm of x. "},
      {"log1p", do_log1p, "log1p(x)", "log(1+x) "},
      {"nint", do_nint, "nint(x)", "Rounds x to nearest integer. <0.5 down; >=0.5 up."},
      {"r2d", do_r2d, "r2d(x)", "Radians to degrees. "},
      {"sin", do_sin, "sin(x)", "Sine of x, with x in radians. "},
      {"sind", do_sind, "sind(x)", "Sine of x, with x in degrees. "},
      {"sinh", do_sinh, "sinh(x)", "Hyperbolic sine of x "},
      {"srand", do_srand, "srand(seed)",
       "Seed the random number generator with the given integer value. "},
      {"sqrt", do_sqrt, "sqrt(x)", "Square root of x. "},
      {"tan", do_tan, "tan(x)", "Tangent of x, with x in radians. "},
      {"tand", do_tand, "tand(x)", "Tangent of x, with x in radians. "},
      {"tanh", do_tanh, "tanh(x)", "Hyperbolic tangent of x. "},
      {nullptr, nullptr, nullptr, nullptr}};

  init_a arith_a_fncts[] = {
      {"rows", do_rows, "rows(array)", "Returns the number of rows in the array. "},
      {"cols", do_cols, "cols(array)", "Returns the number of columns in the array. "},
      {nullptr, nullptr, nullptr, nullptr}};

  init_dd arith_dd_fncts[] = {
      {"atan2", do_atan2, "atan2(y,x)", "Inverse tangent of y/x, returns radians."},
      {"atan2d", do_atan2d, "atan2d(y,x)", "Inverse tangent of y/x, returns degrees."},
      {"dim", do_dim, "dim(x,y)", "x - min(x,y)"},
      {"fmod", do_fmod, "fmod(x,y)", "Floating-point remainder of x/y."},
      {"hypot", do_hypot, "hypot(x,y)", "sqrt(x^2+y^2)."},
      {"max", do_max, "max(x,y)", "Maximum of x and y. "},
      {"min", do_min, "min(x,y)", "Minimum of x and y. "},
      {"polarX", do_polarX, "polarX(r,a)", "r * cos(a), a is in degrees "},
      {"polarY", do_polarY, "polarY(r,a)", "r * sin(a), a is in degrees "},
      {"pow", do_pow, "pow(x,y)", "x^y "},
      {"rand", do_rand, "rand(xl,xh)", "Random value between xl and xh; uniformly distributed. "},
      {"rand_normal", do_rand_normal, "rand_normal(m,s)",
       "Random value normally distributed with mean m and stddev s."},
      {"rand_lognormal", do_rand_lognormal, "rand_lognormal(m,s)",
       "Random value with lognormal distribution with mean m and stddev s."},
      {"rand_weibull", do_rand_weibull, "rand_weibull(a, b)",
       "Random value with weibull distribution with alpha=a and beta=b. "},
      {"sign", do_sign, "sign(x,y)", "x * sgn(y)"},
      {nullptr, nullptr, nullptr, nullptr}};

  init_dddd arith_dddd_fncts[] = {
      {"Vangle", do_angle, "Vangle(x1,y1,x2,y2)",
       "Angle (radians) between vector x1_i+y1_j and x2_i+y2_j."},
      {"Vangled", do_angled, "Vangled(x1,y1,x2,y2)",
       "Angle (degrees) between vector x1_i+y1_j and x2_i+y2_j."},
      {"dist", do_dist, "dist(x1,y1, x2,y2)", "sqrt((x1-x2)^2 + (y1-y2)^2)"},
      {nullptr, nullptr, nullptr, nullptr}};

  init_cc arith_cc_fncts[] = {{"word_count", do_word_count, "word_count(svar,del)",
                               "Number of words in svar. Words are separated by one or more of the "
                               "characters\n\t\t\tin the "
                               "string variable 'del'."},
                              {nullptr, nullptr, nullptr, nullptr}};

  init_ccc arith_ccc_fncts[] = {
      {"find_word", do_find_word, "find_word(w,s,d)",
       "Find the 1-based index of word 'w' in variable 's'. Words are separated "
       "by one or more of the\n\t\t\tcharacters in the "
       "string variable 'd'. Returns 0 if not found."},
      {nullptr, nullptr, nullptr, nullptr}};

  init_c arith_c_fncts[] = {{"strtod", do_strtod, "strtod(svar)",
                             "Returns a double-precision floating-point number "
                             "equal to the value represented by the\n\t\t\tcharacter "
                             "string pointed to by svar."},
                            {nullptr, nullptr, nullptr, nullptr}};

  init_cd arith_cd_fncts[] = {{"option", do_option, "option(?,?)", "Internal"},
                              {nullptr, nullptr, nullptr, nullptr}};

  init_ddd arith_ddd_fncts[] = {
      {"julday", do_julday, "julday(mm, dd, yy)", "Julian day corresponding to mm/dd/yy. "},
      {nullptr, nullptr, nullptr, nullptr}};

  init_dddddd arith_dddddd_fncts[] = {{"juldayhms", do_juldayhms, "juldayhms(m,d,y,h,m,s)",
                                       "Julian day corresponding to m/d/y at h:m:s "},
                                      {nullptr, nullptr, nullptr, nullptr}};

  str_init string_fncts[] = {
      {"DUMP", do_dumpsym, "DUMP()",
       "Output a list of all user-defined variables and their value."},
      {"DUMP_JSON", do_dumpsym_json, "DUMP()",
       "Output a list of all user-defined variables and their value in JSON format."},
      {"DUMP_FUNC", do_dumpfunc, "DUMP_FUNC()",
       "Output a list of all double and string functions recognized by aprepro."},
      {"DUMP_PREVAR", do_dumpvar, "DUMP_PREVAR()",
       "Output a list of all predefined variables and their value."},
      {"get_date", do_get_date, "get_date()",
       "Returns a string representing the current date in the form YYYY/MM/DD."},
      {"get_iso_date", do_get_iso_date, "get_iso_date()",
       "Returns a string representing the current date in the form YYYYMMDD."},
      {"get_time", do_get_time, "get_time()",
       "Returns a string representing the current time in the form HH:MM:SS."},
      {"get_temp_filename", do_get_temp_filename, "get_temp_filename()",
       "Returns a string which can be used for a temporary filename without conflicting with any "
       "other filenames."},
      {"version", do_version, "version()",
       "Return the version string (See also _VERSION variable)"},
      {nullptr, nullptr, nullptr, nullptr}};

  str_c_init string_c_fncts[] = {
      {"DUMP", do_dumpsym1, "DUMP(str)",
       "Output a list of all defined variables and their value if name contains 'str'."},
      {"DUMP_FUNC", do_dumpfunc1, "DUMP_FUNC()",
       "Output a list of all double and string functions recognized by aprepro if name contains "
       "'str'."},
      {"DUMP_PREVAR", do_dumpvar1, "DUMP_PREVAR()",
       "Output a list of all predefined variables and their value if name contains 'str'."},
      {"tolower", do_tolower, "tolower(svar)",
       "Translates all uppercase characters in svar to "
       "lowercase. It modifies svar and returns the "
       "resulting string.  "},
      {"toupper", do_toupper, "toupper(svar)",
       "Translates all lowercase character in svar to "
       "uppercase. It modifies svar and returns the "
       "resulting string. "},
      {"to_lower", do_tolower, "to_lower(svar)",
       "Translates all uppercase characters in svar to "
       "lowercase. It modifies svar and returns the "
       "resulting string.  "},
      {"to_upper", do_toupper, "toupper(svar)",
       "Translates all lowercase character in svar to "
       "uppercase. It modifies svar and returns the "
       "resulting string. "},
      {"getenv", do_getenv, "getenv(svar)",
       "Returns a string containing the value of the environment variable svar. If the environment "
       "\n\t\t\tvariable is not defined, an empty string is returned. "},
      {"file_to_string", do_file_to_string, "file_to_string(fn)",
       "Opens the file specified by fn and returns the contents as a multi-line string."},
      {"error", do_error, "error(svar)",
       "Outputs the string svar to stderr and then terminates the code with an error exit status."},
      {"execute", do_execute, "execute(svar)",
       "svar is parsed and executed as if it were a line read from the input file."},
      {"output", do_output, "output(filename)",
       "Creates the file specified by filename and sends "
       "\n\t\t\tall subsequent output from aprepro to that file."},
      {"output_append", do_append, "output_append(fn)",
       "If file with name fn exists, append output to it; otherwise create the file and "
       "send\n\t\t\tall "
       "subsequent output from aprepro to that file."},
      {"rescan", do_rescan, "rescan(svar)",
       "The difference between execute(sv1) and rescan(sv2) "
       "is that sv1 must be a valid expression,\n\t\t\tbut sv2 can "
       "contain zero or more expressions. "},
      {"include_path", do_include_path, "include_path(path)",
       "Specify an optional path to be prepended to a filename when opening a file.\n\t\t\tCan "
       "also be "
       "specified via the -I command line option when executing aprepro."},
      {"Units", do_Units, "Units(svar)",
       "See manual. svar is one of the defined units "
       "systems:\n\t\t\t'si', 'cgs', 'cgs-ev', 'shock', 'swap', "
       "'ft-lbf-s', 'ft-lbm-s', 'in-lbf-s'"},
      {"delete", do_delete, "delete(var_name)", "Delete the variable with name 'var_name'."},
      {"if", do_str_if, "if(x)",
       "Handles the if statements. x can be any valid expression; nonzero is true"},
      {"If", do_str_if, "If(x)",
       "Handles the if statements. x can be any valid expression; nonzero is true"},
      {"elseif", do_str_elseif, "elseif(x)",
       "Handles the if statements. x can be any valid expression; nonzero is true"},
      {"Elseif", do_str_elseif, "Elseif(x)",
       "Handles the if statements. x can be any valid expression; nonzero is true"},
      {"_ifdef", do_str_if, "ifdef(x)",
       "Handles the if statements. x can be any valid expression; "
       "nonzero is true (deprecated, use if)"},
      {"_ifndef", do_str_notif, "ifndef(x)",
       "Handles the if statements. x can be any valid "
       "expression; nonzero is true (deprecated, use if)"},
#if defined(EXODUS_SUPPORT)
      {"exodus_meta", do_exodus_meta, "exodus_meta(ex_fn)",
       "Creates several variables related to the exodus metadata in the specified file. "},
#endif
      {nullptr, nullptr, nullptr, nullptr}};

  str_d_init string_d_fncts[] = {
      {"IO", do_intout, "IO(x)", "Convert x to an integer and then to a string. "},
      {"to_string", do_tostring, "to_string(x)",
       "Returns a string representation of the numerical variable x. The variable x is unchanged."},
      {"tostring", do_tostring, "tostring(x)",
       "Returns a string representation of the numerical variable x. The variable x is unchanged."},
      {"if", do_if, "if(x)",
       "Handles the if statements. x can be any valid expression; nonzero is true"},
      {"If", do_if, "If(x)",
       "Handles the if statements. x can be any valid expression; nonzero is true"},
      {"elseif", do_elseif, "elseif(x)",
       "Handles the if statements. x can be any valid expression; nonzero is true"},
      {"Elseif", do_elseif, "Elseif(x)",
       "Handles the if statements. x can be any valid expression; nonzero is true"},
      {"_ifdef", do_if, "ifdef(x)",
       "Handles the if statements. x can be any valid expression; "
       "nonzero is true.\n\t\t\tEats leading whitespace. (deprecated, use "
       "if)"},
      {"_ifndef", do_notif, "ifndef(x)",
       "Handles the if statements. x can be any valid "
       "expression; nonzero is true.\n\t\t\tEats leading whitespace. "
       "(deprecated, use if)"},
      {"switch", do_switch, "switch(x)",
       "Switch statement. Select from the following case "
       "statements which matches 'x' and execute that one.\n\t\t\tEnd "
       "with endswitch"},
      {"Switch", do_switch, "Switch(x)",
       "Switch statement. Select from the following case "
       "statements which matches 'x' and execute that one.\n\t\t\tEnd "
       "with endswitch"},
      {"case", do_case, "case(x)",
       "Switch statement. A case used in a containing switch statement."},
      {"Case", do_case, "Case(x)",
       "Switch statement. A case used in a containing switch statement."},
      {nullptr, nullptr, nullptr, nullptr}};

  str_dcc_init string_dcc_fncts[] = {
      {"get_word", do_get_word, "get_word(n,svar,del)",
       "Returns a string containing the nth word of svar. The words are separated by one or more "
       "\n\t\t\tcharacters in the string variable del "},
      {nullptr, nullptr, nullptr, nullptr}};

  str_ccc_init string_ccc_fncts[] = {
      {"extract", do_extract, "extract(s, b, e)",
       "Return substring [b,e). 'b' is included; 'e' is not. If 'b' not found, return empty; If "
       "'e' not found,\n\t\t\treturn rest of string. If 'b' empty, start at beginning; if 'e' "
       "empty, "
       "return rest of string."},
#if defined(EXODUS_SUPPORT)
      {"exodus_info", do_exodus_info_range, "exodus_info(ex_fn, beg, end)",
       "Parses the info records starting after 'beg' and ending before 'end'"},
#endif
      {nullptr, nullptr, nullptr, nullptr}};

  str_cc_init string_cc_fncts[] = {
#if defined(EXODUS_SUPPORT)
      {"exodus_info", do_exodus_info, "exodus_info(ex_fn, prefix)",
       "Parses the info records that begin with 'prefix' extracted from the exodus file 'ex_fn'"},
#endif
      {nullptr, nullptr, nullptr, nullptr}};

  str_a_init string_a_fncts[] = {
      {"print_array", do_print_array, "print_array(array)", "Prints the data in the array."},
      {nullptr, nullptr, nullptr, nullptr}};

  array_c_init array_c_fncts[] = {{"csv_array", do_csv_array1, "csv_array(filename)",
                                   "Create a 2D array from the data in a csv file."},
                                  {nullptr, nullptr, nullptr, nullptr}};

  array_cd_init array_cd_fncts[] = {
      {"csv_array", do_csv_array, "csv_array(filename, [skip])",
       "Create a 2D array from the data in a csv file optionally skipping rows."
       " If skip is integer\n\t\t\tskip that many rows; if skip is a character, skip lines "
       "beginning with "
       "that character"},
      {nullptr, nullptr, nullptr, nullptr}};

  array_cc_init array_cc_fncts[] = {
      {"csv_array", do_csv_array2, "csv_array(filename, [skip])",
       "Create a 2D array from the data in a csv file optionally skipping rows."
       " If skip is integer skip that many rows; if skip is a character, skip lines beginning with "
       "that character"},
      {"array_from_string", do_array_from_string, "array_from_string(string, delim)",
       "Create a 1D array from the data in a delimited string."
       " The array double values are\n\t\t\tseparated by one or more of the characters in the "
       "string "
       "variable delim."},
      {nullptr, nullptr, nullptr, nullptr}};

  array_ddd_init array_ddd_fncts[] = {
      {"linear_array", do_linear_array, "linear_array(init, final, count)",
       "Create a 1D array of 'count' rows. Values linearly spaced from 'init' to 'final'."},
      {"make_array", do_make_array_init, "make_array(rows, cols, init=0)",
       "Create a 2D array of size 'rows' by 'cols' initialized to 'init'. 0 if not specified."},
      {nullptr, nullptr, nullptr, nullptr}};

  array_dd_init array_dd_fncts[] = {
      {"make_array", do_make_array, "make_array(rows, cols)",
       "Create a 2D array of size 'rows' by 'cols' initialized to zero."},
      {nullptr, nullptr, nullptr, nullptr}};

  array_d_init array_d_fncts[] = {
      {"identity", do_identity, "identity(size)",
       "Create a 2D identity array with 'size' rows and columns. Diagonal = 1.0"},
      {nullptr, nullptr, nullptr, nullptr}};

  array_a_init array_a_fncts[] = {
      {"transpose", do_transpose, "transpose(array)", "Return the transpose of input array"},
      {nullptr, nullptr, nullptr, nullptr}};

  // clang-format off
  var_init variables[] = {
      {"DEG",  57.29577951308232087680},  /* 180/pi, degrees per radian */
      {"RAD",   0.01745329251994329576},  /* pi/180, radians per degree */
      {"E",     2.71828182845904523536},  /* e, base of natural log     */
      {"GAMMA", 0.57721566490153286060},  /* euler-mascheroni constant  */
      {"PHI",   1.61803398874989484820},  /* golden ratio               */
      {"TAU",   6.28318530717958623200},  /* 2*PI see Tau Manifesto, http://tauday.com */
      {"PI",    3.14159265358979323846},  /* pi                         */
      {"PI_2",  1.57079632679489661923},  /* pi / 2                      */
      {"SQRT2", 1.41421356237309504880},  /* square root of 2            */
      {"TRUE",  1},
      {"FALSE", 0},
      {nullptr, 0}
  };
  // clang-format on

  svar_init svariables[] = {{"_FORMAT", "%.10g"}, /* Default output format */
                            {nullptr, nullptr}};
  /* NOTE: The current comment is stored in "_C_"
   *     Since it can be changed by user on command line, we
   *     initialize is differently than the other string variables.
   */

#define internal_init_table(functions, func_type, sym_type)                                        \
  do {                                                                                             \
    for (int i = 0; functions[i].fname != nullptr; i++) {                                          \
      symrec *ptr          = putsym(functions[i].fname, sym_type, true);                           \
      ptr->value.func_type = functions[i].fnct;                                                    \
      ptr->info            = functions[i].description;                                             \
      ptr->syntax          = functions[i].syntax;                                                  \
    }                                                                                              \
  } while (0)

  extern SEAMS::Aprepro *aprepro;

  void Aprepro::init_table(const char *comment)
  {
    // clang-format off
    internal_init_table(arith_0_fncts,      fnctptr,        SYMBOL_TYPE::FUNCTION);
    internal_init_table(arith_fncts,        fnctptr_d,      SYMBOL_TYPE::FUNCTION);
    internal_init_table(arith_dd_fncts,     fnctptr_dd,     SYMBOL_TYPE::FUNCTION);
    internal_init_table(arith_a_fncts,      fnctptr_a,      SYMBOL_TYPE::FUNCTION);
    internal_init_table(arith_dddd_fncts,   fnctptr_dddd,   SYMBOL_TYPE::FUNCTION);
    internal_init_table(arith_ccc_fncts,    fnctptr_ccc,    SYMBOL_TYPE::FUNCTION);
    internal_init_table(arith_cc_fncts,     fnctptr_cc,     SYMBOL_TYPE::FUNCTION);
    internal_init_table(arith_c_fncts,      fnctptr_c,      SYMBOL_TYPE::FUNCTION);
    internal_init_table(arith_cd_fncts,     fnctptr_cd,     SYMBOL_TYPE::FUNCTION);
    internal_init_table(arith_ddd_fncts,    fnctptr_ddd,    SYMBOL_TYPE::FUNCTION);
    internal_init_table(arith_dddddd_fncts, fnctptr_dddddd, SYMBOL_TYPE::FUNCTION);

    internal_init_table(string_fncts,       strfnct,        SYMBOL_TYPE::STRING_FUNCTION);
    internal_init_table(string_c_fncts,     strfnct_c,      SYMBOL_TYPE::STRING_FUNCTION);
    internal_init_table(string_d_fncts,     strfnct_d,      SYMBOL_TYPE::STRING_FUNCTION);
    internal_init_table(string_dcc_fncts,   strfnct_dcc,    SYMBOL_TYPE::STRING_FUNCTION);
    internal_init_table(string_ccc_fncts,   strfnct_ccc,    SYMBOL_TYPE::STRING_FUNCTION);
    internal_init_table(string_cc_fncts,    strfnct_cc,     SYMBOL_TYPE::STRING_FUNCTION);
    internal_init_table(string_a_fncts,     strfnct_a,      SYMBOL_TYPE::STRING_FUNCTION);

    internal_init_table(array_c_fncts,      arrfnct_c,      SYMBOL_TYPE::ARRAY_FUNCTION);
    internal_init_table(array_cc_fncts,     arrfnct_cc,     SYMBOL_TYPE::ARRAY_FUNCTION);
    internal_init_table(array_cd_fncts,     arrfnct_cd,     SYMBOL_TYPE::ARRAY_FUNCTION);
    internal_init_table(array_d_fncts,      arrfnct_d,      SYMBOL_TYPE::ARRAY_FUNCTION);
    internal_init_table(array_dd_fncts,     arrfnct_dd,     SYMBOL_TYPE::ARRAY_FUNCTION);
    internal_init_table(array_ddd_fncts,    arrfnct_ddd,    SYMBOL_TYPE::ARRAY_FUNCTION);
    internal_init_table(array_a_fncts,      arrfnct_a,      SYMBOL_TYPE::ARRAY_FUNCTION);
    // clang-format on

    for (int i = 0; variables[i].vname != nullptr; i++) {
      // These should be immutable, but possible user is using them for some other purpose...
      // For backward compatibility, keep as mutable.
      add_variable(variables[i].vname, variables[i].value, false, true);
    }

    for (int i = 0; svariables[i].vname != nullptr; i++) {
      add_variable(svariables[i].vname, svariables[i].value, false, true);
    }

    add_variable("_C_", comment, false, true);

    std::string version = SEAMS::Aprepro::version();
    auto        tokens  = tokenize(version, " ");
    double      ver     = std::stod(tokens[0]);
    add_variable("_VERSION_", ver, true, true);
  }
} // namespace SEAMS
