/**
 * @file src/main.c
 * @brief Define proc_cmds
 */

#include "main.h"
#include "benchmarking.h"
#include "editline.h"
#include "elemop.h"
#include "error.h"
#include "evalfn.h"
#include "exproriented.h"
#include "graphplot.h"
#include "mem.h"
#include "optexpr.h"
#include "rand.h"
#include "rc.h"
#include "testing.h"

auto eval_f = evalExprReal;
void (*print_complex)(comp) = printComplexComplex;

int main(int argc, char const **argv) {
  initPlotCfg();
  loadInitScript(nullptr);

  procAList(argc - 1, argv + 1);

  readerLoop(stdin);

  return 0;
}

//! @brief Display help message
static void startupMsg() {
  PRINT(
    "===========================================================\n",
    "     RPX - Reverse Polish notation calculator eXtended\n",
    "===========================================================\n",
    " A powerful RPN calculator supporting multiple modes:\n",
    "  - Real numbers      (default)\n",
    "  - comp numbers   (:tc to toggle)\n",
    "\n",
    " Features:\n",
    "  - Register ($a to $z)\n",
    "  - Result history (@a, @h)\n",
    "  - Special constants (\\P for pi, \\E for e)\n",
    "  - Advanced functions (sin, cos, log, etc.)\n",
    "  - Matrix operations (in comp mode)\n",
    "\n",
    " Usage:\n",
    "  - Enter RPN expressions directly\n",
    "  - Use ':' for commands (e.g., :tc for comp mode)\n",
    "\n",
    " Examples:\n",
    "  3 4 + 5 *     -> 35\n",
    "  2 \\P * s      -> 0 (approximately)\n",
    "  [2; 2 2][2; 1 2 3 4]*  -> [8,12] (matrix multiplication)\n",
    "\n",
    " Press Ctrl+D to exit the program.\n",
    "===========================================================\n",
  );
}

/**
 * @brief Process input
 * @param[in] input_buf input
 */
[[gnu::nonnull]] void procInput(char const *restrict input_buf) {
  *input_buf == ':' ? procCmds(input_buf + 1) : printElem(eval_f(input_buf));
}

/**
 * @brief Process argument list
 * @param[in] argc arg count
 * @param[in] argv arg value
 */
void procAList(int argc, char const **argv) {
  if (argc == 0) return;

  if (**argv != '-') { // interpreted as a file name
    FILE *fp dropfile
      = fopen(*argv, "r") ?: p$panic(ERR_FILE_NOT_FOUND, "%s ", *argv);
    readerLoop(fp);
  } else switch ((*argv)[1]) { // interpreted as a option
    case 'h':
      startupMsg();
      break;
    case 'r':
      procInput(*++argv);
      break;
    case 'q':
      exit(0);
    default:
      panic(ERR_UNKNOWN_OPTION, "%c ", (*argv)[1]);
    }

  procAList(argc - 1, argv + 1);
}

/**
 * @brief Read raw line from fp
 * @return is reached EOF
 */
static bool readRawLine(char *buf, size_t len, FILE *fp) {
  return fgets(buf, (int)len, fp) != nullptr;
}

/**
 * @brief Read from stdin
 * @return is pressed ctrl_d
 */
static bool readerInteractiveLine(char *buf, size_t len, FILE *__) {
  return editline(len, buf);
}

/**
 * @brief File reading loop
 * @param[in] fp File stream
 */
[[gnu::nonnull]] void readerLoop(FILE *restrict fp) {
  char input_buf[buf_size];
  auto reader_fn = fp == stdin ? readerInteractiveLine : readRawLine;
  while (reader_fn(input_buf, buf_size, fp)) [[clang::likely]]
    procInput(input_buf);
}

/**
 * @brief Output elem_t in appropriate format
 * @param[in] elem Output comtent
 */
void printElem(elem_t elem) {
  switch (elem.rtype) {
  case RTYPE_REAL:
    printReal(elem.elem.real);
    break;
  case RTYPE_COMP:
    print_complex(elem.elem.comp);
    break;
  case RTYPE_MATR:
    printMatrix(elem.elem.matr);
    break;
  case RTYPE_LAMB:
    printLambda(elem.elem.lamb);
    break;
  default:
    [[clang::unlikely]];
  }
}

/**
 * @brief Output value of type double
 * @param[in] result Output value
 */
void printReal(double result) {
  if (result == (double)(long)result) PRINT("result: ", (long)result, "\n");
  else PRINT("result: ", result, "\n");
}

/**
 * @brief Output value of type comp
 */
void printComplexComplex(comp result) {
  PRINT("result: ", creal(result), " + ", cimag(result), "i\n");
}

/**
 * @brief Output value of type comp in phasor view
 */
void printComplexPolar(comp result) {
  comp res = result;
  if (isnan(creal(res)) || isnan(cimag(res))) return;

  PRINT(
    "result: ", cabs(res), " \\phasor ", atan2(cimag(res), creal(res)), "\n"
  );
}

/**
 * @brief Output value of type matrix_t
 */
void printMatrix(matrix_t result) {
  for (size_t i = 0; i < result.rows; i++) {
    for (size_t j = 0; j < result.cols; j++) {
      comp res = result.matrix[result.cols * i + j];
      putchar('\t');
      if (eq(cimag(res), 0.0)) PRINT(creal(res));
      else PRINT(creal(res), " + ", cimag(res), "i");
    }

    putchar('\n');
  }
}

[[gnu::nonnull]] void printLambda(char const *result) {
  PRINT("result: ", (char *)result, "\n");
}

/**
 * @brief Process command
 * @param[in] cmd Command input
 */
[[gnu::nonnull]] void procCmds(char const *restrict cmd) {
  plotcfg_t pcfg = getPlotCfg();

  // TODO save registers
  switch (*cmd++) {
  case 't':   // toggle
    switch (*cmd) {
    case 'c': // comp mode
      eval_f = eval_f == evalExprReal ? evalExprComplex : evalExprReal;
      break;
    case 'p': // plotcfg explicit implicit
      pcfg.plotexpr = pcfg.plotexpr == plotexpr ? plotexprImplicit : plotexpr;
      setPlotCfg(pcfg);
      break;
    case 'P': // print_complex
      print_complex = print_complex == printComplexComplex
                      ? printComplexPolar
                      : printComplexComplex;
      break;
    default:
      [[clang::unlikely]];
    }
    break;
  case 'o': {
    char buf[buf_size];
    strncpy(buf, cmd, buf_size);
    optexpr(buf);
    puts(buf);
  } break;
  case 'p':
    cmd += skipByte(cmd, ' ');
    if (*cmd == '\0') {
      plotexpr(pcfg.prevexpr);
      break;
    }

    pcfg.plotexpr(cmd);
    strncpy(pcfg.prevexpr, cmd, buf_size);
    setPlotCfg(pcfg);
    break;
  case 'r':   // rand
    switch (*cmd) {
    case 's': // set seed
      sxorsh((uint64_t)evalExprReal(cmd + 1).elem.real);
      break;
    default:
    }
    break;
  case 's':   // settings
    switch (*cmd) {
    case 'p': // plot
      changePlotCfg(cmd + 1);
      break;
    default:
      [[clang::unlikely]];
    }
    break;
  default:
    DISPERR("unknown command: ", *(cmd - 1));
  }
}