/*
 * 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 <stdarg.h>
#include <almosmkh.h>
#include <unistd.h>
#include <fcntl.h>

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

FILE open_file_array[MAX_OPEN_FILE_PER_PROCESS];  // array of open files structures

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

///////////////////////////////////////////////////
static unsigned int xprintf( char         * string,
                             unsigned int   length,
                             const char   * format, 
                             va_list      * args ) 
{
    unsigned int ps = 0;    // write index to 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 display 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'):             // 32 bits hexadecimal 
            case ('l'):             // 64 bits hexadecimal 
            {
                unsigned int       imax;
                unsigned long long val;
                
                if ( *format == 'l' )   // 64 bits
                {
                    val = va_arg( *args, unsigned long long);
                    imax = 16;
                }
                else                    // 32 bits
                {
                    val = va_arg( *args, unsigned int);
                    imax = 8;
                }
                
                TO_STREAM( '0' );
                TO_STREAM( 'x' );
                
                for(i = 0; i < imax; i++) 
                {
                    buf[(imax-1) - i] = HexaTab[val % 16];
                    if (!(val /= 16))  break;
                }
                len =  i + 1;
                pbuf = &buf[(imax-1) - 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 to string
        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;
    unsigned int       count;
    
    va_start( args, format );
    count = xprintf( string , 4095 , format , &args ); 
    va_end( args );

    if ( count == -1 ) 
    {
        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;
    unsigned int       count;

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

    if( count < length ) 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[ERROR] in %s : 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[ERROR] in %s : file %s not found\n", __FUNCTION__ , pathname );
        return NULL;
    }
    if( fd > MAX_OPEN_FILE_PER_PROCESS )
    {
        printf("\n[ERROR] in %s : 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 open_file_array[]
    open_file_array[fd].key = 0;
    
    return close( fd );
}  // end fclose()

/////////////////////////////////
int fprintf( FILE       * stream,
             const char * format, ... )
{
    char               string[4096];
    va_list            args;
    unsigned int       count;
    int                fd;
    
    // check stream valid
    if( stream->key != VALID_OPEN_FILE ) return EOF;

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

    if ( count == -1 ) 
    {
        display_string( "fprintf : xprintf failure" );
        return -1;
    }
    else 
    {
        // get file descriptor from file pointer
        fd = stream->fd;
        
        string[count] = 0;

        return write( fd , &string , count );
    }
}  // end fprintf()





