/* Copyright 2025 by COG9 Enterprises LLC
 * All rights reserved.
 * Title: graph.ino
 * Author: Jed Marti KI7NNP
 * Description: Graph 100 values. Work in progress.
 * Created: Mon Oct 13 20:57:11 2025
 * Revision History:
 */

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#include "powerard.h"
#include "ssd1306.h"
#include "graph.h"


extern Adafruit_SSD1306 display;

// The current sensors.
extern INA219 MA;
extern INA219 MB;
extern INA219 MC;

// I2C temperature addresses.
extern uint8_t aTemp_i2c;
extern uint8_t bTemp_i2c;
extern uint8_t cTemp_i2c;
extern uint8_t auxTemp_i2c;

// Graphics queues.
extern Queue16 maA;
extern Queue16 maB;
extern Queue16 maC;
extern Queue8  TA;
extern Queue8  TB;
extern Queue8  TC;
extern Queue8  TAUX;
extern QueueF  BAT;

// Number of milliseconds between graph data samples.
extern uint16_t graphMS;

void initGraphics(void)
{
  graphMS = E.graphMS;
  initQueue16(&maA, "Supply A (5v) mA");
  initQueue16(&maB, "Supply B (12v) mA");
  initQueue16(&maC, "Supply C (19v) mA");
  initQueue8(&TA, "Temp A  F");
  initQueue8(&TB, "Temp B F");
  initQueue8(&TC, "Temp C F");
  initQueue8(&TAUX, "AUX F");
  initQueueF(&BAT, "Battery Volts");
}


void graphicsSample(void)
/*     Save the current values for the graphics screen of which there are potentially 8 based
 *   on the queues above.
 */
{
  int32_t ma;

  if (isOK(MA))
  {
    ma = (int16_t) ina219GetCurrent(&MA);
    if (ma < 0) ma = -ma;
    add2Queue16(&maA, (uint16_t) ma);
  }
  if (isOK(MB)) add2Queue16(&maB, (uint16_t) ina219GetCurrent(&MB));
  if (isOK(MC)) add2Queue16(&maC, (uint16_t) ina219GetCurrent(&MC));
  if (aTemp_i2c) add2Queue8(&TA, getFahrenheit(aTemp_i2c));
  if (bTemp_i2c) add2Queue8(&TB, getFahrenheit(bTemp_i2c));
  if (cTemp_i2c) add2Queue8(&TC, getFahrenheit(cTemp_i2c));
  if (auxTemp_i2c) add2Queue8(&TAUX, getFahrenheit(auxTemp_i2c));
  add2QueueF(&BAT, raw2Voltf(batRaw()));
}

static void hLineDot(int16_t x1, int16_t x2, int16_t y)
{
  int16_t x;
  for (x = x1; x <= x2; x += 2) display.drawPixel(x, y, 1);
}


static void vLineDot(int16_t x, int16_t y1, int16_t y2)
{
  int16_t y;
  if (y1 > y2) { y = y1; y1 = y2; y2 = y; }
  for (y = y1; y <= y2; y += 2) display.drawPixel(x, y, 1);
}



static void graphBackground(char *msg1)
   
{
  int16_t ox, oy;
  int16_t nx, ny;
  
  display.clearDisplay();

  display.setCursor(0, 0);
  display.setTextSize(1);
  display.print(msg1);

  display.setTextSize(1);
  //  display.setCursor(0, 10);
  //  display.print(msg2);

  // Inside box is 32 x 100
  hLineDot(26, 127, 64 - 10);        // Bottom line.
  hLineDot(26, 127, 64 - 26);        // Center line.
  hLineDot(26, 127, 64 - 44);        // Top line.
  vLineDot(26, 64 - 10, 64 - 44);
  vLineDot(127, 64 - 10, 64 - 44);

  batteryIcon();
}



static void graphBackgroundSVG(char *msg1)
/*     Draw the SVG background. This has the owner and the name of the graph. The SVG is written
 *   to the serial port where a special server can read it.
 * Parameters:
 *   msg1: Graph name (ampsA, ...)
 * External References:
 *   E: EEPROM copy, set at startup.
 * Returned values: none
 * Errors detected: none
 */
{
  int16_t ox, oy;
  int16_t nx, ny;
  char    msg[64];
  
  Serial.print(F("<text x=\"0\" y=\"7\" fill=\"0\" class=\"f10\">"));
  if (E.eeOwner[0] != 0)
    snprintf(msg, 64, "%s %s", E.eeOwner, msg1);
  else
    strcpy(msg, msg1);
  Serial.print(msg);
  Serial.println(F("</text>"));
	       
  // Inside box is 32 x 100
  Serial.print("<line x1=\"26\" x2=\"127\" y1=\"54\" y2=\"54\" stroke=\"#C0C0C0\" />");
  Serial.print("<line x1=\"26\" x2=\"127\" y1=\"38\" y2=\"38\" stroke=\"#C0C0C0\" />");
  Serial.print("<line x1=\"26\" x2=\"127\" y1=\"20\" y2=\"20\" stroke=\"#C0C0C0\" />");
  Serial.print("<line x1=\"26\" x2=\"26\" y1=\"54\" y2=\"20\" stroke=\"#C0C0C0\" />");
  Serial.print("<line x1=\"127\" x2=\"127\" y1=\"54\" y2=\"20\" stroke=\"#C0C0C0\" />");
}



void graph8(Queue8 *q)
{
  uint16_t biggest;
  uint8_t i;
  uint8_t b;
  int16_t scale;
  int16_t ox, oy, nx, ny;
  
  graphBackground(q->qname);

  // Decide on scale.
  biggest = 0;
  for (i = 0; i < queueSize; ++i)
  {
    b = getQueue8(q, i);
    if (b > biggest) biggest = b;
  }

  if (biggest < 32) scale = 1;
  else if (biggest < 64) scale = 2;
  else if (biggest < 128) scale = 4;
  else scale = 8;

  display.setCursor(0, 64 - 18);
  display.print(0);
  display.setCursor(0, 64 - 32);
  display.print(scale * 16);
  display.setCursor(0, 64 - 48);
  display.print(scale * 32);
  
  ox = 27,
    oy = 64 - (getQueue8(q, 0) / scale + 11);
  for (i = 1; i < queueSize; ++i)
  {
    nx = ox + 1,
      ny = 64 - ( getQueue8(q, i) / scale + 11);
    display.drawLine(ox, oy, nx, ny, 1);
    ox = nx,
      oy = ny;
  }
}


  
void graph16(Queue16 *q)
/*     Graph 16 bit values - these are current draws in milliamps. Scale to fit on the screen
 * Parameters:
 *   q: Queue control block. Has name, index and values.
 * External References:
 *   display: OLED display handle. Note: y is upside down.
 * Returned values: none
 * Errors detected: none
 */
{
  uint16_t biggest;
  uint8_t i;
  uint16_t b;
  int16_t scale;
  int16_t ox, oy, nx, ny;
  
  graphBackground(q->qname);

  // Decide on scale.
  biggest = 0;
  for (i = 0; i < queueSize; ++i)
  {
    b = getQueue16(q, i);
    if (b > biggest) biggest = b;
  }

  if (biggest < 32) scale = 1;
  else if (biggest < 64) scale = 2;
  else if (biggest < 128) scale = 4;
  else if (biggest < 256) scale = 8;
  else if (biggest < 512) scale = 16;
  else if (biggest < 1024) scale = 32;
  else if (biggest < 2048) scale = 64;
  else if (biggest < 4096) scale = 128;
  else if (biggest < 8192) scale = 256;
  else scale = 128;

  display.setCursor(0, 64 - 18);
  display.print(0);
  display.setCursor(0, 64 - 32);
  display.print(scale * 16);
  display.setCursor(0, 64 - 48);
  display.print(scale * 32);

  ox = 27,
    oy = 63 - (getQueue16(q, 0) / scale + 11);
  for (i = 1; i < queueSize; ++i)
  {
    nx = ox + 1,
      ny = 63 - (getQueue16(q, i) / scale + 11);
    display.drawLine(ox, oy, nx, ny, 1);
    ox = nx,
      oy = ny;
  }
}


#define FSCALE 1.88
void graphF(QueueF *q)
/*     Floating point is the battery input voltage. We graph 3.0 - 21.0 so the scale is 1.88
 * Parameters:
 *   q: Queue control block. Has name, index and values.
 * External References:
 *   display: OLED display handle. Note: y is upside down.
 *   FSCALE: constant to scale voltage to 0-32.
 * Returned values: none
 * Errors detected: none */
{
  float   biggest;
  uint8_t i;
  float   f;
  int16_t ox, oy, nx, ny;

  graphBackground(q->qname);
  display.setCursor(0, 64 - 18);
  display.print(3.0f, 1);
  display.setCursor(0, 64 - 32);
  display.print(12.0f, 1);
  display.setCursor(0, 64 - 48);
  display.print(21.0f, 1);

  ox = 27,
    oy = 63 - (((int16_t) ((getQueueF(q, 0) - 3.0f) * FSCALE)) + 11);
  for (i = 1; i < queueSize; ++i)
  {
    nx = ox + 1,
      ny = 63 - (((int16_t) ((getQueueF(q, i) -3.0f) * FSCALE)) + 11);
    display.drawLine(ox, oy, nx, ny, 1);
    ox = nx,
      oy = ny;
  }
}



static void headerSVG(void)
/*     Write the SVG graph header to the serial port. We start with a ^A that the web server is
 *   looking for to indicate start of some data to write to a file, then the box size and more
 *   font information than we need.
 * Parameters: none
 * External References: none
 * Returned values: none
 * Errors Detected: none here.
 */
{
  // ^A to start.
  Serial.write((uint8_t) 1);

  // SVG.
  Serial.println(F("<svg version=\"1.1\" viewBox=\"0 0 127 70\" xmlns=\"http://www.w3.org/2000/svg\">"));
  Serial.println(F("<style>"));
  Serial.println(F(".f0 { font: normal 0px sans-serif; }"));
  Serial.println(F(".f1 { font: normal 1px sans-serif; }"));
  Serial.println(F(".f2 { font: normal 2px sans-serif; }"));
  Serial.println(F(".f3 { font: normal 3px sans-serif; }"));
  Serial.println(F(".f4 { font: normal 4px sans-serif; }"));
  Serial.println(F(".f5 { font: normal 5px sans-serif; }"));
  Serial.println(F(".f6 { font: normal 6px sans-serif; }"));
  Serial.println(F(".f7 { font: normal 7px sans-serif; }"));
  Serial.println(F(".f8 { font: normal 8px sans-serif; }"));
  Serial.println(F(".f9 { font: normal 9px sans-serif; }"));
  Serial.println(F(".f10 { font: normal 10px sans-serif; }"));
  Serial.println(F("</style>"));
}



static void graph8SVG(Queue8 *q)
/*     Draw an 8 bit graph into the serial port as SVG. This will be requested by
 *        svg ???    where ??? is the name of an 8 bit graph (temperature).
 *   The web server does the requesting on a serial port, it stores it in /tmp and then points
 *   the browser at the result.
 * Parameters:
 *   q: one of the 8 bit queues (temperature).
 * External References: none
 * Returned values: none
 * Errors detected: none
 */
{
  uint16_t biggest;
  uint8_t i;
  uint8_t b;
  int16_t scale;
  int16_t ox, oy, nx, ny;

  // SVG header and background to serial port.
  headerSVG();
  graphBackgroundSVG(q->qname);

  // Decide on scale.
  biggest = 0;
  for (i = 0; i < queueSize; ++i)
  {
    b = getQueue8(q, i);
    if (b > biggest) biggest = b;
  }

  // Set scale factor to fit in graph area.
  if (biggest < 32) scale = 1;
  else if (biggest < 64) scale = 2;
  else if (biggest < 128) scale = 4;
  else scale = 8;

  // Graticule.
  Serial.println(F("<text x=\"0\" y=\"55\" fill=\"#000000\" class=\"f6\">0</text>"));
  Serial.print(F("<text x=\"0\" y=\"40\" fill=\"#000000\" class=\"f6\">"));
  Serial.print(scale * 16);
  Serial.println(F("</text>"));
  Serial.print(F("<text x=\"0\" y=\"22\" fill=\"#000000\" class=\"f6\">"));
  Serial.print(scale * 32);
  Serial.println(F("</text>"));

  // The graph in red.
  ox = 27,
    oy = 64 - (getQueue8(q, 0) / scale + 11);
  for (i = 1; i < queueSize; ++i)
  {
    nx = ox + 1,
      ny = 64 - ( getQueue8(q, i) / scale + 11);
    Serial.print(F("<line x1=\""));
    Serial.print(ox);
    Serial.print(F("\" y1=\""));
    Serial.print(oy);
    Serial.print(F("\" x2=\""));
    Serial.print(nx);
    Serial.print(F("\" y2=\""));
    Serial.print(ny);
    Serial.println("\" stroke=\"#FF0000\"/>");
    ox = nx,
      oy = ny;
  }
  Serial.println(F("</svg>"));

  // ^D ends it. The webserver is waiting for this to send it to a browser.
  Serial.write(4);
}



static void graph16SVG(Queue16 *q)
/*     Graph 16 bit values - these are current draws in milliamps. Output is SVG, probably should
 *   be javaScript and real time.
 * Parameters:
 *   q: Queue control block. Has name, index and values.
 * External References: none
 * Returned values: none
 * Errors detected: none
 */
{
  uint16_t biggest;
  uint8_t i;
  uint16_t b;
  int16_t scale;
  int16_t ox, oy, nx, ny;

  headerSVG();
  graphBackgroundSVG(q->qname);

  // Decide on scale.
  biggest = 0;
  for (i = 0; i < queueSize; ++i)
  {
    b = getQueue16(q, i);
    if (b > biggest) biggest = b;
  }

  if (biggest < 32) scale = 1;
  else if (biggest < 64) scale = 2;
  else if (biggest < 128) scale = 4;
  else if (biggest < 256) scale = 8;
  else if (biggest < 512) scale = 16;
  else if (biggest < 1024) scale = 32;
  else if (biggest < 2048) scale = 64;
  else if (biggest < 4096) scale = 128;
  else if (biggest < 8192) scale = 256;
  else scale = 128;

  Serial.println(F("<text x=\"0\" y=\"55\" fill=\"#000000\" class=\"f6\">0</text>"));
  Serial.print(F("<text x=\"0\" y=\"40\" fill=\"#000000\" class=\"f6\">"));
  Serial.print(scale * 16);
  Serial.println(F("</text>"));
  Serial.print(F("<text x=\"0\" y=\"22\" fill=\"#000000\" class=\"f6\">"));
  Serial.print(scale * 32);
  Serial.println(F("</text>"));

  ox = 27,
    oy = 63 - (getQueue16(q, 0) / scale + 11);
  for (i = 1; i < queueSize; ++i)
  {
    nx = ox + 1,
      ny = 63 - (getQueue16(q, i) / scale + 11);
    Serial.print(F("<line x1=\""));
    Serial.print(ox);
    Serial.print(F("\" y1=\""));
    Serial.print(oy);
    Serial.print(F("\" x2=\""));
    Serial.print(nx);
    Serial.print(F("\" y2=\""));
    Serial.print(ny);
    Serial.println("\" stroke=\"#FF0000\"/>");
    ox = nx,
      oy = ny;
  }
  Serial.println(F("</svg>"));

  // Close the dump.
  Serial.write(4);
}



void graphFSVG(QueueF *q)
/*     Floating point is the battery input voltage. We graph 3.0 - 21.0 so the scale is 1.88
 * Parameters:
 *   q: Queue control block. Has name, index and values.
 * External References:
 *   FSCALE: constant to scale voltage to 0-32.
 * Returned values: none
 * Errors detected: none
 */
{
  float   biggest;
  uint8_t i;
  float   f;
  int16_t ox, oy, nx, ny;

  headerSVG();
  graphBackgroundSVG(q->qname);
  
  Serial.println(F("<text x=\"0\" y=\"55\" fill=\"#000000\" class=\"f6\">3</text>"));
  Serial.println(F("<text x=\"0\" y=\"40\" fill=\"#000000\" class=\"f6\">12</text>"));
  Serial.print(F("<text x=\"0\" y=\"22\" fill=\"#000000\" class=\"f6\">24</text>"));
  
  ox = 27,
    oy = 63 - (((int16_t) ((getQueueF(q, 0) - 3.0f) * FSCALE)) + 11);
  for (i = 1; i < queueSize; ++i)
  {
    nx = ox + 1,
      ny = 63 - (((int16_t) ((getQueueF(q, i) -3.0f) * FSCALE)) + 11);
    Serial.print(F("<line x1=\""));
    Serial.print(ox);
    Serial.print(F("\" y1=\""));
    Serial.print(oy);
    Serial.print(F("\" x2=\""));
    Serial.print(nx);
    Serial.print(F("\" y2=\""));
    Serial.print(ny);
    Serial.println("\" stroke=\"#FF0000\"/>");
    ox = nx,
      oy = ny;
    
    ox = nx,
      oy = ny;
  }
  Serial.println(F("</svg>"));

  // Close the dump.
  Serial.write(4);
}



void graphSVG(void)
{
  char *tok;
  tok = strtok(NULL, " \n");
  if (strcasecmp(tok, "tempA") == 0) graph8SVG(&TA);
  else if (strcasecmp(tok, "tempB") == 0) graph8SVG(&TB);
  else if (strcasecmp(tok, "tempC") == 0) graph8SVG(&TC);
  else if (strcasecmp(tok, "tempAUX") == 0) graph8SVG(&TAUX);
  else if (strcasecmp(tok, "ampsA") == 0) graph16SVG(&maA);
  else if (strcasecmp(tok, "ampsB") == 0) graph16SVG(&maB);
  else if (strcasecmp(tok, "ampsC") == 0) graph16SVG(&maC);
  else if (strcasecmp(tok, "battery") == 0) graphFSVG(&BAT);
  else Serial.print(F("\n?\n"));
}


// EOF
