
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "callcheck.h"
#include "myname.h"
#include "token.h"
#include "fatal_error.h"
#include "wrappers.h"
#include "scanner.h"


#define READ_MODE "r"
#define WRITE_MODE "w"

#define INITIAL_BUFF_SIZE 1
#define INITIAL_BUFF_INDEX 0
#define INITIAL_LINENO 1

#define PRINTF_ERROR -1
#define TRUE 1
#define FALSE 0;
#define DOUBLE 2
#define EMPTY 0
#define NO_ERROR 0
#define NONZERO 1

#define CHANGE_SIGN -1

#define MAX_LINE_LEN 1000
#define INITIAL_LINE_LEN 0


/*
 * outputs token in the appropriate format, namely "lineno:status:string\n"
 */
void output_token (FILE *out, long long int lineno,
		   tokenerror_t error, char *buff) {
  int ret;

  if (strlen(buff) == EMPTY) {
    return;
  }

  ret = fprintf(out, "%lli:%s:%s\n", lineno, token_err2str(error), buff);
  scallcheck(ret != PRINTF_ERROR, "scanner error with I/O");
}

/* 
 * scans input for tokens, writes them to out with with line number
 * information and status
 */
void scanner (FILE *input, FILE *output) {
  int inchar, ret;
  long long int lineno = INITIAL_LINENO;
  size_t count = INITIAL_LINE_LEN;
  size_t buff_size = INITIAL_BUFF_SIZE;
  size_t buff_i = INITIAL_BUFF_INDEX;
  char *buff, *temp;

  /*
   * initialize buffer
   */
  buff = ra_malloc(sizeof(char) * buff_size);
  scallcheck(buff != NULL, "scanner error allocating memory");


  /*
   * I/O loop
   */
  while (!feof(input)) {
    /*
     * grows the buffer when necessary
     */
    if ((size_t) buff_i >= buff_size) {
      buff_size *= DOUBLE;
      temp = ra_realloc(buff, buff_size);
      scallcheck(temp != NULL, "scanner error allocating memory");
      buff = temp;
    }

    /*
     * reads a character
     */
    inchar = fgetc(input);
    scallcheck(ferror(input) == 0, "scanner error with I/O");
    if (feof(input)) {
      /*
       * used to signal the state machine to terminate the current token and line
       */
      inchar = '\n';

      /*
       * the line would be incorrectly incremented without this
       */
      lineno--;
    }

    if (inchar != '\n') {
      count++;
    }
    else {
      count = INITIAL_LINE_LEN;
    }

    /*
     * *** character handler {
     *
     * comment case
     */
    if (inchar == ';') {
      buff[buff_i] = '\0';
      output_token(output, lineno, E_NOERROR, buff);
      buff_i = EMPTY;

      /*
       * consumes the rest of the line because we ignore comments
       */
      while (!feof(input)) {
	inchar = fgetc(input);
     	scallcheck(ferror(input) == NO_ERROR, "scanner error with I/O");

	if (inchar == '\n') {
	  lineno++;
	  break;
	}
      }
    }

    /*
     * string case
     */
    else if (inchar == '"') {
      /*
       * outputs whatever we were working on before this because the string is another token
       */
      buff[buff_i] = '\0';
      output_token(output, lineno, E_NOERROR, buff);
      buff_i = EMPTY;

      /*
       * outputs a '"' token
       */
      output_token(output, lineno, E_NOERROR, "\"");

      /*
       * consumes the rest of the line, then outputs that as one token
       */
      while (!feof(input)) {
	inchar = fgetc(input);
	scallcheck(ferror(input) == NO_ERROR, "scanner error with I/O");
	buff[buff_i++] = (char) inchar;

	if (inchar == '\n') {
	  buff[--buff_i] = '\0';
	  output_token(output, lineno, E_NOERROR, buff);
	  buff_i = EMPTY;

	  lineno++;
	  break;
	}
      }
    }

    /*
     * block bounds case
     */
    else if (inchar == '[' || inchar == ']') {
      /*
       * outputs whatever we were working on before this
       */
      buff[buff_i] = '\0';
      output_token(output, lineno, E_NOERROR, buff);
      buff_i = EMPTY;

      /*
       * outputs this one character token
       */
      buff[buff_i++] = (char) inchar;
      buff[buff_i] = '\0';
      output_token(output, lineno, E_NOERROR, buff);
      buff_i = EMPTY;
    }

    /*
     * alphanumeric case
     */
    else if (isalnum(inchar)) {
      /* 
       * extends the buffer with this character
       */
      buff[buff_i++] = (char) inchar;
      /*
       * cannot unilaterally eat the rest of the line in this case
       */
    }

    /*
     * whitespace case
     */
    else if (isspace(inchar)) {
      /*
       * output whatever we were working on before this
       */
      buff[buff_i] = '\0';
      output_token(output, lineno, E_NOERROR, buff);
      buff_i = EMPTY;

      if (inchar == '\n') {
	lineno++;
      }
    }

    /*
     * character is illegal
     */
    else {
      /*
       * notify the parser we're bailing
       */
      output_token(output, lineno, E_ILLEGALCHAR, "ILLEGAL CHAR");

      exit(EXIT_FAILURE);
    }
    /*
     * *** }
     */
  }

  /*
   * notify the parser we have reached the end of input
   */
  output_token(output, lineno, E_EOF, "EOF");

  ret = fclose(input);
  scallcheck(ret == NO_ERROR, "scanner error closing input stream");
  ret = fclose(output);
  scallcheck(ret == NO_ERROR, "scanner error closing output stream");

  exit(EXIT_SUCCESS);
}

