///////////////////////////////////////////////////////////////////////////////////////
//  file   : windows.c  
//  date   : october 2020
//  author : Alain Greiner
///////////////////////////////////////////////////////////////////////////////////////
// This file describes the single thread interactive "windows" application,
// that uses the ALMOS-MKH specific system calls to access the Frame Buffer.
// It can create, destroy, and/or modify up to MAX_WINDOWS simultaneous windows.
// The supported commands are defined in the command[] array below:
// - CREATE  : create a new user accessible window. 
// - DELETE  : delete a previously created window.
// - MOVE    : move a previously created window in Frame Buffer.
// - DISPLAY : display a raw image stored on disk in a previously created window.
// - BUILD   : build a synthetic image in a previously  created window.
// - REFRESH : refresh a previously created window from the user buffer. 
// - STATE   : display current status for all created windows.
// - EXIT    / delete all created windows and exit the application.
///////////////////////////////////////////////////////////////////////////////////////

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <almosmkh.h>

#define MAX_BYTES      256     // max number of bytes for one single command
#define MAX_ARGS       8       // max number of arguments in one single command
#define MAX_WINDOWS    32      // max number of simultaneously created windows 

#define CMD_CREATE     0
#define CMD_DELETE     1
#define CMD_MOVE       2
#define CMD_DISPLAY    3
#define CMD_BUILD      4
#define CMD_REFRESH    5
#define CMD_FRONT      5
#define CMD_STATE      6
#define CMD_EXIT       7

#define DEBUG_MAIN     0
#define DEBUG_CREATE   0
#define DEBUG_DELETE   0
#define DEBUG_MOVE     0
#define DEBUG_DISPLAY  0
#define DEBUG_REFRESH  0
#define DEBUG_FRONT    0

typedef struct windows_cmd_s
{
    char * type;                       /*! command type                             */
    char * args;                       /*! command arguments                        */
}
windows_cmd_t;

//////////////////////////////////////////////////////////////////////////////////////
//    Global Variables
//////////////////////////////////////////////////////////////////////////////////////

// Array of supported commands : it must be kept consistent with 
// the CMD_*** define above, and with the decoding done in the main()

windows_cmd_t command[] =
{
	{ "create ", "lzero   pzero   nlines   npixels" },
	{ "delete ", "u_wid"                            },
	{ "move   ", "u_wid   lzero   pzero"            },
    { "display", "u_wid   path"                     },
    { "build"  , "u_wid"                            },
    { "refresh", "u_wid"                            },
	{ "front  ", "u_wid"                            },
    { "state  ", ""                                 },
	{ "exit   ", ""                                 }   };

// Frame Buffer physical parameters 

int    fbf_width;                     // number of pixels per line
int    fbf_height;                    // number of lines
int    fbf_type;                      // pixel encoding


// user created windows 

int    windows_count;                 // number of created windows 
int    k_wid[MAX_WINDOWS];            // array of kernel defined WID  
void * u_buf[MAX_WINDOWS];            // array of pointers on buffer in user space
int    lines[MAX_WINDOWS];            // array of windows height (number of lines)
int    pixels[MAX_WINDOWS];           // array of windows width (nunber of pixels)
int    l_zero[MAX_WINDOWS];           // array of uper left coner X coordinate
int    p_zero[MAX_WINDOWS];           // array of uper left coner X coordinate
    
// used for debug by display_string()

char   string[128];         


////////////////////////////////
void print_windows_state( void )
{
    printf("\n  u_wid  | k_wid | lzero | pzero | lines | pixels\n" );

    int i;

    for( i = 0 ; i< windows_count ; i ++ )
    {
        if( k_wid[i] != -1 )
        printf("  %d\t | %d\t | %d\t | %d\t | %d\t | %d\n",
        i , k_wid[i] , l_zero[i] , p_zero[i] , lines[i] , pixels[i] );
    }
}

//////////////////////////////
void cmd_create( int     argc,
                 char ** argv )
{
    int user_wid = 0;
    int found; 
    int i;

    // check argc
    if( argc != 5 )
    {
        printf("\n   usage: %s %s\n",
        command[CMD_CREATE].type , command[CMD_CREATE].args );
        return;
    }

    // get a free slot in user windows array
    for( i = 0 , found = 0 ; (i < MAX_WINDOWS) && (found == 0) ; i++ )
    {
        if( k_wid[i] == -1 )
        {
            user_wid = i;
            found    = 1;
        }
    }

    if( found == 0 )
    {
        printf("\n   error: too much windows\n");
        return;
    }

#if DEBUG_CREATE
printf("\n[%s] get a free user_wid ( %d )\n", __FUNCTION__, user_wid );
#endif

    // pointer on window buffer in user space
    void * buf;

    // get new window arguments
    int lzero   = atoi( argv[1] );
    int pzero   = atoi( argv[2] );
    int nlines  = atoi( argv[3] );
    int npixels = atoi( argv[4] );
    
    // call relevant FBF access function
    int kern_wid = fbf_create_window( lzero,
                                      pzero,
                                      nlines,
                                      npixels,
                                      &buf ); 
    if( kern_wid < 0 )
    {
        printf("\n   error: illegal arguments\n");
        return;
    }

#if DEBUG_CREATE
printf("\n[%s] get a kern_wid ( %d )\n", __FUNCTION__, user_wid );
#endif

    // register kernel WID and buffer pointer in relevant arrays
    windows_count++;
    k_wid[user_wid]  = kern_wid;
    u_buf[user_wid]  = buf;
    lines[user_wid]  = nlines;
    pixels[user_wid] = npixels;
    l_zero[user_wid] = lzero;
    p_zero[user_wid] = pzero;

#if DEBUG_CREATE
print_windows_state();
#endif

}  // end cmd_create()

//////////////////////////////////////////
void cmd_delete( int argc , char ** argv )
{
    if( argc != 2 )
    {
        printf("\n   usage: %s %s\n",
        command[CMD_DELETE].type , command[CMD_DELETE].args );
        return;
    }

    // get user_wid argument
    int user_wid = atoi( argv[1] );

    if( user_wid >= MAX_WINDOWS )
    {
        printf("\n   error: illegal user_id argument\n");
        return;
    }

    // get kern_wid from user_wid
    int kern_wid = k_wid[user_wid];

    // call relevant FBF access function
    if( fbf_delete_window( kern_wid ) )
    {
        printf("\n   error: undefined user_id argument\n");
        return;
    }

    // reset the k_wid and u_buf arrays
    k_wid[user_wid] = -1;
    u_buf[user_wid] = NULL;

#if DEBUG_DELETE
print_windows_state();
#endif

}  // end cmd_delete()

////////////////////////////////////////
void cmd_move( int argc , char ** argv )
{
    if( argc != 4 )
    {
        printf("\n   usage: %s %s\n",
        command[CMD_MOVE].type , command[CMD_MOVE].args );
        return;
    }

    // get arguments
    int user_wid = atoi( argv[1] );
    int lzero    = atoi( argv[2] );
    int pzero    = atoi( argv[3] );

    if( user_wid >= MAX_WINDOWS )
    {
        printf("\n   error: illegal user_id argument\n");
        return;
    }

    // get kern_wid from user_wid
    int kern_wid = k_wid[user_wid];

    // call relevant FBF access function
    if( fbf_move_window( kern_wid, lzero, pzero ) )
    {
        printf("\n   error: illegal lzero / pzero arguments\n");
        return;
    }

    // update moved window
    l_zero[user_wid] = lzero;
    p_zero[user_wid] = pzero;

#if DEBUG_MOVE
print_windows_state();
#endif

}  // end cmd_move()

///////////////////////////////////////////
void cmd_display( int argc , char ** argv )
{
    if( argc != 3 )
    {
        printf("\n   usage: %s %s\n",
        command[CMD_DISPLAY].type , command[CMD_DISPLAY].args );
        return;
    }

    // get arguments
    int    user_wid = atoi( argv[1] );
    char * path     =  argv[2];

#if DEBUG_DISPLAY
printf("\n[%s] enter for path <%s>\n", __FUNCTION__, path );
#endif

    if( user_wid >= MAX_WINDOWS )
    {
        printf("\n   error: illegal user_id argument\n");
        return;
    }
    else if( k_wid[user_wid] == -1 )
    {
        printf("\n   error: undefined user_id argument\n");
        return;
    }

    // open file
    int fd = open( path , O_RDONLY , 0 );

    if( fd < 0 )
    {
        printf("\n   error: cannot open file <%s>\n", path );
        return;
    }

    // load user buffer from file
    int nbytes = lines[user_wid] * pixels[user_wid];

    if( read( fd , u_buf[user_wid] , nbytes ) != nbytes )
    {
        printf("\n   error: cannot load file <%s>\n", path );
        return;
    }
 
#if DEBUG_DISPLAY
printf("\n[%s] file <%s> loaded in user buffer\n", __FUNCTION__, path );
#endif

    // close file
    close( fd );

    // activate window
    if( fbf_active_window( k_wid[user_wid], 1 ) )
    {
        printf("\n   error: cannot activate window <%d>\n", user_wid );
        return;
    }
 
#if DEBUG_DISPLAY
printf("\n[%s] window <%d> activated\n", __FUNCTION__, user_wid );
#endif

    // refresh window
    if( fbf_refresh_window( k_wid[user_wid], 0, lines[user_wid] ) )
    {
        printf("\n   error: cannot refresh user window <%d>\n", user_wid );
        return;
    }

#if DEBUG_DISPLAY
printf("\n[%s] window <%d> refreshed\n", __FUNCTION__, user_wid );
#endif

}  // end cmd_display()

/////////////////////////////////////////
void cmd_build( int argc , char ** argv )
{
    if( argc != 2 )
    {
        printf("\n   usage: %s %s\n",
        command[CMD_BUILD].type , command[CMD_BUILD].args );
        return;
    }

#if DEBUG_BUILD
printf("\n[%s] enter\n", __FUNCTION__ );
#endif

    // get user_wid argument
    int    user_wid = atoi( argv[1] );

    if( user_wid >= MAX_WINDOWS )
    {
        printf("\n   error: illegal user_id argument\n");
        return;
    }
    else if( k_wid[user_wid] == -1 )
    {
        printf("\n   error: undefined user_id argument\n");
        return;
    }

    // get window nlines, npixels, and buf 
    int    nlines  = lines[user_wid];
    int    npixels = pixels[user_wid];
    char * base    = u_buf[user_wid];

    // build image
    int    line;
    int    pixel;
    for( line = 0 ; line < nlines ; line++ )
    {
        for( pixel = 0 ;  pixel < npixels ; pixel++ )
        {
            char * buf = base + (line * npixels) + pixel;

            *buf = ( ((pixel <  (npixels>>1)) && (line <  (nlines>>1))) ||
                     ((pixel >= (npixels>>1)) && (line >= (nlines>>1))) ) ? 0xFF : 0;
        }
    }

#if DEBUG_BUILD
printf("\n[%s] image build in user buffer\n", __FUNCTION__ );
#endif

    // activate window
    if( fbf_active_window( k_wid[user_wid], 1 ) )
    {
        printf("\n   error: cannot activate window <%d>\n", user_wid );
        return;
    }
 
#if DEBUG_DISPLAY
printf("\n[%s] window <%d> activated\n", __FUNCTION__, user_wid );
#endif

    // refresh window
    if ( fbf_refresh_window( k_wid[user_wid], 0, lines[user_wid] ) )
    {
        printf("\n   error: cannot refresh user window <%d>\n", user_wid );
        return;
    }
 
#if DEBUG_DISPLAY
printf("\n[%s] window <%d> refreshed\n", __FUNCTION__, user_wid );
#endif

}   // enc cmd_build()

///////////////////////////////////////////
void cmd_refresh( int argc , char ** argv )
{
    if( argc != 2 )
    {
        printf("\n   usage: %s %s\n",
        command[CMD_DISPLAY].type , command[CMD_DISPLAY].args );
        return;
    }

    // get user_wid argument
    int    user_wid = atoi( argv[1] );

    if( user_wid >= MAX_WINDOWS )
    {
        printf("\n   error: illegal user_id argument\n");
        return;
    }
    else if( k_wid[user_wid] == -1 )
    {
        printf("\n   error: undefined user_id argument\n");
        return;
    }

    // refresh window
    if ( fbf_refresh_window( k_wid[user_wid] , 0 , lines[user_wid] ) )
    {
        printf("\n   error: cannot refresh user window <%d>\n", user_wid );
        return;
    }
}  // end cmd_refresh()

/////////////////////////////////////////
void cmd_front( int argc , char ** argv )
{
    if( argc != 2 )
    {
        printf("\n   usage: %s %s\n",
        command[CMD_FRONT].type , command[CMD_FRONT].args );
        return;
    }

#if DEBUG_FRONT
printf("\n[%s] enter\n", __FUNCTION__ );
#endif

    // get user_wid argument
    int user_wid = atoi( argv[1] );

    if( user_wid >= MAX_WINDOWS )
    {
        printf("\n   error: illegal user_id argument\n");
        return;
    }

    // get kern_wid from user_wid
    int kern_wid = k_wid[user_wid];

    // call relevant FBF access function
    if( fbf_front_window( kern_wid ) )
    {
        printf("\n   error: undefined user_id argument\n");
        return;
    }

#if DEBUG_FRONT
printf("\n[%s] exit\n", __FUNCTION__ );
#endif

}  // end cmd_delete()

//////////////////////
void cmd_state( void )
{
    print_windows_state() ;

}  // end cmd_state()

/////////////////////
void cmd_exit( void )
{
    int kern_wid;
    int i;

    // delete all created windows
    for( i = 0 ; i < MAX_WINDOWS ; i++ )
    {
        kern_wid = k_wid[i];

        if( kern_wid != -1 )  fbf_delete_window( kern_wid );
    }

    // exit application
    exit( 0 );

}  // end cmd_exit()


////////////////
int main( void )
{

    char            cmd[MAX_BYTES];      // string for one command
 	int             argc = 0;            // number of arguments in command (including type)
	char          * argv[MAX_ARGS];      // array or arguments for one command
	int             len;                 // command length in bytes (including NUL)
	int             i;

    // check frame buffer size
    fbf_get_config( &fbf_width , &fbf_height , &fbf_type );

    printf("\n[windows] start / FBF = %d lines * %d pixels\n", fbf_height, fbf_width );

    // initialize windows counter
    windows_count = 0;

    // initialize windows arrays
    for( i = 0 ; i < MAX_WINDOWS ; i++ )
    {
        k_wid[i] = -1;
        u_buf[i] = NULL;
    }

    // command interpreter : acquire and execute one command per iteration
    while( 1 )
    {
        // display prompt
        printf("\n[windows] ");

        // get command
        len = get_string( cmd , MAX_BYTES );

        if( len < 0 )
        {
            printf("\n\n[error in windows] cannot get command\n");
            exit( 0 );
        }
      
        // decompose command <=> build argc/argv
        argc = 0;
        for( i = 0 ; i < len ; i++ )
        {
            // convert SPACE to NUL
            if (cmd[i] == ' ') 
            {
			    cmd[i] = '\0';
		    }
            else if (i == 0 || cmd[i - 1] == '\0') 
            {
			    if (argc < MAX_ARGS) 
                {
				    argv[argc] = &cmd[i];
				    argc++;
			    }
		    }
	    }

#if DEBUG_MAIN
printf("\n[%s] command %s / argc %d\n", __FUNCTION__, argv[0], argc );
#endif
        // handle empty case
        if( argc == 0 ) continue;

        // analyse command
        if     ( strcmp( argv[0] , "create"  ) == 0 )  cmd_create ( argc , argv );
        else if( strcmp( argv[0] , "delete"  ) == 0 )  cmd_delete ( argc , argv );
        else if( strcmp( argv[0] , "move"    ) == 0 )  cmd_move   ( argc , argv );
        else if( strcmp( argv[0] , "display" ) == 0 )  cmd_display( argc , argv );
        else if( strcmp( argv[0] , "build"   ) == 0 )  cmd_build  ( argc , argv );
        else if( strcmp( argv[0] , "refresh" ) == 0 )  cmd_refresh( argc , argv );
        else if( strcmp( argv[0] , "front"   ) == 0 )  cmd_front  ( argc , argv );
        else if( strcmp( argv[0] , "state"   ) == 0 )  cmd_state  ( );
        else if( strcmp( argv[0] , "exit"    ) == 0 )  cmd_exit   ( );
        else
        {
            printf("  usage: %s %s\n"
                   "         %s %s\n"
                   "         %s %s\n"
                   "         %s %s\n"
                   "         %s %s\n"
                   "         %s %s\n"
                   "         %s %s\n"
                   "         %s %s\n"
                   "         %s %s\n",
            command[CMD_CREATE ].type , command[CMD_CREATE ].args,
            command[CMD_DELETE ].type , command[CMD_DELETE ].args,
            command[CMD_MOVE   ].type , command[CMD_MOVE   ].args,
            command[CMD_DISPLAY].type , command[CMD_DISPLAY].args,
            command[CMD_BUILD  ].type , command[CMD_BUILD  ].args,
            command[CMD_REFRESH].type , command[CMD_REFRESH].args,
            command[CMD_FRONT  ].type , command[CMD_FRONT  ].args,
            command[CMD_STATE  ].type , command[CMD_STATE  ].args,
            command[CMD_EXIT   ].type , command[CMD_EXIT   ].args );
        }
    }  // end while

    // avoid a warning
    return 0;

}   // end main()


