/*
  HI65 - a high-level Commodore 65 emulator
  Copyright (C) 2013-2023  Simone Gremmo
  Contact: devilmaster@email.com

  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
  http://www.gnu.org/licenses/gpl.txt
*/
#define OLDEXPRESSION

#include <allegro.h>
#include <cstdio>
#include <cmath>
#include <ctime>
#include "f8x8.h"
#include "gdef.h"
#include "pro.h"
#include "var.h"


void initvars(void) // VARIABLE INITIALIZATION
{
 ulong i,j,k;
 
 // program counter, expression counter, line counter, seconds counter
 pc=ec=lc=tickscapped=0;
  
 // line buffer
 for (i=0; i<80; i++)
  linebuffer[i]=0;

#ifdef SYSTEMRAM  
 // system RAM
 for (i=0; i<256; i++)
 {
  for (j=0; j<65536; j++)
   systemram[i][j]=0;
 }
#endif
 currentbank=128;
  
 // C65 variables
 resetc65vars();
 
 // color palette (red, green and blue components for each of the 256
 // allowed colors, for each of the two screens)
 for (i=16; i<256;i++)
 {
  for (j=0; j<3; j++)
   palette[0][i][j]=palette[1][i][j]=0;
 }
 paletterestore();
 
 // screen properties (width, height and bitplane depth for each of the two screens)
 for (i=0; i<2; i++)
 {
  for (j=0; j<3; j++)
   graphicscreen[i][j]=0;
 }
 
 // drawscreen and viewscreen
 DrawViewScreen[0]=DrawViewScreen[1]=0;
 openscreen=0;
 
 screentype=65;
 chperline=80;
 
 // status for each of the 256 logical file numbers (all closed)
 for (i=0; i<256; i++)
  filestatus[i]=CLOSED;
}

void resetc65vars(void)
{
 uint i,j,k;
 
 for (i=0; i<962; i++)
 {
  numvars[i][0]=0; // numeric variables
  for (j=0; j<255; j++)
  {
   for (k=0; k<255; k++)
    stringvars[j][i][k]=0; // string variables
  }
 }
 numvars[170][0]=65535; // BASIC error line
 numvars[176][0]=-1; // BASIC error number
 // stringvars[i][140][0] is used for DOS error messages
}

void skipspaces(void)
{
 while (program[pc]==SPACE)
  pc++;
}

void skipdataspaces(void)
{
 while (program[dc]==SPACE)
  dc++;
}

void skiparrayindex(void)
{
 while ((program[pc]>='0' && program[pc]<='9') || (program[pc]>='A' && program[pc]<='Z') || program[pc]=='(' || program[pc]==')')
  pc++;
}

uint locatevar(void)
{
 uint varpos;
 
 // The first character of a variable name is always a capital letter
 // (from A to Z). There are 37 possible variable names starting with a
 // letter (the single letter, 10 cases of the letter followed by a
 // number, 26 cases of the letter followed by another letter)
 varpos=(program[pc]-'A')*37;
 
 // If a second character in the variable name exists, it is either a
 // digit or a capital letter. Digits (from 0 to 9) are placed
 // immediately after the single letter; letters (from A to Z) are
 // placed after the digits
 if (program[pc+1]>='0' && program[pc+1]<='9')
  varpos+=(program[pc+1]-('0'-1));
 else if (program[pc+1]>='A' && program[pc+1]<='Z')
  varpos+=(program[pc+1]-('A'-11));

 return varpos;
}

// FIX BUG: when a colon immediately follows a variable name, it is
// considered part of the expression. It should not (a colon is only a
// separator) (CANNOT REPRODUCE)
// FIX BUG in the frontend: when VICE does not reside in the same disk or
// partition as the frontend, the frontend cannot find the paths for the
// computer screen icons anymore when the VICE path is set
double evaluateexpression(void)
{
 double val[3];
 uint aux;
 uchar token;
 uint destvarpos,array_index;
 uchar i,j;
 uchar expressionstring[255];
 double tempvar;
 uchar tempvarstring[255];
 int howmanydigits;

 if (!enable_muparser)
 {
  val[0]=val[1]=val[2]=token=0;
  // The "easy" implementation of this expression evaluator recognizes
  // expressions with at most 1 operator.
  // It does not recognize negative numbers, except as parameters of
  // mathematical functions ("-10" must be expressed as "0-10").
  // It can recognize a variable and extract its value.
  // It can recognize the mathematical functions: ATN COS LOG PEEK SIN SQR TAN
  // Examples of expressions it will recognize:
  // 5 5*4 5.2 5.2*4.8 COS(5.7) 5+A COS(A) COS(-10) COS(-A)
  aux=pc;
  // If we are not on the token for a mathematical function, we look for
  // the eventual operator
  
  if (program[pc]!=ASC &&
      program[pc]!=ATN &&
      program[pc]!=COS &&
      program[pc]!=EXP &&
      program[pc]!=INT &&
      program[pc]!=LEN &&
      program[pc]!=LOG &&
      program[pc]!=PEEK &&
      program[pc]!=RND &&
      program[pc]!=SIN &&
      program[pc]!=SQR &&
      program[pc]!=TAN)
  {
   if (inside_if_clause)
   {
    while (program[pc] &&
           program[pc]!=C65ADD &&
           program[pc]!=C65SUB &&
           program[pc]!=C65MUL &&
           program[pc]!=C65DIV &&
           program[pc]!=C65PWR &&
           program[pc]!=C65GT &&
           program[pc]!=C65EQU &&
           program[pc]!=C65LT)
     pc++;
   }
   else
   {
    while (program[pc] &&
           program[pc]!=C65ADD &&
           program[pc]!=C65SUB &&
           program[pc]!=C65MUL &&
           program[pc]!=C65DIV &&
           program[pc]!=C65PWR &&
           program[pc]!=TO &&
           program[pc]!=STEP &&
           program[pc]!=':')
     pc++;
   }
   // if (program[pc]) // if we are on an operator
   if ((!inside_if_clause && program[pc] && program[pc]!= TO && program[pc]!= STEP && program[pc]!=':') ||
        (inside_if_clause && program[pc] && program[pc]!= C65GT && program[pc]!= C65EQU && program[pc]!= C65LT))
   {
    token=program[pc];
    
    pc=aux; // we go back to the first digit of the first value
    
    val[0]=getnum(); // We store the first value into val[0]
    skipspaces(); pc++; skipspaces(); // We go past the operator
    val[1]=getnum(); // We store the second value into val[1]
    // We calculate the result of the operation
    switch (token)
    {
     case C65ADD: val[0]+=val[1]; break;
     case C65SUB: val[0]-=val[1]; break;
     case C65MUL: val[0]*=val[1]; break;
     case C65DIV: 
          if (val[1])
           val[0]/=val[1];
          else
           printerror(DIVISIONBYZERO);
          break;
     case C65PWR: val[0]=pow(val[0],val[1]); break;
    }
    // return val[0];
   }
   else // if there are no operators
   {
    pc=aux; // we return to the first digit of the first value 
            // (which can be either an immediate value or a variable:
            // "getnum" gets them both)
    // read the integer part of the value
    // read the decimal part of the value
    // put them together
    // return the obtained value
    val[0]=getnum(); // We store the integer part of the value into val[0]...
   }
  }
  else // If we are on the token for a mathematical function
  {
   token=program[pc];
   if (token!=ASC &&
       token!=LEN)
   {
    while (!((program[pc]>='0' && program[pc]<='9') || (program[pc]>='A' && program[pc]<='Z')))
     pc++; // We advance until we find the number or the variable
     // Now, mathematical functions can also get negative numbers as parameters
    if (program[pc-1]==C65SUB)
     pc--;
    val[0]=getnum(); 
   }

   switch (token)
   {
    case ASC: val[0]=Asc(); break;
    case ATN: val[0]=atan(val[0]); break;
    case COS: val[0]=cos(val[0]); break;
    case EXP: val[0]=pow(M_E,val[0]); break;
    case INT: val[0]=floor(val[0]); break;
    case LEN: val[0]=Len(); break;
    case LOG: val[0]=log(val[0]); break;
    case PEEK: val[0]=0xDEAD; break; // A full RAM map is not implemented yet, so we simulate the result of a PEEK
    case RND: val[0]=Rnd(val[0]); break;
    case SIN: val[0]=sin(val[0]); break;
    case SQR: val[0]=sqrt(val[0]); break;
    case TAN: val[0]=tan(val[0]); break;
   }
   // return val[0];
  }
  return val[0];
 }
 else // if enable_muparser is true
 {
  // gfxdemo1 without muParser: 0.116667 seconds
  // gfxdemo1 with muParser:   32.150000 seconds
  // with muParser, calculations are over 275 times slower!
  for (i=0; i<255; i++)
   expressionstring[i]=0;
  
  // First step: we translate the expression into a format that
  // MuParser can understand.
  // - Numbers and parentheses are copied without any change
  // - Tokens for arithmetic operations are replaced with arithmetic
  //   symbols
  // - Tokens for goniometric functions are replaced with the proper
  //   names of those functions
  // - Variable names are replaced with their values
  i=0;
  while (program[pc]!=':' &&
         program[pc]!=',' &&
         program[pc]!=TO &&
         program[pc]!=STEP &&
         program[pc]!=C65GT &&
         program[pc]!=C65EQU &&
         program[pc]!=C65LT &&
         pc<instruction_end_address)
  {
   switch (program[pc])
   {
    case ' ': pc++; break;
    case C65ADD: expressionstring[i++]='+'; pc++; break;
    case C65SUB: expressionstring[i++]='-'; pc++; break;
    case C65MUL: expressionstring[i++]='*'; pc++; break;
    case C65DIV: expressionstring[i++]='/'; pc++; break;
    case C65PWR: expressionstring[i++]='^'; pc++; break;
    case SIN:
         expressionstring[i++]='s';
         expressionstring[i++]='i';
         expressionstring[i++]='n';
         pc++; break;
    case COS:
         expressionstring[i++]='c';
         expressionstring[i++]='o';
         expressionstring[i++]='s';
         pc++; break;
    case TAN:
         expressionstring[i++]='t';
         expressionstring[i++]='a';
         expressionstring[i++]='n';
         pc++; break;
    case ATN:
         expressionstring[i++]='a';
         expressionstring[i++]='t';
         expressionstring[i++]='a';
         expressionstring[i++]='n';
         pc++; break;
    case LEN:
         val[0]=Len(); // execute Len
         for(j=0; j<255; j++)
          tempvarstring[i]=0;
         aux=sprintf((char *)tempvarstring, "%f",val[0]); // transform the numeric result into an array of characters
         for (j=0; tempvarstring[j]!=0 && tempvarstring[j]!='.'; j++) // copy it into expressionstring
          expressionstring[i++]=tempvarstring[j];
         break;
    case LOG: // the token LOG executes a NATURAL logarithm operation in the C65
         expressionstring[i++]='l';
         expressionstring[i++]='n';
         pc++; break;
    case SQR:
         expressionstring[i++]='s';
         expressionstring[i++]='q';
         expressionstring[i++]='r';
         expressionstring[i++]='t';
         pc++; break;
    case ABSC65:
         expressionstring[i++]='a';
         expressionstring[i++]='b';
         expressionstring[i++]='s';
         pc++; break;
    case EXP:
         expressionstring[i++]='e';
         expressionstring[i++]='x';
         expressionstring[i++]='p';
         pc++; break;
    default: if ((program[pc]>='0' && program[pc]<='9') || program[pc]=='(' || program[pc]==')')
              expressionstring[i++]=program[pc++];
             else if (program[pc]>='A' && program[pc]<='Z')
             {
              expressionstring[i++]='('; // We enclose the variable value into parentheses
              destvarpos=locatevar();
              array_index=getarrayindex();
              tempvar=numvars[destvarpos][array_index];
              howmanydigits=sprintf((char *)tempvarstring,"%f",tempvar);
              for (j=0; tempvarstring[j]; j++)
               expressionstring[i++]=tempvarstring[j];
              while ((program[pc]>='A' && program[pc]<='Z') || (program[pc]>='0' && program[pc]<='9'))
               pc++;
              expressionstring[i++]=')'; // Closed parenthesis enclosing the variable value
              // At this point, we might be on an open parenthesis,
              // meaning that we are on an array element. We advance
              // until we find the corresponding closed parenthesis.
              if (program[pc]=='(')
              {
               while (program[pc]!=')')
                pc++;
               pc++;
              }
             }
             break;
   }
  }

  // Second step: we evaluate the obtained expression and return the
  // result
  try
  {
   p.SetExpr((char *)expressionstring);
   return p.Eval();
  }
  catch (mu::Parser::exception_type &e)
  {
   printerror (MUPARSERERROR+e.GetCode());
  }  
 }
}

uchar evaluatecondition(void)
{
 uchar cmp;
 // Now we are either on the comparison sign (=,<,>) or on the FIRST
 // character of a comparison sign (<>,<=,>=). We must discriminate
 // the cases.
 cmp=program[pc++]; // single character, either =,<,>
 if (cmp!=C65EQU) // double character
 {
  if (cmp==C65LT && program[pc]==C65GT)
   cmp='d'; // D_ifferent
  else if (cmp==C65LT && program[pc]==C65EQU)
   cmp='l'; // L_ess or equal
  else if (cmp==C65GT && program[pc]==C65EQU)
   cmp='g'; // G_reater or equal
 }
 
 // If we found a double-character comparison sign, we are still on the
 // second character. We must skip it.
 if (cmp=='d' || cmp=='l' || cmp=='g')
  pc++;
  
 return cmp;
}

uchar conditionistrue_num(double first, uchar comp, double second)
{
 switch (comp)
 {
  case C65EQU:
       if (first==second)
        return 1;
       else
        return 0;
       break;
  case C65LT:
       if (first<second)
        return 1;
       else
        return 0;
       break;
  case C65GT:
       if (first>second)
        return 1;
       else
        return 0;
       break;
  case 'd':
       if (first!=second)
        return 1;
       else
        return 0;
       break;
  case 'l':
       if (first<=second)
        return 1;
       else
        return 0;
       break;
  case 'g':
       if (first>=second)
        return 1;
       else
        return 0;
       break;
 } 
}

uchar conditionistrue_string(uchar comp)
{
 uint i; 
 for (i=0; i<256; i++)
 {
  if (stringtemp[i][0]!=stringtemp[i][1])
   break; // We break out of the loop as soon as we find a different character
 }
 
 // If the strings are equal, the counter will contain the value 255,
 // otherwise its value will be lower 
 if (comp==C65EQU)
 {
  if (i<255)
   return 0;
  else
   return 1;
 }
 else
 {
  if (i<255)
   return 1;
  else
   return 0;
 }
}

double getnum(void)
{
 double decimal;
 uchar found_decimal_point=0;
 uchar found_minus=0;
 ulong divide_by=1;
 double result=0;
 ulong aux;
 // This function is called when the program counter is on the first
 // character of a number written in ASCII or the name of a variable.
 // It returns the number as a double.
 
 // If we're on a minus token, we must ONLY register it if we're in a
 // mathematical function with parentheses. So, we go backwards until we either
 // find an open parenthesis or a C65EQU token.
 if (program[pc]==C65SUB)
 {
  aux=pc;
  while (program[pc]!='(' && program[pc]!=C65EQU)
   pc--;
  if (program[pc]=='(')
  {
   found_minus=1;
   pc=aux+1;
  }
  else
   pc=aux;
 }
 
 if (program[pc]>='0' && program[pc]<='9')
 {
  // This is executed when the program counter is on the first
  // character of a number written in ASCII
  do
  {
   if (program[pc]=='.')
    found_decimal_point=1;
   else
   {
    if (!found_decimal_point)
    {
     result=result*10+program[pc];
     result-='0';
    }
    else
    {
     divide_by*=10;
     decimal=program[pc];
     decimal-='0';
     decimal/=divide_by;
     result+=decimal;
    }
   }
   pc++;
  }
  while ((program[pc]>='0' && program[pc]<='9') || program[pc]=='.');
 }
 else if (program[pc]>='A' && program[pc]<='Z')
 {
  // This is executed when the program counter is on the first
  // character of a variable name  
  result=numvars[locatevar()][getarrayindex()];
  // After reading the variable, we push the program counter forward
  // until it is no longer on an ASCII digit or letter (the second
  // character of a variable name can be a digit). We also skip
  // parentheses (used to delimit an array index).
  // NOTE: a condition like "program[pc]>='0' && program[pc]<='Z'"
  // is WRONG because it would consider the characters : ; < = > ? @
  // like a variable name
  skiparrayindex();
 }
 
 // After the number there may or may not be spaces, followed in any
 // case by a comma or a decimal point, after which there
 // may or may not be more spaces
 while (program[pc]==SPACE || program[pc]==',' || program[pc]=='.')
  pc++;
  
 // Now the program counter is on the next element of the instruction
 // (or the null character marking the end of the instruction) so we
 // can return the result
 if (found_minus)
  result=-result;
 return result;
}

uint getarrayindex(void)
{
 uint aux;
 uint result=0;
 // This function is called when the program counter is on the first
 // character of a variable name. First, it determines whether it's
 // just a variable or an array (if the name is followed by a round
 // bracket, it's an array, otherwise it's not). If the variable is
 // an array, it moves to the first character of the index, which can
 // be a number written in ASCII or the name of a variable.
 // At the end, it either returns 0 (if it's just a variable) or an
 // unsigned integer (if it's an array).
 
 aux=pc;
 
 // Now we are on the first character of a variable name. We move
 // past the whole name.
 while ((program[pc]>='0' && program[pc]<='9') || (program[pc]>='A' && program[pc]<='Z') || program[pc]=='$')
  pc++;
  
 // There may or may not be spaces, so we skip them
 skipspaces();
 
 // If now we are on an open parenthesis, we found an array
 if (program[pc]!='(')
 {
  pc=aux;
  return 0;
 }
 else
 {
  // We are on an open parenthesis, so we advance past it
  pc++;
  if (program[pc]>='0' && program[pc]<='9')
  {
   // This is executed when the program counter is on the first
   // character of a number written in ASCII
   do
   {
    // The index of an array cannot be a non-integer, so, if we find
    // a decimal point, we signal a syntax error
    if (program[pc]=='.')
    {
     printerror(SYNTAXERROR);
     return(0xFF); // invalid value
    }
    else
    {
     result=result*10+program[pc];
     result-='0';
    }
    pc++;
   }
   while (program[pc]>='0' && program[pc]<='9');
  }
  else if (program[pc]>='A' && program[pc]<='Z')
  {
   // This is executed when the program counter is on the first
   // character of a variable name (we do not allow an array element
   // to serve as an index for another array)
   result=(uint)numvars[locatevar()][0];

   while ((program[pc]>='0' && program[pc]<='9') || (program[pc]>='A' && program[pc]<='Z'))
    pc++;
  }

  // An array index cannot be greater than 255 or negative, so,
  // if it is, we signal a syntax error
  if (result>255 || result<0)
  {
   printerror(SYNTAXERROR);
   return(0xFF); // invalid value
  }
  else
  {
   pc=aux;
   return result;
  }
 }
}

double getdatanum(void)
{
 double decimal;
 uchar found_decimal_point=0;
 ulong divide_by=1;
 double result=0;
 // This function is called when the data counter is on the first
 // character of a number written in ASCII.
 // It returns the number as a double.
 do
 {
  if (program[dc]=='.')
   found_decimal_point=1;
  else
  {
   if (!found_decimal_point)
   {
    result=result*10+program[dc];
    result-='0';
   }
   else
   {
    divide_by*=10;
    decimal=program[dc];
    decimal-='0';
    decimal/=divide_by;
    result+=decimal;
   }
  }
  dc++;
 }
 while ((program[dc]>='0' && program[dc]<='9') || program[dc]=='.');
 
 // After the number there may or may not be spaces, followed in any
 // case by a comma or a decimal point, after which there
 // may or may not be more spaces
 while (program[dc]==SPACE || program[dc]==',' || program[dc]=='.')
  dc++;
  
 // Now the data counter is on the next element of the instruction
 // (or the null character marking the end of the instruction) so we
 // car return the result
 
 return result;
}

void initdatacounter(void)
{
 dc=1;

 do
 {
  // We push the data counter to the end of the current instruction
  while (program[dc])
   dc++;

  // We move 5 bytes forward, to "land" past the line number
  dc+=5;

  // At this point there may be a number of spaces, so we skip them,
  // to "land" on the instruction token
  skipdataspaces();
 }
 while (!(program[dc]==DATA || (!program[dc] && !program[dc+1] && !program[dc+2])));

 // Now we may be on the first DATA token or at the end of the program.
 // If we are on the first DATA token, we increase the data counter and
 // skip the eventual spaces until we reach the first data element 
 if (program[dc]==DATA)
 {
  dc++;
  skipdataspaces();
 }
}

void ticker(void)
{
 time_t rawtime;
 struct tm* timeinfo;
 char hhmmssbuffer[255];
 uchar i;
 
 numvars[TI][0]++;
 tickscapped++;
 if (tickscapped==60) // this happens once per second
 {
  tickscapped=0;
  
  // The system time (hours, minutes, seconds) is transferred to TI$.
  // This reproduces the behavior of a C65 with a real-time clock.
  time(&rawtime);
  timeinfo=localtime(&rawtime);
  strftime (hhmmssbuffer,255,"%H%M%S",timeinfo);
  for (i=0; i<255; i++)
   stringvars[i][TI][0]=hhmmssbuffer[i];
 }
}
END_OF_FUNCTION(ticker)
