/**
 * @file src/graphplot.c
 * @brief Define graph related functions
 */

#include "graphplot.h"
#include "error.h"
#include "evalfn.h"
#include "rtconf.h"
#include "term.h"

constexpr double fontrow = 2;
constexpr double fontcol = 1;
constexpr double font_ratio = fontcol / fontrow;

static void drawAxisX(double xn, int disp_size, double dx) {
  putchar('\t');
  putchar('+');
  for (int i = 0; i < disp_size / font_ratio; i++) putchar('-');
  putchar('\n');
  putchar('\t');
  printf("%.3lf", xn);
  for (int i = 0; i < disp_size / font_ratio / 2; i++) putchar(' ');
  printf("%.3lf", xn + dx * disp_size / 2 / font_ratio);
  putchar('\n');
}

plotbuf_t plotBuf(size_t row, size_t col) {
  char **b = xalloc(char *, row);
  for (size_t i = 0; i < row; i++) {
    b[i] = xalloc(char, col + 1);
    memset(b[i], ' ', col);
    b[i][col] = 0;
  }
  return (plotbuf_t){.buf = b, .row = row, .col = col};
}

void freePlotBuf(plotbuf_t *pb) {
  for (size_t i = 0; i < pb->row; i++) free(pb->buf[i]);
  free(pb->buf);
}

void displayPlotBuf(plotbuf_t pb, double yn, double dy) {
  for (int i = (int)pb.row - 1; 0 <= i; i--) {
    double y = yn + dy * i;
    printf("%.3lf\t|", y);
    puts(pb.buf[i]);
  }
}

[[gnu::nonnull]] void plotexpr(char const *restrict expr) {
  plotcfg_t const pcfg = getPlotCfg();
  machine_t m;
  size_t col = (size_t)(pcfg.dispx / font_ratio);
  real_t stack = {.elem = {.real = pcfg.xn}, .isnum = true};
  plotbuf_t pb ondrop(freePlotBuf) = plotBuf((size_t)pcfg.dispy, col);

  m.e.args = &stack - 7;
  m.c.expr = expr;
  initEvalinfo(&m);
  rpxEval(&m);
  double y0 = m.s.rsp->elem.real;
  for (size_t i = 0; i < col; i++) {
    double x1 = pcfg.xn + pcfg.dx * (double)i;
    stack.elem.real = x1;
    initEvalinfo(&m);
    rpxEval(&m);
    double y1 = m.s.rsp->elem.real;
    double gt = more(y0, y1);
    double lt = less(y0, y1);
    if (gt < pcfg.yn || pcfg.yx < lt) continue;
    size_t gt_index = (size_t)less(pcfg.dispy - 1, (gt - pcfg.yn) / pcfg.dy);
    size_t lt_index = (size_t)more(0, (lt - pcfg.yn) / pcfg.dy);
    for (size_t j = lt_index; j <= gt_index; j++) pb.buf[j][i] = '*';
    y0 = y1;
  }

  displayPlotBuf(pb, pcfg.yn, pcfg.dy);
  drawAxisX(pcfg.xn, pcfg.dispx, pcfg.dx);
}

[[gnu::nonnull]] void plotexprImplicit(char const *restrict expr) {
  plotcfg_t const pcfg = getPlotCfg();
  machine_t m;
  m.c.expr = expr;

  double err = pcfg.dy / 2; // allowable error
  double x0 = pcfg.xn - pcfg.dx;
  real_t stack[2] = {{.isnum = true}, {.isnum = true}};
  m.e.args = (real_t *)stack - 6;
  for (int i = 0; i < pcfg.dispy; i++) {
    double y0 = pcfg.yx - pcfg.dy * i;
    printf("%.3lf\t|", y0);
    stack[0].elem.real = y0;
    stack[1].elem.real = x0;
    initEvalinfo(&m);
    rpxEval(&m);
    double y1 = y0 - pcfg.dy;
    stack[0].elem.real = y1;
    double res0 = m.s.rsp->elem.real;
    for (int j = 0; j < pcfg.dispx / font_ratio; j++) {
      double x1 = pcfg.xn + pcfg.dx * j;
      stack[1].elem.real = x1;
      initEvalinfo(&m);
      rpxEval(&m);
      double res1 = m.s.rsp->elem.real;
      putchar(-err <= more(res0, res1) && less(res0, res1) <= err ? '*' : ' ');
      res0 = res1;
    }

    putchar('\n');
  }

  drawAxisX(pcfg.xn, pcfg.dispx, pcfg.dx);
}

static void setPlotBounds(double xx, double xn, double yx, double yn) {
  plotcfg_t pcfg = getPlotCfg();

  pcfg.xx = xx;
  pcfg.xn = xn;
  pcfg.yx = yx;
  pcfg.yn = yn;

  pcfg.dx = (xx - xn) / pcfg.dispx * font_ratio;
  pcfg.dy = (yx - yn) / pcfg.dispy;

  setPlotCfg(pcfg);
}

void initPlotCfg() {
  struct winsize w = getWinSize();
  int dispsz = (int)less((double)w.ws_row, w.ws_col * font_ratio);

  plotcfg_t pcfg = getPlotCfg();
  pcfg.dispx = pcfg.dispy = dispsz - 5;
  pcfg.plotexpr = plotexpr;
  setPlotCfg(pcfg);
  setPlotBounds(1, -1, 1, -1);
}

[[gnu::nonnull]] void changePlotCfg(char const *restrict cmd) {
  switch (*cmd++) {
  case 'd': { // display size
    plotcfg_t pcfg = getPlotCfg();

    stack_t s = evalExprRealStack(cmd);
    double newx = (s.rsp - 1)->elem.real;
    double newy = (s.rsp - 0)->elem.real;

    double centerx = (pcfg.xx + pcfg.xn) / 2;
    double centery = (pcfg.yx + pcfg.yn) / 2;

    double coefx = newx / pcfg.dispx;
    double coefy = newy / pcfg.dispy;

    pcfg.xx = centerx + (pcfg.xx - centerx) * coefx;
    pcfg.xn = centerx + (pcfg.xn - centerx) * coefx;
    pcfg.yx = centery + (pcfg.yx - centery) * coefy;
    pcfg.yn = centery + (pcfg.yn - centery) * coefy;

    pcfg.dispx = (int)newx;
    pcfg.dispy = (int)newy;

    setPlotCfg(pcfg);
  } break;
  case 'r': { // range
    stack_t s = evalExprRealStack(cmd);
    if (s.rsp - 4 != s.rbp) DISPERR("expected 4 params");
    double newxx = (s.rsp - 3)->elem.real;
    double newxn = (s.rsp - 2)->elem.real;
    double newyx = (s.rsp - 1)->elem.real;
    double newyn = (s.rsp - 0)->elem.real;

    setPlotBounds(newxx, newxn, newyx, newyn);
  } break;
  default:
    [[clang::unlikely]];
  }
}