/*! \file libnachos.c
 *  \brief Functions of our library, for user programs.
 *
 * This library only provides some  usefull functions for
 * programming.
 * 
 * -----------------------------------------------------
 * This file is part of the Nachos-RiscV distribution
 * Copyright (c) 2022 University of Rennes 1.
 * 
 * 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, version 3.
 *
 * 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 
 * (see see <http://www.gnu.org/licenses/>).
 * -----------------------------------------------------
 */

#include "libnachos.h"
#include <stdarg.h>
#include <stdint.h>

//----------------------------------------------------------------------
// threadStart()
/*!	Makes a thread execute a function or program. This function
//      is static, it is called internally by library function threadCreate
//      and should not be called directly by user programs. The interest
//      of this function is to be able to terminate threads correctly,
//      even when the thread to be terminated does not explicitly call
//      Exit. threadStart provides the mechanism by which Exit
//      is called automatically
//
//	\param func is the identificator of the function to execute.
*/
//----------------------------------------------------------------------

static void threadStart(uint64_t func)
{
    VoidNoArgFunctionPtr func2;
    func2=(VoidNoArgFunctionPtr)func;
    // Call the function that actually contains the thread code
    (*func2)();
    // Call exit, such that there is no return using an empty stack
    Exit(0);    
}

//----------------------------------------------------------------------
// threadCreate()
/*!	 Creates a thread and makes it execute a function.
//
//      NB : instead of directly executing the required function,
//           function threadStart is called so as to ensure
//           that the thread will properly exit
//      This function must be called instead of calling directly
//      the system call newThread
//
//      \param name the name of the thread (for debugging purpose)
//	\param func is the address of the function to execute.
*/
//----------------------------------------------------------------------
ThreadId threadCreate(char *debug_name, VoidNoArgFunctionPtr func) 
{ 
    return newThread(debug_name, (uint64_t)threadStart,(uint64_t)func);
}

//----------------------------------------------------------------------
// n_strcmp()
/*!	String comparison
//
//	\param s1 is the first string,
//	\param s2 is the second one.
//	\return an integer greater than, equal to, or less than 0,
//	  if the first string is greater than, equal to, or less than 
//	  the the second string. 
*/
//----------------------------------------------------------------------
int n_strcmp(const char *s1, const char *s2)
{
  int comparaison;
  int fini=0;
  int i=0;
  while(!fini) {
    if ((s1[i]==0)&&(s2[i]==0)) {
      fini=1;
      comparaison=0;
    }
    if (s1[i]<s2[i]) {
      fini=1;
      comparaison=-1;
    }
    if(s1[i]>s2[i]) {
      fini=1;
      comparaison=1;
    }
    i++;
  }
  return comparaison;
}

//----------------------------------------------------------------------
// n_strcpy()
/*!	String copy
//
//	\param dst is where the string is to be copied,
//	\param src is where the string to copy is.
//	\return dst, if the copy successes, 0 otherwise
*/
//----------------------------------------------------------------------
char *n_strcpy(char *dst, const char *src)
{
  int i=0;
  int fini=0;
  if ((dst!=0)&&(src!=0)) {
    while(fini==0) {
      if(src[i]=='\0') fini=1;
      dst[i]=src[i];
      i++;
    }
    return dst;
  }
  else return 0;
}

//----------------------------------------------------------------------
// n_strlen()
/*!	Gives the number of bytes in a string, not including the 
//	terminating null character.
//
//	\param c is a pointer onto a string.
//	\return the length of the string.
*/
//----------------------------------------------------------------------
size_t n_strlen(const char *s)
{
  size_t i=0;
  while (s[i] != 0) i++;
  return i;
}


//----------------------------------------------------------------------
// n_strcat()
/*!	Appends a copy of a string, including null character, to the end
//	of another string. Enough memory has to be available in the
//      destination string.
//
//	\param dst is a pointer onto the string where the other string
//	will be appended.
//	\param src is the string to append.
//	\return the pointer string dst.
*/
//----------------------------------------------------------------------
char *n_strcat(char *dst, const char *src)
{
  int i,j,k;
  i=(int)n_strlen(dst);
  j=(int)n_strlen(src);  
  for(k=i;k<=j+i;k++) {
      dst[k]=src[k-i];
  }
  return dst;
}


//----------------------------------------------------------------------
// n_toupper()
/*!	Gives the upper-case letter corresponding to the lower-case
//	letter passed as parameter.
//
//	\param c is the ASCII code of the letter to transform.
//	\return the corresponding upper-case letter 
*/
//----------------------------------------------------------------------
int n_toupper(int c)
{
  if((c>='a')&&(c<='z'))
	return c+('A'-'a');
  else return c;
}

//----------------------------------------------------------------------
// n_tolower()
/*!	Gives the lower-case letter corresponding to the upper-case
//	letter passed as parameter
//
//	\param c is the ASCII code of the letter to transform.
//	\return the corresponding lower-case letter 
*/
//----------------------------------------------------------------------
int n_tolower(int c)
{
  if((c<='Z')&&(c>='A'))
	return c+('a'-'A');
  else return c;
}

//----------------------------------------------------------------------
// n_atoi()
/*!	String to integer conversion.
//
//	\param c is a pointer onto a string.
//	\return the corresponding value 
*/
//----------------------------------------------------------------------
int n_atoi(const char *str)
{
  int i=0;
  int fini=0;
  int val=0;
  int negative = 0;
  if (str[i] == '-') {
    negative = 1; i=1;
  }
  while(!fini)
    {
      if(str[i]==0 || str[i]<'0' || str[i]>'9')
	fini=1;
      else 
	{
	  val*=10;
	  val+=str[i]-'0';
	  i++;
	}
    }
  if (negative) return(-val); else return val;
}

//----------------------------------------------------------------------
// n_memcmp()
/*!	Memory comparison.
//
//	\param s1 is the first memory area,
//	\param s2 is the second memory area.
//      \param n size in bytes of the area to be compared.
//	\return an integer less than, equal to, or greater than 0, 
//	according as s1 is lexicographically less than, equal to, 
//	or greater than s2 when taken to be unsigned characters.
//
*/
//----------------------------------------------------------------------
int n_memcmp(const void *s1, const void *s2, size_t n)
{
  unsigned char* c1=(unsigned char*)s1;
  unsigned char* c2=(unsigned char*)s2;

  int comparaison=0;
  int fini=0;
  int i=0;
  while ((!fini)&&(i<n)) {
    if (c1[i]<c2[i]) {
      fini=1;
      comparaison=-1;
    }
    if (c1[i]>c2[i]) {
      fini=1;
      comparaison=1;
    }
    i++;
  }
  return comparaison;
}

//----------------------------------------------------------------------
// n_memcpy()
/*!	Memory copy.
//
//	\param s1 is where the elements are to be copied,
//	\param s2 is the memory area to copy.
//      \param n size in bytes of the area to be copied.
//	\return the memory area where the copy has been done.
*/
//----------------------------------------------------------------------
void *n_memcpy(void *s1, const void *s2, size_t n)
{
 
  unsigned char* c1=(unsigned char*)s1;
  unsigned char* c2=(unsigned char*)s2;
  
  int i=0;
  if ((c1!=0)&&(c2!=0)) {
    while(i<n) {
      c1[i]=c2[i];
      i++;
    }
    return (void *)c1;
  }
  else return 0;
}


//----------------------------------------------------------------------
// n_memset()
/*!	Sets the first n bytes of a memory area to a value (converted to 
//	an unsigned char).
//
//	\param s is the memory area to transform,
//	\param c is the value wanted,
//	\param n is the number of bytes to put at c.
//	\return s.
*/
//----------------------------------------------------------------------
void *n_memset(void *s, int c, size_t n)
{
  unsigned char* c1=(unsigned char*)s;
  int i;
  for (i=0;i<n;i++) {
    c1[i]=c;
  }
  return (void *)c1;
}

//----------------------------------------------------------------------
// n_dumpmem()
/*!	Dumps on the string the n first bytes of a memory area
//      (used for debugging)
//
//	\param addr address of the memory area
//	\param len number of bytes to be dumped
*/
//----------------------------------------------------------------------
void n_dumpmem(char *addr, int len)
{
#define TOHEX(x) \
  ({ char __x = (x); if(__x < 10) __x+='0'; else __x='a'+(__x-10) ; __x; })

  int i;
  for (i = 0 ; i < len ; i++) {
    char s[3];
    if ((i%16) == 0)
      n_printf("%x\t", (unsigned long)&addr[i]);
    else if ((i%8) == 0)
      n_printf("   ");
    s[0] = TOHEX((addr[i] >> 4) & 0xf);
    s[1] = TOHEX(addr[i] & 0xf);
    s[2] = '\0';
    n_printf("%s ", s);
    if ((((i+1)%16) == 0) || (i == len-1))
      n_printf("\n");
  }
}

#define PUTCHAR(carac) \
  do { \
    if (result < len-1) *buff++ = carac;\
    result++; \
  } while (0)


//----------------------------------------------------------------------
// n_vsnprintf()
/*!	Build a string according to a specified format (internal function)
//
//	Nachos vsnprintf accepts:
//		%c to print a character,
//		%s, to print a string,
//		%d, to print an integer,
//		%x, to print an integer in hexa
//              %lx %ld same for 64-bit values
//              %f, to print a floating point value
//
//      \param buff the destination buffer to generate the string to
//      \param len the size of buff, determines the number max of
//        characters copied to buff (taking the final \0 into account)
//	\param format the string to parse
//	\param ap parameters to print
//
//      \return the number of characters formatted (NOT including \0),
//        that is, the number of characters that would have been written
//        to the buffer if it were large enough. -1 on error.
*/
//----------------------------------------------------------------------
static int n_vsnprintf(char *buff, int len, const char *format, va_list ap)
{
  int i, result;

  if (!buff || !format || (len < 0)) {
    return -1;
  }
  result = 0;

  for (i=0 ; format[i] != '\0' ; i++) {
    switch (format[i]) {
    case '%':
      i++;
      switch(format[i]) {
      case '%': {
	PUTCHAR('%');
	break;
      }
      case 'i':
      case'd': {
	int integer = (int) va_arg(ap,int);
	int cpt2 = 0;
	char buff_int[11];
	if (integer<0) {PUTCHAR('-');
	}
	do {	
	  int m10 = integer%10;
	  m10 = (m10 < 0)? -m10:m10;
	  buff_int[cpt2++]=(char)('0'+ m10);
	  integer=integer/10;
	} while(integer!=0);
	for (cpt2 = cpt2 - 1 ; cpt2 >= 0 ; cpt2--) {
	  PUTCHAR(buff_int[cpt2]);
	}
	break;
      }
      case 'l': {
	i++;
	switch(format[i]) {
	case 'd': {
	  long longer = va_arg(ap,long);
	  int cpt2 = 0;
	  char buff_long[20];
	  if (longer<0) {
	    PUTCHAR('-');
	  }
	  do {	
	    int m10 = longer%10;				
	    m10 = (m10 < 0)? -m10:m10;
	    buff_long[cpt2++]=(char)('0'+ m10);
	    longer=longer/10;
	  } while(longer!=0);
	  for (cpt2 = cpt2 - 1 ; cpt2 >= 0 ; cpt2--) {
	    PUTCHAR(buff_long[cpt2]);
	  }
	  break;
	}
	case 'x': {
	  uint64_t hexa = va_arg(ap,long);
	  uint64_t nb;
	  uint32_t i, had_nonzero = 0;
	  for (i=0 ; i < 16 ; i++) {
	    nb = (hexa << (i*4));
	    nb = (nb >> 60);
	    nb = nb & 0x000000000000000f;
	    // Skip the leading zeros
	    if (nb == 0) {
	      if (had_nonzero) {
		PUTCHAR((uint8_t)'0');
	      }
	    }
	    else {
	      had_nonzero = 1;
	      if (nb < 10)
		PUTCHAR((uint8_t)'0'+(uint8_t)nb);
	      else
		PUTCHAR((uint8_t)'a'+(uint8_t)(nb-10));
	    }
	  }
	  if (! had_nonzero)
	    PUTCHAR((uint8_t)'0');
	  break;
	}
	default: {
	  PUTCHAR('%');		
	  PUTCHAR('l');
	  PUTCHAR(format[i]);
	  break;
	}
	}
	
	break;
      }
      case 'c': {
	int value = va_arg(ap,int);
	PUTCHAR((char)value);
	break;
      }
      case 's': {
	char *string = va_arg(ap,char *);
	if (! string)
	  string = "(null)";
	for( ; *string != '\0' ; string++)
	  PUTCHAR(*string);
	break;
      }
      case 'x':  {
	uint32_t hexa = va_arg(ap,int);
	uint32_t nb;
	uint32_t i, had_nonzero = 0;
	for (i=0 ; i < 8 ; i++) {
	  nb = (hexa << (i*4));
	  nb = (nb >> 28);
	  nb = nb & 0x0000000f;
	  // Skip the leading zeros
	  if (nb == 0) {
	    if (had_nonzero)
	      PUTCHAR((uint8_t)'0');
	  }
	  else {
	    had_nonzero = 1;
	    if (nb < 10)
	      PUTCHAR((uint8_t)'0'+(uint8_t)nb);
	    else
	      PUTCHAR((uint8_t)'a'+(uint8_t)(nb-10));
	  }
	}
	if (! had_nonzero)
	  PUTCHAR((uint8_t)'0');
	break;
      }
	/*case 'f': {
	// Very simple routine to print floats as xxxx.yyyyy
	// Not very good (unable to print large numbers)
	// If anyone wants to re-write it, feel free ...
	double f = (double) va_arg(ap,double);
	int cpt2, j;
	char buff_float[200];
	long ient,idec;
	if (f<0) {
	  PUTCHAR('-');
	  f = -f;
	}
	ient = (int)f;
	// 100000 = print 5 digits max
	idec = (int)((f - ((double)ient))*100000);
	// Round up
	if ( f - ((double)ient) - ((double)idec)/100000.0 >= 0.5E-5)
	  idec ++;
	cpt2 = 0;
	// Print digits after the '.'
	for (j=0 ; j<5 ; j++) {
	  buff_float[cpt2++]=(char)('0'+(idec%10));
	  idec=idec/10;
	}
	buff_float[cpt2++] = '.';
	// Print digits before the '.'
	do {
	  buff_float[cpt2++]=(char)('0'+ (ient%10));
	  ient=ient/10;
	} while (ient!=0);
	for(j = cpt2 - 1 ; j >= 0 ; j--)
	  PUTCHAR(buff_float[j]);
	break;
      }
	*/
      default:
	PUTCHAR('%');
	PUTCHAR(format[i]);
      }
      break;
    default:
      PUTCHAR(format[i]);
    }
  }
  *buff = '\0';
  return result;
}

//----------------------------------------------------------------------
// n_snprintf()
/*!	Build a string according to a specified format
//
//	Nachos snprintf accepts:
//		%c to print a character,
//		%s, to print a string,
//		%d, to print an integer,
//		%x, to print a string in hexa
//              %f, to print a floating point value
//		
//      \param buff the destination buffer to generate the string to
//      \param len the size of buff, determines the number max of
//        characters copied to buff (taking the final \0 into account)
//	\param format the string to parse
//	\param ... the (variable number of) arguments
//
//      \return the number of characters formatted (NOT including \0),
//        that is, the number of characters that would have been written
//        to the buffer if it were large enough. -1 on error.
*/
//----------------------------------------------------------------------
int n_snprintf(char * buff, int len, const char *format, ...){
  va_list ap;
  va_start(ap, format);
  len = n_vsnprintf(buff, len, format, ap);
  va_end(ap);
  return len;
}

//----------------------------------------------------------------------
// n_printf()
/*!	Print to the standard output parameters.
//
//	Nachos printf accepts:
//		%c to print a character,
//		%s, to print a string,
//		%d, to print an integer,
//		%x, to print a string in hexa
//              %ld, %lx, same for 64-bit values
//              %f, to print a floating point value
//		
//	\param parameters to print,
//	\param type of print.
*/
//----------------------------------------------------------------------
void n_printf(const char *format, ...){

  va_list ap;
  char buff[200];
  int len;

  va_start(ap, format);
  len = n_vsnprintf(buff, sizeof(buff), format, ap);
  va_end(ap);

  if (len >= sizeof(buff)) {
    len = sizeof(buff) - 1;
  }
  if (len > 0) {
    Write(buff,len,CONSOLE_OUTPUT);
  }
}

//----------------------------------------------------------------------
// n_read_int()
/*!	
// Very basic minimalist read integer function, no error
// checking...
*/
//----------------------------------------------------------------------
int n_read_int(void) {
  char buff[200];
  Read(buff,200,CONSOLE_INPUT);
  return n_atoi(buff);
}