/* arithmetic.c - arithmetic functions, 32 bit, float.
 *  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 "serial.h"
#include "storage.h"
#include "strings.h"

extern __xdata uint16_t ST[];
extern uint16_t         DREG;
extern uint8_t          SPTR;
extern __xdata uint8_t  D[];

extern __xdata char       STR[];         // For working with strings.
extern __xdata floatOrInt IACC;          // Some accumulators.
extern __xdata floatOrInt IACCB;
extern __xdata floatOrInt FACC;
extern __xdata floatOrInt FACCB;

// Set to 1 to indicate thrown error.
extern __bit            errorThrown;

// Set to 1 when we're executing, but 0 when just scanning.
extern __bit            doit;


void badType(char *oper)
// A common error so show it, set the error flag.
{
  putstring(oper);
  putstring(" bad type\n");
  errorThrown = 1;
  return;
}


  
void plus2(void)
// Add the two items on top the stack. Also includes string operations. We do integers,
// single-precision floats, and strings.
{
  char __xdata *s;
  char __xdata *r;
  
  // Not executing, just return.
  if (!doit) return;

  // RHS value.
  DREG = popST();
  switch (tag()) {

  // ??? + Int.
  case DESC_INT:
    getIACC();

    DREG = popST();
    switch (tag()) {

    // Int + Int.
    case DESC_INT:
      getIACCB();
      pushST(newInt(IACCB.i + IACC.i));
      break;

    // Float + Int.
    case DESC_FLOAT:
      getFACC();
      FACCB.f = (float) IACC.i;
      FACC.f += FACCB.f;
      pushST(newFloat(FACC.f));
      break;

    // String + Int.
    case DESC_STRING:
      s = strCpy(STR, &D[DREG + 3]);
      int2string(s);
      pushST(newStringSTR());
      break;
      
    default:
      badType("??? + int");
    }
    break;

  case DESC_FLOAT:
    getFACC();
    DREG = popST();
    switch (tag()) {
    case DESC_INT:
      getIACC();
      pushST(newFloat((float) IACC.i + FACC.f));
      break;
      
    case DESC_FLOAT:
      getFACCB();
      pushST(newFloat(FACCB.f + FACC.f));
      break;

    // string + float
    case DESC_STRING:
      s = strCpy(STR, &D[DREG + 3]);
      float2string(s);
      pushST(newStringSTR());
      break;
      
    default:
      badType("??? + float");
    }
    break;

  // ??? + string.
  case DESC_STRING:
    r = &D[DREG + 3];
    DREG = popST();
    switch (tag()) {

    // Int + string
    case DESC_INT:
      getIACC();
      s = int2string(STR);
      strCpy(s, r);
      pushST(newStringSTR());
      break;

    // Float + string
    case DESC_FLOAT:
      getFACC();
      s = float2string(STR);
      strCpy(s, r);
      pushST(newStringSTR());
      break;

    // String + String.
    case DESC_STRING:
      s = &D[DREG + 3];
      s = strCpy(STR, s);
      strCpy(s, r);
      pushST(newStringSTR());
      break;
      
    default:
      badType("string + ???");
    }
    break;

  default:
    badType("??? + ???");
  }
}



void difference(void)
// Subtract two values, floats predominate.
{
  // Not executing, just return.
  if (!doit) return;
  
  // RHS value.
  DREG = popST();
  switch (tag()) {

  // ??? - Integer.
  case DESC_INT:
    getIACC();

    DREG = popST();
    switch (tag()) {
    case DESC_INT:
      getIACCB();
      pushST(newInt(IACCB.i - IACC.i));
      break;

    case DESC_FLOAT:
      getFACC();
      pushST(newFloat(FACC.f - (float) IACC.i));
      break;

    default:
      badType("??? - int");
    }
    break;

  case DESC_FLOAT:
    getFACC();
    DREG = popST();
    switch (tag()) {
    case DESC_INT:
      getIACC();
      pushST(newFloat((float) IACC.i - FACC.f));
      break;
    case DESC_FLOAT:
      getFACCB();
      pushST(newFloat(FACCB.f - FACC.f));
      break;
    default:
      badType("??? - float");
    }
    break;

  default:
    badType("??? - ???");
  }
}



void times2(void)
// Multiply two values, floats take precedence.
{
  // Not executing, just return.
  if (!doit) return;
  
  // RHS value.
  DREG = popST();
  switch (tag()) {

  // ??? * Integer.
  case DESC_INT:
    getIACC();

    DREG = popST();
    switch (tag()) {
    case DESC_INT:
      getIACCB();
      pushST(newInt(IACCB.i * IACC.i));
      break;

    case DESC_FLOAT:
      getFACC();
      pushST(newFloat(FACC.f * (float) IACC.i));
      break;

    default:
      badType("??? * int");
    }
    break;

  case DESC_FLOAT:
    getFACC();
    DREG = popST();
    switch (tag()) {
    case DESC_INT:
      getIACC();
      pushST(newFloat((float) IACC.i * FACC.f));
      break;
    case DESC_FLOAT:
      getFACCB();
      pushST(newFloat(FACCB.f * FACC.f));
      break;
    default:
      badType("??? * float");
    }
    break;

  default:
    badType("??? * ???");
  }
}





void quotient(void)
// Divide two values, check for division by 0.
{
  // Not executing, just return.
  if (!doit) return;
  
  // RHS value.
  DREG = popST();
  switch (tag()) {

  // ??? / Integer.
  case DESC_INT:
    getIACC();

    DREG = popST();
    switch (tag()) {
    case DESC_INT:
      getIACCB();
      if (IACC.i == 0)
      {
        putstring("Int / 0\n");
        errorThrown = 1;
        return;
      }
      pushST(newInt(IACCB.i / IACC.i));
      break;

    case DESC_FLOAT:
      getFACC();
      if (IACC.i == 0)
      {
        putstring("Int / 0\n");
        errorThrown = 1;
        return;
      }
      pushST(newFloat(FACC.f / (float) IACC.i));
      break;

    default:
      badType("??? / int");
    }
    break;

  case DESC_FLOAT:
    getFACC();
    DREG = popST();
    switch (tag()) {
    case DESC_INT:
      getIACC();
      if (FACC.f == 0.0f)
      {
        putstring("Float / 0\n");
        errorThrown = 1;
        return;
      }
      pushST(newFloat((float) IACC.i / FACC.f));
      break;
    case DESC_FLOAT:
      getFACCB();
      if (FACC.f == 0.0f)
      {
        putstring("Float / 0.0\n");
        errorThrown = 1;
        return;
      }
      pushST(newFloat(FACCB.f / FACC.f));
      break;
    default:
      badType("??? / float");
    }
    break;

  default:
    badType("??? / ???");
  }
}



void remainder(void)
// Remainder of integers only.
{
  // Not executing, just return.
  if (!doit) return;
  
  DREG = popST();
  if (tag() == DESC_INT)
  {
    getIACC();
    DREG = popST();
    if (tag() == DESC_INT)
    {
      if (IACC.i == 0)
      {
        putstring("Int % 0\n");
        errorThrown = 1;
        return;
      }
      getIACCB();
      pushST(newInt(IACCB.i % IACC.i));
      return;
    }
    else
    {
      badType("Int %");
      return;
    }
  }
  badType("??? % ???\n");
}




void minus(void)
// Make negative the top of the stack.
{
  // Not executing, just return.
  if (!doit) return;
  
  DREG = popST();
  switch (tag()) {
  case DESC_INT:
    getIACC();
    IACC.i = -IACC.i;
    pushST(newInt(IACC.i));
    break;

  case DESC_FLOAT:
    getFACC();
    FACC.f = -FACC.f;
    pushST(newFloat(FACC.f));
    break;

  default:
    badType("-");
  }
}



// EOF
