/*
 * stdio.c - User level <stdio> library implementation.
 * 
 * Author     Alain Greiner (2016,2017,2018)
 *
 * Copyright (c) UPMC Sorbonne Universites
 *
 * This file is part of ALMOS-MKH.
 *
 * ALMOS-MKH 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 2.0 of the License.
 *
 * ALMOS-MKH 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 ALMOS-MKH; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <stdio.h>
#include <hal_shared_types.h>
#include <hal_user.h>
#include <syscalls_numbers.h>
#include <stdarg.h>
#include <almosmkh.h>
#include <unistd.h>
#include <fcntl.h>

////////////////////////////////////////////////////////////////////////////////////////
//          stdio library global variables
////////////////////////////////////////////////////////////////////////////////////////

// This user space array registers all FILE descriptors that can be simultaneously
// open by a given process. The structure FILE is defined in the <stdio.h> file. 

FILE open_file_array[MAX_OPEN_FILE_PER_PROCESS];  

////////////////////////////////////////////////////////////////////////////////////////
//          stdio library functions
////////////////////////////////////////////////////////////////////////////////////////

/////////////////////////////
int rename( const char * old,
            const char * new )
{
    return hal_user_syscall( SYS_RENAME,
                             (reg_t)old,
                             (reg_t)new, 0, 0 );    
}

////////////////////////////////////////////////////////////////////////////////////////
// This static function analyses the <format> and the <args> to build a formated
// string in the buffer defined by <string> and <length>.
// It does NOT add a terminating NUL character in the <string buffer>.
// If success, it returns the number of bytes actually copied in the string buffer.
// It returns -1 in case of illegal format, or if the formated string exceeds the
// the length argument. 
////////////////////////////////////////////////////////////////////////////////////////
static int xprintf( char         * string,
                    int            length,
                    const char   * format, 
                    va_list      * args ) 
{
    int ps = 0;    // index in the string buffer

#define TO_STREAM(x) do { string[ps] = (x); ps++; if(ps==length) return -1; } while(0);

xprintf_text:

    while ( *format != 0 ) 
    {

        if (*format == '%')   // copy argument to string
        {
            format++;
            goto xprintf_arguments;
        }
        else                  // copy one char to string
        {
            TO_STREAM( *format );
            format++;
        }
    }

    return ps;

xprintf_arguments:

    {
        char              buf[30];    // buffer to store the string for one number
        char *            pbuf;       // pointer on first char to display
        unsigned int      len = 0;    // number of char to display
        static const char HexaTab[] = "0123456789ABCDEF";
        unsigned int      i;
        
        // Ignore fields width and precision 
        for ( ; (*format >= '0' && *format <= '9') || (*format == '.') ; format++ );

        switch (*format) 
        {
            case ('c'):             // char conversion 
            {
                int val = va_arg( *args, int );
                buf[0] = val;
                pbuf   = buf;
                len    = 1;
                break;
            }
            case ('d'):             // decimal signed integer
            {
                int val = va_arg( *args, int );
                if (val < 0) 
                {
                    TO_STREAM( '-' );
                    val = -val;
                }
                for(i = 0; i < 10; i++) 
                {

                    buf[9 - i] = HexaTab[val % 10];
                    if (!(val /= 10)) break;
                }
                len =  i + 1;
                pbuf = &buf[9 - i];
                break;
            }
            case ('u'):             // decimal unsigned integer
            {
                unsigned int val = va_arg( *args, unsigned int );
                for(i = 0; i < 10; i++) 
                {
                    buf[9 - i] = HexaTab[val % 10];
                    if (!(val /= 10)) break;
                }
                len =  i + 1;
                pbuf = &buf[9 - i];
                break;
            }
            case ('x'):             // up to 8 digits hexa after "0x"
            case ('X'):             // exactly 8 digits hexa after "0x"
            {
                unsigned int val = va_arg( *args , unsigned int );
                TO_STREAM( '0' );
                TO_STREAM( 'x' );
                for(i = 0 ; i < 8 ; i++) 
                {
                    buf[7 - i] = HexaTab[val & 0xF];
                    if( (*format == 'x') && ((val >> 4) == 0) )  break;
                    val = val >> 4;
                }
                len =  i + 1;
                pbuf = &buf[7 - i];
                break;
            }
            case ('l'):             // up to 16 digits hexa after "0x"
            case ('L'):             // exactly 16 digits hexa after "0x"
            {
                unsigned long long val = ((unsigned long long)va_arg( *args, unsigned int)) |
                                         ((unsigned long long)va_arg( *args, unsigned int) << 32);
                TO_STREAM( '0' );
                TO_STREAM( 'x' );
                for(i = 0 ; i < 16 ; i++) 
                {
                    buf[15 - i] = HexaTab[val & 0xF];
                    if( (*format == 'l') && ((val >> 4) == 0) )  break;
                    val = val >> 4;
                }
                len =  i + 1;
                pbuf = &buf[15 - i];
                break;
            }
            case ('s'):             /* string */
            {
                char * str = va_arg( *args , char* );
                while (str[len]) { len++; }
                pbuf = str;
                break;
            }
            case ('f'):             // IEEE754 64 bits
                                    // integer part : up to 10 decimal digits
                                    // decimal part : 9 decimal digits
            {
                union
                {
                    double d;
                    unsigned long long ull;
                } val;
                
                val.d = va_arg( *args, double );
                
                unsigned long long mantisse;
                mantisse = val.ull & 0xFFFFFFFFFFFFFULL;    // mantisse
                
                unsigned int exp;
                exp = (unsigned int)((val.ull & 0x7FF0000000000000ULL) >> 52); // exp 

                if (exp == 0x7FF) // special values
                {
                    if (mantisse & 0xFFFFFFFFFFFFFULL)  // Not a Number
                    {
                        buf[0] = 'N';
                        buf[1] = 'a';
                        buf[2] = 'N';
                        len = 3;
                        pbuf = buf;
                    }
                    else                              // infinite
                    {
                        // inf
                        buf[0] = (val.ull & 0x8000000000000000ULL) ? '-' : '+';
                        buf[1] = 'i';
                        buf[2] = 'n';
                        buf[3] = 'f';
                        len = 4;
                        pbuf = buf;
                    }
                    break;
                }

                // display sign & analyse overflow 
                unsigned int overflow = 0;
                if (val.ull & 0x8000000000000000ULL)  // negative 
                {
                    TO_STREAM( '-' );
                    val.d = val.d * -1;
                    if( val.d < -9999999999.0) overflow = 1;
                }
                else                                  // positive
                {
                    TO_STREAM( '+' );
                    if( val.d > 9999999999.0) overflow = 1;
                }
                
                // check overflow caused by the 10.9 format
                if ( overflow )    
                {
                    buf[0] = 'o';
                    buf[1] = 'v';
                    buf[2] = 'r';
                    len = 3;
                    pbuf = buf;
                    break;
                }

                // compute integer & decimal parts
                unsigned int intp;                  // integer part
                unsigned int decp;                  // decimal part
                intp = (unsigned int)val.d;      
                val.d -= (double)intp;
                decp = (unsigned int)(val.d * 1000000000);
                
                // display decimal value in 10.9 format
                for(i = 0; i < 10; i++) 
                {
                    buf[9 - i] = HexaTab[intp % 10];
                    if (!(intp /= 10)) break;
                }
                pbuf = &buf[9 - i];
                len = i+11;
                buf[10] = '.';
                for(i = 0; i < 9; i++)
                {
                    buf[19 - i] = HexaTab[decp % 10];
                    decp /= 10;
                }
                break;
            }
            default:       // unsupported argument type
            {
                return -1;
            }
        }  // end switch on  argument type

        format++;

        // copy argument sub-string to the string buffer
        for( i = 0 ; i < len ; i++ )
        {
            TO_STREAM( pbuf[i] );
        }
        
        goto xprintf_text;
    }
} // end xprintf()

//////////////////////////////////////
int printf( const char * format, ... )
{
    char      string[4096];
    va_list   args;
    int       count;
    
    va_start( args, format );
    count = xprintf( string , 4095 , format , &args ); 
    va_end( args );

    if ( count < 0 ) 
    {
        display_string( "printf : xprintf failure" );
        return -1;
    }
    else 
    {
        string[count] = 0;

        return write( 1 , &string , count );
    }
}  // end printf()

///////////////////
int getchar( void )
{
    char byte;

    if ( read( 0 , &byte , 1 ) != 1 ) return 0;
    else                              return (int)byte; 
}

////////////////////
int putchar( int c )
{
    char byte = (char)c;

    if( write( 1 , &byte , 1 ) != 1 ) return 0;
    else                              return c; 
}

///////////////////////////////////////
int snprintf( char           * string,
              unsigned int     length,
              const char     * format, ... )
{
    va_list   args;
    int       count;

    va_start( args, format );
    count = xprintf( string , (int)length , format , &args ); 
    va_end( args );

    if( (count < 0) || (count == (int)length) )  // failure 
    {
        return -1;
    }
    else                                        // success
    {
        // add NUL character
        string[count] = 0;

        return count;
    }
}  // end snprintf()





////////////////////////////////////
FILE * fopen( const char * pathname,
              const char * mode )
{
    //TODO handle the "mode" argument
    if( mode != NULL )
    {
        printf("\n[%s] error : the mode argument must be NULL\n", __FUNCTION__ );
        return NULL;
    }

    // get a file descriptor from kernel
    int fd = open( pathname,
                   O_CREAT | O_RDWR, 
                   0 );

    if( fd < 0 )
    {
        printf("\n[%s] error : file <%s> not found\n", __FUNCTION__ , pathname );
        return NULL;
    }
    if( fd > MAX_OPEN_FILE_PER_PROCESS )
    {
        printf("\n[%s] error : not enough space for file <%s>\n", __FUNCTION__ , pathname );
        return NULL;
    }

    // register stream in open_file_array[]
    open_file_array[fd].fd  = fd;
    open_file_array[fd].key = VALID_OPEN_FILE;

    return &open_file_array[fd];

}  // end fopen()

///////////////////////////
int fclose( FILE * stream )
{
    // check stream valid
    if( stream->key != VALID_OPEN_FILE ) return EOF;

    // get file descriptor from stream pointer
    int fd = stream->fd;

    // remove stream from user open_file_array[]
    open_file_array[fd].key = 0;
    
    // close the kernel file descriptor
    if( close( fd ) )
    {
        printf("\n[%s] error : cannot close file %d\n", __FUNCTION__ , fd );
        return -1;
    }

    return 0;

}  // end fclose()

//////////////////////////
int fgetc( FILE * stream )
{
    // check stream valid
    if( stream->key != VALID_OPEN_FILE ) return EOF;

    // get file descriptor from stream pointer
    int fd = stream->fd;

    char byte;

    if ( read( fd , &byte , 1 ) != 1 ) return 0;
    else                               return (int)byte; 

}

/////////////////
int fputc( int c, 
           FILE * stream)
{
    // check stream valid
    if( stream->key != VALID_OPEN_FILE ) return EOF;

    // get file descriptor from stream pointer
    int fd = stream->fd;

    char byte = (char)c;

    if( write( fd , &byte , 1 ) != 1 ) return 0;
    else                               return c; 

}

//////////////////////////////////////////
unsigned int fread( void         * buffer,
                    unsigned int   size,
                    unsigned int   nitems,
                    FILE         * stream )
{
    // check stream valid
    if( stream->key != VALID_OPEN_FILE ) return EOF;

    // get file descriptor from stream pointer
    int fd = stream->fd;

    // compute nbytes
    int nbytes = size * nitems;

    if( hal_user_syscall( SYS_READ,
                          (reg_t)fd,
                          (reg_t)buffer,
                          (reg_t)nbytes, 0 ) == nbytes ) return nitems;
    else return 0;

}  // end fread() 

//////////////////////////////////////////
unsigned int fwrite( void         * buffer,
                     unsigned int   size,
                     unsigned int   nitems,
                     FILE         * stream )
{
    // check stream valid
    if( stream->key != VALID_OPEN_FILE ) return EOF;

    // get file descriptor from stream pointer
    int fd = stream->fd;

    // compute nbytes
    int nbytes = size * nitems;

    if( hal_user_syscall( SYS_WRITE,
                          (reg_t)fd,
                          (reg_t)buffer,
                          (reg_t)nbytes, 0 ) == nbytes ) return nitems;
    else return 0;

}  // end fwrite() 

//////////////////////////////////////////
unsigned int fseek( FILE         * stream,
                    unsigned int   offset,
                    int            whence )
{
    // check stream valid
    if( stream->key != VALID_OPEN_FILE ) return -1;

    // get file descriptor from stream pointer
    int fd = stream->fd;

    return( hal_user_syscall( SYS_LSEEK,
                              (reg_t)fd,
                              (reg_t)offset,
                              (reg_t)whence, 0 ) );
}  // end fseek() 

/////////////////////////////////
int fprintf( FILE       * stream,
             const char * format, ... )
{
    char               string[4096];
    va_list            args;
    int                count;
    int                writen;
    int                fd;
    
    // check stream valid
    if( stream->key != VALID_OPEN_FILE )
    {
        printf("\n[error in %s] stream %x non registered\n", __FUNCTION__, stream );
        return -1;
    }

    va_start( args, format );
    count = xprintf( string , 4095 , format , &args ); 
    va_end( args );

    // check format
    if ( count < 0 ) 
    {
        printf("\n[error in %s] unsupported format %s\n", __FUNCTION__, format );
        return -1;
    }

    // get file descriptor from file pointer
    fd = stream->fd;

    // set terminating NUL
    string[count] = 0;

    // copy string to file
    writen = write( fd , &string , count );

    // check write
    if(writen != count )
    {
        printf("\n[error in %s] cannot write to stream %s\n", __FUNCTION__, stream );
        return -1;
    }

    return writen;

}  // end fprintf()





