// main_tileg.c for MultiQuake on Cisco SX80.
// Nils Liaaen Corneliusen 2014
// https://www.ignorantus.com/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <getopt.h>
#include <math.h>

#include <tmc/cpus.h>
#include <tmc/task.h>

#include "quakedef.h"

/* To run on carbon target:

1. make and copy the executable:
[on mainframe]
> cd (...)product/carbon/misc/quake/
> make
> scp quake root@xx.xx.xx.xx:/tmp

2. copy pak files:
[on carbon]
> cd /tmp
> mkdir id1
[on mainframe]
> scp (...)product/carbon/misc/quake/id1/pak0.pak root@xx.xx.xx.xx:/tmp/id1/

3. stop stuff running on the box:
[on carbon]
> touch /config/noboot
> systemctl stop main gui task-migration
> systemctl stop fbviewer
> echo $$ > /sys/fs/cgroup/cpuset/tasks

4. start quake and fbviewer:
[on carbon]
> ./quake &
> fbviewer -f /tmp/quakescreen -s 1920x1080
*/

// default settings
#define DEFAULT_WIDTH        1920
#define DEFAULT_HEIGHT       1080
#define DEFAULT_CORES          -1
#define DEFAULT_FBFILE       "/tmp/quakescreen"
#define DEFAULT_MULTIPLY        1


int kb = -1;
int ir = -1;
int measure = 0;
int multiply = 0;
int coreid;
uint32_t *myscreen;
int mystride;
int cores        = DEFAULT_CORES;

extern int quake_main( int argc, char *argv[] );

// fix
int multiply;


static int parallelize( int count )
{
    cpu_set_t cpus;

    if( tmc_cpus_get_my_affinity( &cpus ) != 0 )
        tmc_task_die( "Failure in 'tmc_cpus_get_my_affinity()'." );

    if( tmc_cpus_count( &cpus ) < count )
        tmc_task_die( "Insufficient cpus (%d < %d).", tmc_cpus_count(&cpus), count );

    int watch_forked_children = tmc_task_watch_forked_children( 1 );

    int rank;
    for( rank = 1; rank < count; rank++ ) {
        pid_t child = fork();
        if( child < 0 )
            tmc_task_die( "Failure in 'fork()'." );
        if( child == 0 ) goto done;
    }
    rank = 0;

    tmc_task_watch_forked_children( watch_forked_children );

done:
    if( tmc_cpus_set_my_cpu( tmc_cpus_find_nth_cpu( &cpus, rank ) ) < 0 )
        tmc_task_die( "Failure in 'tmc_cpus_set_my_cpu()'." );

    return rank;
}


static void print_usage( const char *exec_name )
{
    printf( "Usage: %s [OPTIONS]\n\n", exec_name );
    printf( "Options:\n" );
    printf( "  -f file        FBViewer file to use, default %s\n",          DEFAULT_FBFILE );
    printf( "  -w cores       Number of cores to use, default %d (auto)\n", DEFAULT_CORES );
    printf( "  -s resolution  Resolution, default %dx%d\n",                 DEFAULT_WIDTH, DEFAULT_HEIGHT );
    printf( "  -c factor      Scale factor, default %d\n",                  DEFAULT_MULTIPLY );
    printf( "  -m             Measure mode: Same seed, no start delay\n" );

}

int main( int argc, char *argv[] )
{
    int quakeargc = 1;
    char *quakeargv[] = { "quake", NULL };

    char *fbfile  = DEFAULT_FBFILE;
    int  w        = DEFAULT_WIDTH;
    int  h        = DEFAULT_HEIGHT;
    int  mult     = DEFAULT_MULTIPLY;

    uint32_t *outbuf;
    int fd, rc;

    int opt;
    while( (opt = getopt( argc, argv, "hf:w:s:c:m" ) ) != -1 ) {
        switch( opt ) {
        case 'h':
            print_usage(argv[0]);
            return 0;
        case 'f': fbfile  = optarg;                             break;
        case 'w': cores   = atoi( optarg );                     break;
        case 's': rc      = sscanf(optarg, "%dx%d", &w, &h );   break;
        case 'c': mult    = atoi( optarg );                     break;
        case 'm': measure = 1;                                  break;

        default:
            break;
        }
    }

    if( cores == -1 ) {
        int maxrow, maxcol;
        maxrow = w/(SCREENWIDTH*mult);
        maxcol = h/(SCREENHEIGHT*mult);
        cores = maxrow*maxcol;
    }

    if( cores <= 0 ) {
        printf( "Yeah... good luck with that\n" );
        return 1;
    }

    int row = ceil( sqrt( cores ) );
    int col = ceil( (float)cores/(float)row );

    if( row*SCREENWIDTH*mult > w || col*SCREENHEIGHT*mult > h ) {
        printf( "Too many Quakes for given resolution and scale factor.\n" );
        return 1;
    }

    // create fbfile if needed
    struct stat st;
    rc = stat( fbfile, &st );
    if( rc < 0 || st.st_size < w*h*4 ) {
        printf( "Generating file %s\n", fbfile );
        fd = open( fbfile, O_RDWR|O_CREAT|O_TRUNC );
        if( fd < 0 ) {
            perror( "open" );
            return 1;
        }
        rc = lseek( fd, w*h*4-1, SEEK_SET );
        if( rc == -1 ) {
            perror( "lseek" );
            return 1;
        }
        rc = write( fd, "", 1 );
        if( rc != 1 ) {
            perror( "write" );
            return 1;
        }
        close( fd );
    }

    // start the processes
    coreid = parallelize( cores );

    // have different srand for each core to get random demo starting point
    srand( measure ? 0 : coreid );

    // map the screen buffer
    fd = open( fbfile, O_RDWR );
    if( fd < 0 ) {
        perror( "open" );
        return 1;
    }

    outbuf = mmap( 0, w*h*4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 );
    if( outbuf == MAP_FAILED ) {
        perror( "mmap" );
        return 1;
    }

    if( coreid == 0 ) {
        // clear output buffer
        memset( outbuf, 0, w*h*4 );

        // find an input device
        kb = open( "/dev/input/keyboard", O_RDONLY | O_NOCTTY | O_NONBLOCK );
        if( kb < 0 )
            ir = open( "/dev/input/ir", O_RDONLY | O_NOCTTY | O_NONBLOCK );

        printf( "Keyboard: %s\n", kb >= 0 ? "enabled" : "disabled" );
        printf( "IR:       %s\n", ir >= 0 ? "enabled" : "disabled" );
    }


    // figure out where to draw stuff
    int xoff = (w - SCREENWIDTH *mult*row)/2;
    int yoff = (h - SCREENHEIGHT*mult*col)/2;

    mystride = w;
    myscreen = outbuf + xoff + yoff*mystride +
               ((coreid%row)*SCREENWIDTH*mult) + ((coreid/row)*SCREENHEIGHT*mult*mystride);

    multiply = mult;

    // delay a bit at startup to keep things out of sync.
    if( !measure )
        usleep( 500000*coreid );

    // enough crap, start it!
    quake_main( quakeargc, quakeargv );

    return 0;
}
