//==============
// VDU.cpp
//==============

#include "VDU.h"
#include <stdint.h>
#include "SimpleILI9341.h"
#include "memory.h"
#include "calcdecl.h"
#include "Help.h"
#include "Plotter.h"
#include "Table.h"
#include "Storage.h"
#include "gauss.h"
#include "solver.h"

char lines[NumLines][lenLineStr];
TImageMode ImageMode;
bool fnDown;
int16_t xCurE,yCurE;
int16_t HScrollE;
int16_t VScrollE;
int8_t xError,yError;
uint32_t TimeDown ;


int16_t MeasureWidth(int16_t x,int16_t y)
{
  char c;
  int16_t i;
  c = lines[y][x];
  lines[y][x] = 0;
  i = TextWidth((char*)lines[y],(char*)CurFont);
  lines[y][x] = c;
  return i;
}

int16_t CursorOffset()
{
  return MeasureWidth(xCurE,yCurE);
}

bool DrawEditorCursor(int16_t x,int16_t y, uint16_t color)
/*returns true if (scroll has changed && it has performed a complete redraw*/
{
  int16_t hmin,hmax,vmin,vmax;

  xCurE = x;
  yCurE = y;

  hmin = 5-CursorOffset();
//  hmax = tft_width-5-(5-hmin);
  hmax = tft_width-5-CursorOffset();

  if (hmin > 0)
    hmin = 0;
  vmin = yCurE-tft_height / (TXTH)+1;
  vmax = yCurE;

  if (HScrollE < hmin)
    HScrollE = hmin; else
  if (HScrollE > hmax)
    HScrollE = hmax; else
  if (VScrollE < vmin)
    VScrollE = vmin; else
  if (VScrollE > vmax)
    VScrollE = vmax; else
  {
    x = CursorOffset()+HScrollE;
    y = (yCurE-VScrollE)*(TXTH);
    DrawLine(x,y+1,x,y+TXTH+1, color);
    return false;
  }

  DrawImageEditor();
  return true;
}

void DrawImageEditor()
{
  int16_t x,y,i;

  if (xCurE > strlen(lines[yCurE]))
    xCurE = strlen(lines[yCurE]);

  HScrollE = constrain(HScrollE, 5-CursorOffset(), tft_width-5-CursorOffset());
  if (HScrollE > 0)
    HScrollE = 0;
  VScrollE = constrain(VScrollE,yCurE-tft_height / (TXTH)+1,yCurE);

//  ClearDisplay(TFT_BLACK);
  for (i = 0; i<NumLines; i++)
    DrawEditorLine(i);

  x = CursorOffset()+HScrollE;
  y = (yCurE-VScrollE)*(TXTH);
  DrawEditorCursor(xCurE,yCurE,TFT_GREEN);
}

bool MoveEditorCursor(int16_t x,int16_t y)
{
  DrawEditorCursor(xCurE,yCurE,TFT_BLACK); // TFT_OLIVE);
  y = constrain(y,0,NumLines-1);
  x = constrain(x,0,strlen(lines[y]));
  return DrawEditorCursor(x,y,TFT_GREEN);
}

void DrawEditorLine(int16_t i)
{
  int16_t x,y;
  char *p;

  y = (i-VScrollE)*(TXTH);
  
  if ((y > tft_height) || (y < 0))
    return;
    
  if (yError == i)
    DrawBox(0, y+1, tft_width, TXTH+1, TFT_MAROON); else
    DrawBox(0, y+1, tft_width, TXTH+1, TFT_BLACK);

  if ((yError == i) && (xError >= 0))
  {
    x = MeasureWidth(xError,yError);
    x = x+HScrollE;
    DrawTriangle(x,y, max(x-6,0), y+TXTH, x+6, y+TXTH, TFT_RED);
  }

  DrawStringAt(HScrollE+1,(i-VScrollE+1)*(TXTH)-3,lines[i],(char*)CurFont, TFT_WHITE);

  if ((yError == i) && (xError < 0))
  {
    switch (xError) {
      case -1: p = " Unresolvable inequality"; break;
      case -2: p = " Too many equations"; break;
      case -3: p = " Memory overflow"; break;
      default: p = " Run error"; 
    }
    x = tft_width-TextWidth(p,(char*)CurFont);
    DrawBox(x, y+1, tft_width-x, TXTH+1, TFT_MAROON);
    DrawStringAt(x,(i-VScrollE+1)*(TXTH)-3,p,(char*)CurFont, TFT_YELLOW);
  }
}

void DeleteTrailingBlanks()
{
  int16_t i;

  for (i = 0; i<NumLines; i++)
    while ((lines[i][0] != 0) && (lines[i][strlen(lines[i])-1] == ' '))
      lines[i][strlen(lines[i])-1] = 0;
  MoveEditorCursor(xCurE,yCurE);
}

int16_t CountLines()
{
  int16_t i;
  for (i = NumLines-1; i>=0; i--)
    if (lines[i][0] != 0)
      return i+1;
  return 0;
}

void FormKeyClick(char Key)
{
  int16_t i;
  char c;

  if (ImageMode == imHelp)
  {
    HelpKeyPress(Key);
    return;
  }

  if (ImageMode == imSolved_edit)
  {
    if (has_plot)
      PlotterFormKeyPress(Key); else
      TableFormKeyPress(Key);
    return;
  }

  if (xCurE >= lenLineStr)
    return;

  ShowErrorAt(-1,-1,"");
  switch(Key) {
    case Key_EXECUTE: SolveBtnClicked(); break;
    case Key_LEFT:    MoveEditorCursor(xCurE-1,yCurE); break;
    case Key_UP:      MoveEditorCursor(xCurE,yCurE-1); break;
    case Key_DOWN:    MoveEditorCursor(xCurE,yCurE+1); break;
    case Key_RIGHT:
        if (xCurE >= strlen(lines[yCurE]))
          InsertChar(lines[yCurE],' ',1000);
        MoveEditorCursor(xCurE+1,yCurE);
        break;
    case Key_HOME:    MoveEditorCursor(0,yCurE); break; // not used in calculator
    case Key_END:     DeleteTrailingBlanks(); MoveEditorCursor(strlen(lines[yCurE]),yCurE); break; // not used in calculator
    case Key_DELETE: // not used in calculator
      if (xCurE < strlen(lines[yCurE]))
      {
        FormKeyClick(char(Key_RIGHT));
        FormKeyClick(char(Key_BACK));
      } break;
  }

  if (ImageMode == imSolved_edit)
    return;

  if ((Key >= ' ') && (Key <= 128)) {
    i = TextWidth(lines[yCurE],(char*)CurFont);
    InsertChar(lines[yCurE],Key,xCurE);
    if (xCurE+1 == strlen(lines[yCurE]))
    { // end of line
      DrawEditorCursor(xCurE,yCurE,TFT_BLACK); // TFT_OLIVE);
      ILI9341SetCursor(i+1, (yCurE-VScrollE+1)*(TXTH)-3);
      DrawChar(Key, (char*)CurFont, TFT_WHITE);
    } else
      DrawEditorLine(yCurE);
    DrawEditorCursor(xCurE+1,yCurE,TFT_GREEN);
    ImageMode = imEdit;
  } else
  switch(Key) {
    case '\r':
      if (yCurE < NumLines-1)
      {
        if ((yCurE >= CountLines()-1) && (xCurE == strlen(lines[yCurE])) ) // at end of last line
        {
          MoveEditorCursor(0,yCurE+1);
        } else
        {
          for (i = NumLines-2; i>=yCurE+1; i--)
            CopyLineString(lines[i+1],lines[i]);
          strcpy(lines[yCurE+1],(char*)(&(lines[yCurE][xCurE])));
          lines[yCurE][xCurE] = 0;
          yCurE++;
          xCurE = 0;
          DrawImageEditor();
        }

        ImageMode = imEdit;
      } break;
    case Key_BACK:
      if (xCurE > 0)
      {
        DrawEditorCursor(xCurE,yCurE,TFT_BLACK); // TFT_OLIVE);
        c = lines[yCurE][xCurE-1];
        for (i = xCurE; i<=strlen(lines[yCurE]); i++) 
          lines[yCurE][i-1] = lines[yCurE][i];
        if (xCurE-1 == strlen(lines[yCurE]))
        {
          i = TextWidth(lines[yCurE],(char*)CurFont);
          DrawBox(i+1, (yCurE-VScrollE)*(TXTH)+1, TXTH, TXTH+1, TFT_BLACK);
        } else
          DrawEditorLine(yCurE);
        DrawEditorCursor(xCurE-1,yCurE,TFT_GREEN);
        ImageMode = imEdit;
      } else
      if (yCurE > 0)
      {
        xCurE = strlen(lines[yCurE-1]);
        if (lines[yCurE][0] != 0)
          for (i = 0; i<=strlen(lines[yCurE])-1; i++) 
            InsertChar((char*)(lines[yCurE-1]),lines[yCurE][i],1000);
        for (i = yCurE; i<=NumLines-2; i++) 
          CopyLineString(lines[i],lines[i+1]);
        lines[NumLines-1][0] = 0;
        yCurE--;
        ImageMode = imEdit;
        DrawImageEditor();
      } break;
    case Key_SELECT: fnDown = ! fnDown; break;
  }
}

char prevButton;

void ButtonDown(int8_t aTag)
{
  const char Cap[][3] = {{0,0,0},
    /*1*/  {'7','a',                 13},
    /*2*/  {'8','b',                 23},
    /*3*/  {'9','c',                 12},
    /*4*/  {'+','d',                 24},
    /*5*/  {'(','e',                 14},
    /*6*/  {Key_UP,'f',              34},
    /*7*/  {Key_SELECT,Key_SELECT,   36},
    /*8*/  {'4','g',                 35},
    /*9*/  {'5','h',                 25},
    /*10*/ {'6','i',                 15},
    /*11*/ {'-','j',                 45}, // j also k
    /*12*/ {')','l',                 46},
    /*13*/ {Key_DOWN,'m',            26},
    /*14*/ {'?','n',                 16},
    /*15*/ {'1','o',                 57},
    /*16*/ {'2','p',                 37},
    /*17*/ {'3','q',                 48},
    /*18*/ {'*','r',                 38},
    /*19*/ {'^','s',                 47},
    /*20*/ {Key_LEFT,'t',            27}, // t also u
    /*21*/ {'\r','\r',               17},
    /*22*/ {'=','v',                 67}, // v also w
    /*23*/ {'0','x',                 78},
    /*24*/ {'.','y',                 58},  // . also ,
    /*25*/ {'/','z',                 68},
    /*26*/ {Key_BACK,Key_BACK,       56},
    /*27*/ {Key_RIGHT,' ',           28},
    /*28*/ {Key_EXECUTE,Key_EXECUTE, 18}};

  char c,d;
  static uint32_t t = 0;
  static char prev = ' ';
  const int DblClk = 1000;

//  DrawInt(aTag,CompactFont, TFT_WHITE); DrawChar(' ',CompactFont, TFT_WHITE); // remove

  TimeDown = millis();
  for (int8_t i = 1; i<=28; i++)
    if (Cap[i][2] == aTag)
      c = Cap[i][fnDown];

//  DrawChar(c,CompactFont, TFT_WHITE); DrawChar(' ',CompactFont, TFT_WHITE); // remove
//return;

  switch (c) {
    case '.':
      if (millis()-t < DblClk)
      {
        if (prev == '.' ) { FormKeyClick(Key_BACK); c = ','; } else
        if (prev == ',' ) { FormKeyClick(Key_BACK); c = '.'; }
      }
      prevButton = c;
      break;
    case 'j':
      if (millis()-t < DblClk)
      {
        if (prev == 'j' ) { FormKeyClick(Key_BACK); c = 'k'; } else
        if (prev == 'k' ) { FormKeyClick(Key_BACK); c = 'j'; }
      }
      prevButton = c;
      break;
    case 't':
      if (millis()-t < DblClk)
      {
        if (prev == 't' ) { FormKeyClick(Key_BACK); c = 'u'; } else
        if (prev == 'u' ) { FormKeyClick(Key_BACK); c = 't'; }
      }
      prevButton = c;
      break;
    case '^':
      if (millis()-t < DblClk)
      {
        if (prev == '^' ) { FormKeyClick(Key_BACK); c = '\\'; } else
        if (prev == '\\' ) { FormKeyClick(Key_BACK); c = '^'; }
      }
      prevButton = c;
      break;
    case 'v':
      if (millis()-t < DblClk)
      {
        if (prev == 'v' ) { FormKeyClick(Key_BACK); c = 'w'; } else
        if (prev == 'w' ) { FormKeyClick(Key_BACK); c = 'v'; }
      }
      prevButton = c;
      break;
    default: prevButton = c;
  }
  t = millis();
  prev = c;
}

void FormKeyHold(char Key)
{
  if ((ImageMode == imEdit) || (ImageMode == imEdit_solved))
  switch (Key) {
    case '?':  // Help
        ImageMode = imHelp;
        ShowHelpPage(0,-1,0);
        break;

    case Key_BACK: //   clear screen
      LinesClear(true); break;

    case '\r': //  duplicate line
      if (yCurE < NumLines-1)
      {
        for (int16_t i = NumLines-2; i>=yCurE+1; i--)
          CopyLineString(lines[i+1],lines[i]);
        CopyLineString(lines[yCurE+1],lines[yCurE]);
        yCurE = yCurE+1;
        DrawImageEditor();
      } break;

    case ' ': { //  delete line
        for (int16_t i = yCurE; i<=NumLines-2; i++)
          CopyLineString(lines[i],lines[i+1]);
        lines[NumLines-1][0] = 0;
        DrawImageEditor();
        xCurE = 0;
      } break;

    case Key_LEFT: //   load previous saved file
      LoadPrevFile(); break;

    case Key_RIGHT: //   load next saved file
      LoadNextFile(); break;

//    char(Key_EXECUTE): ; // stopping solving
  }
}

void Timer()
{
  char c;
  if ((prevButton != 0) && (millis()-TimeDown > 700))
  {
    c = prevButton;
    prevButton = 0;
    FormKeyHold(c);
  }
}

void ButtonUp()
{
  FormKeyClick(prevButton);
  prevButton = 0;
}

void SetImageMode(TImageMode im)
{
  ImageMode = im;
  if (ImageMode == imSolved_edit)
  {
    if (has_plot)
      DrawImagePlot(); else
      DrawImageTable();
  } else
    DrawImageEditor();
}

void ShowErrorAt(int16_t x,int16_t y, char *str)
{
  if (y >= 0)
  {
    if (yError < 0)
    {
      MoveEditorCursor(x,y);
      xError = x;
      yError = y;
    }
  } else
  if (yError >= 0)
  {
    y = yError;
    xError = -1;
    yError = -1;
    DrawEditorLine(y);
    DrawEditorCursor(xCurE,yCurE,TFT_GREEN);
  }
}

void RunError(int8_t equNum, int8_t errnum)
{
  if (equNum > 0)
  {
    xError = errnum;
    yError = equations[equNum].ScreenLine-1;
    MoveEditorCursor(xError,yError);
  }
}

void ResetVDU()
{
  xError = -1;
  yError = -1;
  xCurE = 0;
  yCurE = 0;
  HScrollE = 0;
  VScrollE = 0;
  fnDown = false;
}

void LinesClear(bool draw)
{
  for (int16_t i = 0; i<NumLines; i++)
    lines[i][0] = 0;
  if (draw) {
    ResetVDU();
    ClearDisplay(TFT_BLACK);
    DrawEditorCursor(xCurE,yCurE,TFT_GREEN);
//    DrawImageEditor();
  }
}

void DeleteAnswer(char *src)
{
  char *dest;
  dest = src;
  while (*src != 0)
  {
    if (*src == '<')
    {
      *dest = '?';
      dest++;
      while ((*src != 0) && (*src != '>'))
        src++;
      if (*src == '>')
        src++;
    } else
    {
      *dest = *src;
      src++;
      dest++;
    }
  }
  *dest = 0;
}

void DeleteAnswers()
{
  for (int16_t y = 0; y<=NumLines-1; y++)
    DeleteAnswer((char*)(lines[y]));
//  DrawImageEditor();
}

void InsertAnswer(int16_t x,int16_t y, char *s)
{
  DeleteAnswer((char*)(lines[y-1]));

  lines[y-1][x-1] = '<';

  while (*s != 0)
  {
    InsertChar(lines[y-1],*s,x);
    x++;
    s++;
  }
  InsertChar(lines[y-1],'>',x);
}

void CopyLineString(char *dest,char *src)
{
  CopyStringN(dest,src,lenLineStr);
}

void InsertChar(char *dest, char c, int16_t n)
/* dest[n] becomes c; rest of dest moved along; max len of dest is lenLineStr; if (i > len then appends*/
{
  int16_t i,j;

  i = strlen(dest);
  if (i >= lenLineStr)
    return;
  if (n > i)
    n = i;
  for (j = i; j>=n; j--)
    dest[j+1] = dest[j];
  dest[n] = c;
}

int16_t FindEmptyLine()
{
  int16_t result,i;

  result = NumLines-1;
  while ((result > 0) && (lines[result][0] == 0))
    result--;

  if (result < NumLines-1)
  {
    result++;
    return result;
  }

  for (i = 0; i<NumLines; i++)
    if (lines[result][0] == 0)
      return result;

  return -1;
}

void AddQuLine(char *varName)
{
  int16_t i;

  i = FindEmptyLine();
  if (i >= 0)
  {
    for (int16_t j = 0; lines[i][j] = tolower(varName[j]); ++j);

    InsertChar(lines[i],' ',1000);
    InsertChar(lines[i],'=',1000);
    InsertChar(lines[i],' ',1000);
    InsertChar(lines[i],'?',1000);
  }
}

void SolveBtnClicked()
{
  switch (ImageMode) {
    case imSolved_edit: SetImageMode(imEdit_solved); break;
    case imEdit_solved: SetImageMode(imSolved_edit); break;
    default:
        ResetTable();
        ResetMatrix();
        is3Dplot = false;
        DeleteTrailingBlanks();
        DeleteAnswers();
        SaveFile();
        if (solve_strings() && has_fors && (has_qus || has_plot))
          SetImageMode(imSolved_edit); else
          DrawImageEditor();
        char buf[100];
        sprintf(buf,"%d",memtop);
  }
}

void SerialReceived(char c) {
  switch (c) {
    // not used in calculator
    case 'H': FormKeyClick(Key_HOME);    break;
    case 'E': FormKeyClick(Key_END);     break;
    case 'F': FormKeyClick(Key_DELETE);  break;

    // used in calculator
    case 'L': FormKeyClick(Key_LEFT);    break;
    case 'U': FormKeyClick(Key_UP);      break;
    case 'D': FormKeyClick(Key_DOWN);    break;
    case 'R': FormKeyClick(Key_RIGHT);   break;
    case 'B': FormKeyClick(Key_BACK);    break;
    case 'S': FormKeyClick(Key_EXECUTE); break;
    case '\r': FormKeyClick(c);          break;

    default:
      if ((c >= 'A') && (c <= 'Z')) {
      } else
      if ((c >= ' ') && (c <= 'z')) {
        FormKeyClick(c);
      }
  }
}
