/* files.c - Read/write stuff to files.
 *  Copyright (C) 2022 by Jed Marti
 *   This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see https://www.gnu.org/licenses/.
 */


#include <C8051F380.h>
#include <stdint.h>

#include "lfxmit.h"
#include "lex.h"
#include "config.h"
#include "fs.h"
#include "serial.h"
#include "strings.h"
#include "storage.h"



extern __xdata uint8_t     D[];
extern uint16_t            DREG;
extern __bit               errorThrown;
extern __xdata floatOrInt  IACC;
extern __xdata floatOrInt  IACCB;
extern __xdata floatOrInt  FACC;
extern __xdata SymtabEntry S[];
extern __bit               doit;

// Lexical interface.
extern __xdata uint8_t     infh;
extern __xdata uint8_t     ttype;
extern __xdata uint8_t     index;
extern __xdata char        STR[];
extern __xdata uint32_t    N;
extern __xdata floatOrInt  FACC;

// Trace.
extern __xdata uint16_t    PC;

// Local file handle for various purposes.
__xdata uint8_t wFH;

static int8_t getFileHandle(void)
/*     Common to OPEN/CREATE. Get the variable name (to return), check for =, the expression must be
 *   a string, set DREG, return the variable index. Errors on syntax and if name isn't a string.
 *   Here we're working with a lexically analyzed program, not true source.
 * Parameters: none
 * External References:
 *   VARIABLE: constant from lexical analysis.
 *   ASSIGN: constant from lexical analysis, for =.
 *   DREG: Offset into D space.
 *   errorThrown: Set to 1 if an error detected.
 *   DESC_STRING: constant - indicates a string value.
 * Returned values: The variable index into the symbol table, or -1 if there was an error.
 * Errors detected: missing variable, missing =, file name not a string.
 */
{
  uint8_t var;
  
  incPC();
  if (getPByte() != VARIABLE)
  {
    putstring("CREATE/OPEN missing variable\n");
    errorThrown = 1;
    return -1;
  }

  // The variable name.
  incPC();
  var = getPByte();

  // Looking for =.
  incPC();
  if (getPByte() != ASSIGN)
  {
    putstring("CREATE/OPEN missing =\n");
    errorThrown = 1;
    return -1;
  }

  // Looking for string value.
  incPC();
  parseExpression();
  if (errorThrown) return -1;

  DREG = popST();
  if (tag() != DESC_STRING)
  {
    putstring("File not string\n");
    errorThrown = 1;
    return -1;
  }
  return var;
}


  
void runCreateFile(void)
/*     The CREATE command has the syntax:
 *            create var = file
 *   The variable is set to the file handle if successful. Sets the handle if we're executing
 *   and opens an entry in the directory. If not, just parse and return. Errors happen if there's
 *   no files available or no space left in the directory or the flash. Here we're working with
 *   Here we're working with the I space, not source.
 * Parameters: none.
 * External References:
 *   errorThrown: set to 1 if an error detected.
 *   IA16: Global 4 byte variable used everywhere. Be careful.
 *   DREG: offset in D space.
 *   D[]: dynamic space.
 *   S[]: symbol table where we store the handle.
 * Errors detected: Can't create file, probably no space.
 */
{
  int8_t var;

  // Get an empty file handle, parse the var = string expression.
  var = getFileHandle();
  if (errorThrown) return;

  // Create the file or die.
  IACC.i16.i16a = createFile((char __xdata *) &D[DREG + 3]);
  if (IACC.i16.i16a == 255)
  {
    putstring("Can't create ");
    putstring((char *) &D[DREG + 3]);
    terpri();
    errorThrown = 1;
    return;
  }

  // Store the value in the symbol table.
  S[var].addr = newInt(IACC.i16.i16a);
}




void runOpenFile(void)
/*     Open an existing file for input and return the file handle. The syntax:
 *                 open var = file
 *   Sets the handle in the variable if the file can be opened and we're actually executing.
 *   An error signaled if it can't be opened for any reason. Here we're working on I space.
 * Parameters: none
 * External References:
 *   errorThrown: set to 1 if an error detected.
 *   IA16: Global 4 byte variable used everywhere. Be careful.
 *   DREG: offset in D space.
 *   D[]: dynamic space.
 *   S[]: symbol table where we store the handle.
 * Errors detected: Can't open a file,
 */
{
  int8_t var;

  var = getFileHandle();
  if (errorThrown) return;
  
  IACC.i16.i16a = openFile((char __xdata *) &D[DREG + 3]);
  if (IACC.i16.i16a == 255)
  {
    putstring("Can't open ");
    putstring((char *) &D[DREG + 3]);
    terpri();
    errorThrown = 1;
    return;
  }
  S[var].addr = newInt(IACC.i16.i16a);
}
  


static void wputch(uint8_t ch)
/*     Write a character to one of the files. The lowest level file output used here. Cannot
 *   be used to write to I2C.
 *     0: file handle 0.
 *     1: file handle 1.
 *     2: serial port.
 * Parameters:
 *   ch: The character to write.
 * External References:
 *   wFH: The selected file handle.
 * Returned values: none
 * Errors detected: bad file handle.
 */
{
  switch (wFH) {
  case file0In: putchFile(ch, 0); break;          // File 0.
  case file1In: putchFile(ch, 1); break;          // File 1.
  case serialIn: putch(ch); break;                // Serial port.
  default:
    putstring("WPUTCH bad fh\n");
    errorThrown = 1;
    return;
  }
}




static void wputuint32(uint32_t v)
/*     Write an unsigned 32 bit integer to the currently selected output port - one of the two open
 *   files or the serial port. 
 * Parameters:
 *   v: 32 bit unsigneed iunt to write.
 * External References:
 *   IACC: 32 bit temporary.
 * Returned values: none
 * Errors detected: none here.
 * Notes:  245 bytes, 4 bytes low RAM temporary. Definitely a pig.
 */
{
  __bit z = 0;
  __xdata uint8_t i;
  IACC.i = 1000000000;
  
  for (i = 0; i < 9; ++i)
  {
    if (z || v >= IACC.i)
    {
      wputch( v / IACC.i + '0');
      v %= IACC.i;
      z = 1;
    }
    IACC.i /= 10;
  }
  wputch(v + '0');
}




void putuint32(uint32_t v)
/*     Write an unsigned 32 bit number to the serial port. Used by traces, errors messages and
 *   so on.
 * Parameters:
 *   v: 32 bit unsigned int to do.
 * External References:
 *   wFH: File handle to write to (old value saved and restored).
 * Returned values: none
 * Errors detected: none
 */
{
  uint8_t ofh;
  ofh = wFH;
  wFH = 2;
  wputuint32(v);
  wFH = ofh;
}



static void wputint32(int32_t v)
/*     Signed 32 bit int to a file or the serial port.
 * Parameters:
 *   v: 32 bit signed integer. 
 * External References: none
 * Returned values: none
 * Errors detected: none
 */
{
  if (v < 0) { v = -v; wputch('-'); }
  wputuint32((uint32_t) v);
}



void putint32(int32_t v)
//     Write a signed 32 bit number to the serial port. Used by traces, errors messages and
// so on.
{
  wFH = 2;
  wputint32(v);
}



static void wputstring(char *v)
//     Write a string from a random place to the currently assigned port - one of the two files
// or the serial port.
{
  while (*v != 0) wputch(*v++);
}



static void wputByteVector(void)
//     Write a byte vector out - like a string but not zero terminated. Write it to one of the
// output files or the serial port.
{
  uint8_t sz;
  DREG += 3;
  sz = D[DREG];
  ++DREG;
  for (; sz != 0; --sz) wputch(D[DREG++]);
}



static void wputfloat(float v)
// Output a floating point number. Doesn't do exponentials.
{
  uint8_t dig;
  uint8_t i;
  if (v < 0.0f) { wputch('-'); v = -v; }
  wputuint32((uint32_t) v);
  wputch('.');
  for (i = 0; i < 5; ++i)
  {
    v -= (float)((uint32_t) v);
    v *= 10.0f;
    dig = (uint8_t) v;
    wputch(dig + '0');
    if (v == 0.0f) break;
  }
}



void putfloat(float v)
//     Write a floating point number to the serial port.
{
  wFH = 2;
  wputfloat(v);
}


void runWrite(void)
//     A write statement. These are written as ascii representations of their data structures
// except for ByteVectors that have their bytes written out as is. 
// write fh, val, val, ...
{
  incPC();
  parseExpression();
  if (errorThrown) return;

  if (doit)
  {
    DREG = popST();
    if (tag() != DESC_INT)
    {
      putstring("WRITE bad fh\n");
      errorThrown = 1;
      return;
    }
    getIACC();

    // I2C is treated separately.
    if (IACC.i8.b8a == i2cIn)
    {
      runWriteI2C();
      return;
    }
    
    wFH = IACC.i;
  }
  
 loop:
  if (getPByte() == COMMA)
  {
    wputch(' ');
    incPC();
    goto loop;
  }
  if (getPByte() == SEMICOLON)
  {
    wputch('\n');
    return;
  }
  parseExpression();
  if (errorThrown) return;
  if (doit)
  {
    DREG = popST();
    switch (tag()) {
    case DESC_INT:
      getIACC();
      wputint32(IACC.i);
      break;

    case DESC_FLOAT:
      getFACC();
      wputfloat(FACC.f);
      break;

    case DESC_STRING:
      wputstring((char *) &D[DREG + 3]);
      break;

    case DESC_BYTEVECTOR:
      wputByteVector();
      break;

    default:
      puthexbyte(tag());
      putstring(" bad type\n");
      errorThrown = 1;
      return;
    }
  }
  goto loop;
}



void runPutch(void)
{
  incPC();
  parseExpression();
  if (errorThrown) return;

  if (doit)
  {
    DREG = popST();
    if (tag() != DESC_INT)
    {
      putstring("PUTCH bad fh\n");
      errorThrown = 1;
      return;
    }
    getIACCB();

    if (getPByte() == COMMA) incPC();

    parseExpression();
    if (errorThrown) return;

    DREG = popST();
    if (tag() != DESC_INT)
    {
      badType("PUTCH");
      return;
    }
    getIACC();

    switch (IACCB.i8.b8a) {
    case 0:
    case 1:
      putchFile(IACC.i8.b8a, IACCB.i8.b8a);
      break;
    case 2:
      putch(IACC.i8.b8a);
      break;
    default:
      putstring("PUTCH bad fh\n");
      break;
    }
  }

  else
  {
    if (getPByte() == COMMA) incPC();
    parseExpression();
  }
}

    

void doReadch(void)
{
  
  // Punt if skipping or error detected.
  if (errorThrown || !doit) return;

  DREG = popST();
  if (tag() != DESC_INT)
  {
    putstring("READCH handle bad\n");
    errorThrown = 1;
    return;
  }
  getIACC();
 
  switch (IACC.i8.b8a) {
  case 0:
  case 1:
    pushST(newInt(getchFile(IACC.i8.b8a)));
    break;
  case 2:
    pushST(newInt(getch()));
    break;
  default:
    putstring("READCH bad handle\n");
    errorThrown = 1;
    break;
  }
}

    

void runRead(void)
//     Read stuff from a file. Variables can be assigned strings, floats or ints from the
// input stream. Previous values are lost.
//         read fh, variable, ..., variable;
// 
{
  __bit   sgn;
  uint8_t var;
  uint8_t subsc;
  
  incPC();
  parseExpression();
  if (errorThrown) return;
  if (doit)
  {
    DREG = popST();
    if (tag() != DESC_INT)
    {
      putstring("READ handle bad\n");
      errorThrown = 1;
      return;
    }
    getIACC();
    if (IACC.i < 0 || IACC.i > 2)
    {
      putstring("READ handle bad\n");
      errorThrown = 1;
      return;
    }

    // For LEX. Save its previous value.
    wFH = infh;
    infh = IACC.i8.b8a;

    // Clear input buffers.
    newLine();
    
  }

  incPC();

 loop:
  // Scanning the program. If we're at a semicolon, then restore previous lexical handle.
  if (getPByte() == SEMICOLON)
  {
    // Restore previous LEX handle.
    if (doit) infh = wFH;
    return;
  }

  // Ignore a comma.
  else if (getPByte() == COMMA)
  {
    incPC();
    goto loop;
  }

  // Must be a variable.
  else if (getPByte() == VARIABLE)
  {
    // Get the variable name.
    incPC();
    var = getPByte();
    //putstring("var = "); putstring(&D[S[var].pname + 3]); terpri();
    incPC();

    // See if we have an array here.
    if (getPByte() == LSQB)
    {
      incPC();
      parseExpression();
      if (errorThrown)
      {
        if (doit) infh = wFH;
        return;
      }
      if (getPByte() != RSQB)
      {
        putstring("Subscript missing ]\n");
        errorThrown = 1;
        if (doit) infh = wFH;
        return;
      }
      incPC();


      if (doit)
      {
        DREG = S[var].addr;
        if (tag() != DESC_BYTEVECTOR && tag() != DESC_FLOATVECTOR)
        {
          putstring("READ non-vector with []\n");
          errorThrown = 1;
          return;
        }

        // IACCB.i has subscript.
        subscriptCheck(var);
        if (errorThrown) return;

        //      putstring(" subscript is "); putint32(IACCB.i); terpri();
      subsc = IACCB.i;

      sgn = 0;
      sgtok:
        ntok();
        DREG = S[var].addr;
        switch (ttype) {
        case LEXNUM:
          if (tag() == DESC_BYTEVECTOR)
          {
            DREG += 4 + subsc;
            D[DREG] = (uint8_t) (sgn?-(int32_t) N:(int32_t) N);
          }
          else if (tag() == DESC_FLOATVECTOR)
          {
            DREG += 4 * (uint16_t) (subsc + 1);
            *((float*) &D[DREG]) = (float) (sgn?-(int32_t) N:(int32_t) N);
          }
          break;
        case LEXFLOAT:
          if (tag() == DESC_BYTEVECTOR)
          {
            DREG += 4 + (uint16_t) subsc;
            D[DREG] = (uint8_t) (sgn?-FACC.f:FACC.f);
          }
          else if (tag() == DESC_FLOATVECTOR)
          {
            DREG = DREG + 4 * (uint16_t) (subsc + 1);
            *((float*) &D[DREG]) = sgn?-FACC.f:FACC.f;
          }
          break;
        case LEXPUNCT:
          if (STR[0] == '-')
          {
            sgn = 1;
            goto sgtok;
          }
          else
          {
            putstring(STR);
            putstring(" invalid for READ\n");
            errorThrown = 1;
            infh = wFH;
            return;
          }
          break;
        }
      }
      goto loop;
    }
        
    if (doit)
    {
      //   putstring("  reading variable at "); puthex16(PC); terpri();
      sgn = 0;
    signtok:
      ntok();
      switch (ttype) {
      case LEXID:
      case LEXSTRING:
        S[var].addr = newStringSTR();
        break;
      case LEXNUM:
        //        putstring("  got N "); putuint32(N); terpri();
        S[var].addr = newInt(sgn?-(int32_t) N: (int32_t) N);
        break;
      case LEXFLOAT:
        S[var].addr = newFloat(sgn?-FACC.f:FACC.f);
        break;
      case LEXPUNCT:
        if (STR[0] == '-')
        {
          sgn = 1;
          goto signtok;
        }
        else
        {
          putstring(STR);
          putstring(" invalid for READ\n");
          errorThrown = 1;
          infh = wFH;
          return;
        }
        break;
      default:
        putstring("EOL or bad character\n");
        errorThrown = 1;
        infh = wFH;
        return;
      }
    }
    else incPC();
    goto loop;
  }

  else
  {
    putstring("READ syntax\n");
    errorThrown = 1;
    infh = wFH;
    return;
  }
  
  goto loop;
}




void runClose(void)
//     Close a file based on the index - closing serial is ignored.. Syntax is:
//  close <expression>
{

  incPC();
  parseExpression();
  if (errorThrown) return;

  if (doit)
  {
    DREG = popST();
    getIACC();
    switch (IACC.i8.b8a) {
    case 0: closeFile(0); break;
    case 1: closeFile(1); break;
    case 2: break;
    default:
      putstring("CLOSE bad handle\n");
      errorThrown = 1;
    }
  }
}



// EOF
