// This raytracer is based on code from: https://github.com/shodruky-rhyammer/blobubska
// Copyright note below:
/*
Copyright (c) 2014, Shodruky Rhyammer
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

  Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

  Redistributions in binary form must reproduce the above copyright notice, this
  list of conditions and the following disclaimer in the documentation and/or
  other materials provided with the distribution.

  Neither the name of the copyright holders nor the names of its
  contributors may be used to endorse or promote products derived from
  this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

// Ray tracing for dummies: https://www.cs.unc.edu/~rademach/xroads-RT/RTarticle.html

// To compile:
// tile-cc -O3 -std=c99 ray.c -o ray -ltmc -lm -lpthread -static

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>
#include <stdint.h>
#include <limits.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include <getopt.h>

#include <postinc.h>
#include <arch/cycle.h>
#include <tmc/cpus.h>
#include <tmc/task.h>
#include <tmc/udn.h>


#include "ray.h"

trace_global *tglob;

#define WIDTH    1920
#define HEIGHT   1080
#define BLOCKX     16
#define BLOCKY     16

#define CPUFREQ 1200000000


static inline float randf( void )
{
  return (float)rand() / (float)RAND_MAX;
}

//#define BG_R64 0x4040404040404040
//#define BG_G64 0x4040404040404040
//#define BG_B64 0x4040404040404040
#define BG_R64 0x0000000000000000
#define BG_G64 0x0000000000000000
#define BG_B64 0x0000000000000000

static inline target_t trace_ray( obj_t *obj, ray_t ray )
{
    target_t rt = { FARDIST, NOHIT };

    for( int i = 0; i < OBJNUM; i++ ) {
        vec3_t v = vec3_sub( ray.pos, obj[i].pos );
        float b = -dot( v, ray.dir );
        float d = b * b - norm2( v ) + obj[i].radsq;
        if( d <= 0.0f ) continue;
        float t0 = b - fast_sqrt( d );
        int r = t0 >= rt.dist || t0 <= 0.0f;
        rt.dist = r ? rt.dist : t0;
        rt.hit =  r ? rt.hit  : i;
    }
    return rt;
}

static inline int cnv00toff( float val )
{
  union fi32_u {
    float f;
    int32_t i;
  };
  union fi32_u x;
  x.f = val + 256.0f;
  return (x.i>>15)&0xff;
}

static void render( trace_global * restrict tg, uint8_t * restrict rgb, int xstart, int ystart )
{
    int rowstride = (tg->stride*3)/8;
    uint64_t * restrict outr   = (uint64_t *)(rgb + ystart*3*tg->stride                + xstart);
    uint64_t * restrict outg   = (uint64_t *)(rgb + ystart*3*tg->stride +   tg->stride + xstart);
    uint64_t * restrict outb   = (uint64_t *)(rgb + ystart*3*tg->stride + 2*tg->stride + xstart);

    vec3_t startpos = vec3_set( tg->swidth   * -0.5f - tg->eye.x + xstart*tg->ax,
                                tg->sheight  *  0.5f - tg->eye.y - ystart*tg->ay - tg->ayc,
                                -tg->eye.z );

    // just clear block if no hits on corners
    vec3_t spos;
    spos       = startpos;          vec3_t ul  = normalize( spos );
    spos.y    -= tg->ay*(BLOCKY-1); vec3_t bl  = normalize( spos );
    spos.x    += tg->ax*(BLOCKX-1); vec3_t br  = normalize( spos );
    spos.y    += tg->ay*(BLOCKY-1); vec3_t ur  = normalize( spos );

    int i;
    for( i = 0; i < OBJNUM; i++ ) {
        vec3_t  v  = tg->obj[i].poseye; // v  = vec3_sub(ray.pos, tg->obj[i].pos);
        float  rv  = tg->obj[i].rv;     // rv = -norm2( v ) + tg->obj[i].radsq;
        if( dotsq( v, ul ) + rv > 0.0f || dotsq( v, bl ) + rv > 0.0f ||
            dotsq( v, br ) + rv > 0.0f || dotsq( v, ur ) + rv > 0.0f    ) break;
    }

    if( i == OBJNUM ) {
        for( int yy = 0; yy < BLOCKY; yy++ ) {
            // adjust this if BLOCKX is changed
            outr[0] = BG_R64; outr[1] = BG_R64; outr += rowstride;
            outg[0] = BG_R64; outg[1] = BG_G64; outg += rowstride;
            outb[0] = BG_R64; outb[1] = BG_B64; outb += rowstride;
        }
        return;
    }

    spos.y = startpos.y;

    obj_t light = tg->light;

    uint64_t r0 = 0, g0 = 0, b0 = 0;

    for( int y = 0; y < BLOCKY; y++, spos.y -= tg->ay ) {

        float yyzz = spos.y*spos.y + spos.z*spos.z;

        spos.x = startpos.x;

        for( int x = 0; x < BLOCKX; x++, spos.x += tg->ax ) {

            ray_t ray;
            ray.pos = tg->eye;
//            ray.dir = normalize( spos );
            ray.dir = vec3_scale( spos, fast_inv_sqrt( yyzz + spos.x*spos.x ) );

            vec3_t col = { 0.0f, 0.0f, 0.0f };

            for( int j = 0; j < MAXREF; j++ ) {

                target_t rt = trace_ray( tg->obj, ray );
                if( rt.hit == NOHIT ) break;

                vec3_t p = vec3_add( ray.pos, vec3_scale( ray.dir, rt.dist ) );
                vec3_t n = normalize( vec3_sub( p, tg->obj[rt.hit].pos ) );

                ray.pos = vec3_add( p, vec3_scale( n, EPSILON ) );

                vec3_t lv = vec3_sub( light.pos, p );
                vec3_t l  = normalize( lv );
                float diffuse  = dot( n, l );
                float specular = dot( ray.dir, vec3_sub( l, vec3_scale( n, 2.0f * diffuse ) ) );
                diffuse  = max( diffuse,  0.0f );
                specular = max( specular, 0.0f );
                specular = power_spec( specular );

                if( __builtin_expect( tg->obj[rt.hit].shadow, 0 ) ) {
                    ray_t shadow_ray;
                    shadow_ray.dir = l;
                    shadow_ray.pos = ray.pos;
                    target_t rts = trace_ray( tg->obj, shadow_ray );
                    int shadow = rts.dist < norm( lv );
                    diffuse  = shadow ? diffuse * 0.5f : diffuse;
                    specular = shadow ? 0.0f           : specular;
                }

                col = vec3_add( col, vec3_add( vec3_scale( light.col,           specular ),
                                               vec3_scale( tg->obj[rt.hit].col, diffuse  ) ) );

                if( !tg->obj[rt.hit].reflect ) break;

                ray.dir = vec3_sub( ray.dir, vec3_scale( n, dot( ray.dir, n ) * 2.0f ) );
            } // j refl

            r0 |= cnv00toff( min(255.0f, col.x*255.0f) ); r0 = __insn_rotli( r0, 56 );
            g0 |= cnv00toff( min(255.0f, col.y*255.0f) ); g0 = __insn_rotli( g0, 56 );
            b0 |= cnv00toff( min(255.0f, col.z*255.0f) ); b0 = __insn_rotli( b0, 56 );

            if( (x&7) == 7 ) {
                outr[x/8] = r0; r0 = 0;
                outg[x/8] = g0; g0 = 0;
                outb[x/8] = b0; b0 = 0;
            }

        } // x

        outr += rowstride;
        outg += rowstride;
        outb += rowstride;

    } // y

}

vec3_t startpos[OBJNUM] = {
    { 0,0,0 },
    {  1.0f,  1.0f, -4.5f },
    { -1.0f,  1.0f, -4.5f },
    {  1.0f, -1.0f, -4.5f },
    { -1.0f, -1.0f, -4.0f },
    {  0.0f, -2.0f, -2.0f },
};

#define COLNUM 8
vec3_t coltable[COLNUM] = {
    { 1.0f, 0.0f, 0.0f }, // b1
    { 0.0f, 1.0f, 0.0f }, // g1
    { 0.0f, 0.0f, 1.0f }, // r1
    { 1.0f, 1.0f, 0.0f }, // 1
    { 0.0f, 1.0f, 1.0f }, // 1
    { 1.0f, 0.0f, 1.0f }, // 1
    { 1.0f, 1.0f, 1.0f }, // 1
    { 0.0f, 0.0f, 0.0f }, // 1
};

static void trace_init_global( trace_global *tg, int w, int h, int stride )
{
    tg->framew   = w;
    tg->frameh   = h;
    tg->stride   = stride;

    tg->curobj = 1;
    tg->curcol = 0;

    tg->eye       = vec3_set( 0.0f, 0.0f, -7.0f );

    tg->light.col = vec3_set( 1.0f,    1.0f,    1.0f );
    tg->light.pos = vec3_set( 0.0f,   -3.0f,   -7.5f );
    tg->light.spd = vec3_set( 0.0100f, 0.0120f, 0.0f );

    tg->swidth  = 10.0f * (float)w / (float)h;
    tg->sheight = 10.0f;

    tg->ax  = tg->swidth  / (float)w;
    tg->ayc = tg->sheight / (float)h;
    tg->ay  = tg->sheight / (float)h;

//    tg->obj[0].spd     = vec3_set( 0.023f, 0.035f, 0.0f );
//    tg->obj[0].spd     = vec3_set( randf()*0.057f-0.023f, randf()*0.057f-0.023f, 0 );
    tg->obj[0].spd     = vec3_set( 0.0f, 0.0f, 0.0f );
    tg->obj[0].col     = coltable[tg->curcol++];
    tg->obj[0].pos     = vec3_set( 0.0f, 0.0f, 0.0f );
    tg->obj[0].radsq   = 1.7f * 1.7f;
    tg->obj[0].poseye  = vec3_sub( tg->eye, tg->obj[0].pos );
    tg->obj[0].rv      = -norm2( tg->obj[0].poseye ) + tg->obj[0].radsq;
    tg->obj[0].shadow  = true;
    tg->obj[0].reflect = true;

    for( int i = 1; i < OBJNUM; i++ ) {
        tg->obj[i].spd     = vec3_set( randf()*0.065f-0.021f, randf()*0.065f-0.021f, randf()*0.065f-0.021f );
        tg->obj[i].col     = coltable[tg->curcol++];
        if( tg->curcol >= COLNUM ) tg->curcol = 0;
        tg->obj[i].pos     = startpos[i];
        tg->obj[i].radsq   = 0.65f * 0.65f;
        tg->obj[i].poseye  = vec3_sub( tg->eye, tg->obj[i].pos );
        tg->obj[i].rv      = -norm2( tg->obj[i].poseye ) + tg->obj[i].radsq;
        tg->obj[i].shadow  = false;
        tg->obj[i].reflect = i < OBJNUM - 2;
    }

    tg->next_col  = 60*5+30;
    tg->next_ccol = 60*8+25;
    tg->next_col_time  = tg->next_col;
    tg->next_ccol_time = tg->next_ccol;

    tg->current_time = 0;

}

#define BBOX 3.2f
#define LBBOX 7.5f
static void trace_timer( trace_global *tg )
{
    tg->current_time++;

    for( int e = 1; e < OBJNUM; e++ ) {
        vec3_t *pos = &tg->obj[e].pos;
        vec3_t *spd = &tg->obj[e].spd;
        tg->obj[e].pos = vec3_add(tg->obj[e].pos, tg->obj[e].spd);
        if( pos->x >  BBOX ) { spd->x = -spd->x; pos->x =  BBOX; }
        if( pos->y >  BBOX ) { spd->y = -spd->y; pos->y =  BBOX; }
        if( pos->z >  BBOX ) { spd->z = -spd->z; pos->z =  BBOX; }
        if( pos->x < -BBOX ) { spd->x = -spd->x; pos->x = -BBOX; }
        if( pos->y < -BBOX ) { spd->y = -spd->y; pos->y = -BBOX; }
        if( pos->z < -BBOX ) { spd->z = -spd->z; pos->z = -BBOX; }
        tg->obj[e].poseye = vec3_sub( tg->eye, tg->obj[e].pos );
        tg->obj[e].rv     = -norm2( tg->obj[e].poseye ) + tg->obj[e].radsq;
    }

    tg->light.pos = vec3_add( tg->light.pos, tg->light.spd );
    if( tg->light.pos.x >  LBBOX ) { tg->light.spd.x = -tg->light.spd.x; tg->light.pos.x =  LBBOX; }
    if( tg->light.pos.y >  LBBOX ) { tg->light.spd.y = -tg->light.spd.y; tg->light.pos.y =  LBBOX; }
    if( tg->light.pos.x < -LBBOX ) { tg->light.spd.x = -tg->light.spd.x; tg->light.pos.x = -LBBOX; }
    if( tg->light.pos.y < -LBBOX ) { tg->light.spd.y = -tg->light.spd.y; tg->light.pos.y = -LBBOX; }

    if( tg->current_time >= tg->next_col_time ) {
        tg->obj[tg->curobj++].col = coltable[tg->curcol++];
        if( tg->curcol >= COLNUM ) tg->curcol = 0;
        if( tg->curobj >= OBJNUM ) tg->curobj = 1;
        tg->next_col_time = tg->current_time + tg->next_col;
    }

    if( tg->current_time >= tg->next_ccol_time ) {
        tg->obj[0].col = coltable[tg->curcol++];
        if( tg->curcol >= COLNUM ) tg->curcol = 0;
        tg->next_ccol_time = tg->current_time + tg->next_ccol;
    }

}


static bool writebmp( char *fname, uint8_t *src, int width, int height )
{
    uint8_t hdr[] = {
        0x42, 0x4D, 0x36, 0x90,
        0x12, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x36, 0x00,
        0x00, 0x00, 0x28, 0x00,
        0x00, 0x00, 0xC0, 0x02,
        0x00, 0x00, 0x40, 0x02,
        0x00, 0x00, 0x01, 0x00,
        0x18, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x90,
        0x12, 0x00, 0x13, 0x0B,
        0x00, 0x00, 0x13, 0x0B,
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00
    };

    FILE *fp;
    int filesize;
    int size, rowbytes;

    rowbytes = (width*3 + ((width*2)%4));
    size = sizeof(hdr) + rowbytes * height;

    printf( "Saving bitmap %s\n", fname );

    printf( "rowbytes: %d\n", rowbytes );

    fp = fopen( fname, "wb" );
    if( fp == NULL ) {
		printf( "Error: Could not create ouput file %s\n", fname );
        return false;
	}

    hdr[2] = (size>> 0)&0xff;
    hdr[3] = (size>> 8)&0xff;
    hdr[4] = (size>>16)&0xff;
    hdr[5] = (size>>24)&0xff;

    hdr[18] = (width)&0xff;
    hdr[19] = ((width)>>8)&0xff;

    hdr[22] = (height)&0xff;
    hdr[23] = ((height)>>8)&0xff;

    filesize = fwrite( hdr, 1, sizeof(hdr), fp );
    if( filesize != sizeof(hdr) ) {
        printf( "Error: Output header truncated!\n" );
        fclose( fp );
        return false;
    }

    // bmp is upside down, which doesn't really matter.
    // but flip it in case I need to debug something at a specific coord
    for( int y = height-1; y >= 0; y-- ) {
        filesize = fwrite( src + y*width*3, 1, rowbytes, fp );
        if( filesize != rowbytes ) {
            printf( "Error: Output file truncated! %d expected %d\n", filesize, rowbytes );
            fclose( fp );
            return false;
        }
    }

    return true;
}

static uint8_t *rgb24plto24packed( uint8_t *src, int stride, int w, int h )
{
    uint8_t *buf = malloc( w * h * 3 );
    uint8_t *dst = buf;

    for( int y = 0; y < h; y++ ) {

        uint8_t *srcr   = src + y*stride*3;
        uint8_t *srcg   = src + y*stride*3 + stride;
        uint8_t *srcb   = src + y*stride*3 + 2*stride;

        for( int x = 0; x < w; x++ ) {
            *dst++ = *srcb++;
            *dst++ = *srcg++;
            *dst++ = *srcr++;

        }

    }

    return buf;
}


static void __attribute__((noinline,noreturn)) render_core( int coreid )
{
    DynamicHeader header = tmc_udn_header_from_cpu( 0 );

    uint8_t *ptr;

    while( true ) {
        // say we're ready
        tmc_udn_send_1( header, UDN0_DEMUX_TAG, coreid );

        // wait for a job...
        uint32_t xy = tmc_udn0_receive();
        if( xy == 0xffff ) exit( 0 );

        ptr = (uint8_t *)tmc_udn0_receive();

        // ...and do it.
        render( tglob, ptr, xy>>16, xy&0xffff );
    }
}

static void *thread_start( void *arg )
{
    int coreid = (int)(uint64_t)arg;

    if( tmc_cpus_set_my_cpu( coreid ) != 0 )
        tmc_task_die( "tmc_cpus_set_my_cpu() failed." );

    if( tmc_udn_activate() < 0 )
        tmc_task_die( "Failure in ’tmc_udn_activate()’." );

    render_core( coreid );

    return NULL;
}

sem_t framesem;

static void media_completion_callback( void *args )
{
    sem_post( &framesem );
}

#define BAR_FRAMES 5

static void draw1bar( uint8_t *dst, int count, uint8_t val1, uint8_t val2 )
{
    int x;
    for( x = 0; x < count; x++ )
        *dst++ = val1;
    for( ; x < WIDTH; x++ )
        *dst++ = val2;
}
static void drawbar( trace_global *tg, uint8_t *dst, int load )
{
    uint8_t * restrict outr   = dst;
    uint8_t * restrict outg   = dst +   tg->stride;
    uint8_t * restrict outb   = dst + 2*tg->stride;

    for( int y = 0; y < 8; y++ ) {
        draw1bar( outr, load, 0xff, 0x00 );
        draw1bar( outg, load, 0x00, 0xff );
        draw1bar( outb, load, 0x00, 0x00 );

        outr += 3*tg->stride;
        outg += 3*tg->stride;
        outb += 3*tg->stride;
    }

}

// dummy functions to compile
struct io_buf {
    uint8_t *data;
};

struct io_buf *mediaio_video_sink_buf_req( int ch )
{
    return NULL;
}

void mediaio_video_sink_buf_post( int channel, struct io_buf *buf )
{

}

static void render_controller( trace_global *tglob, int channel, int rw, int rh, bool savepics, int frames, int timeskip, bool display )
{
    uint64_t total_bar = 0;
    uint64_t total = 0;
    float load = 0;
    uint8_t *dst;
    struct io_buf *buf;

    int time_now  = 0;
    int time_quit = frames;

    if( !display )
        dst = malloc( rw * rh * 3 );

    while( time_now < time_quit ) {

        for( int i = 0; i < timeskip; i++ )
            trace_timer( tglob );

        if( display ) {
            sem_wait( &framesem );

            buf = mediaio_video_sink_buf_req( channel );
            if( buf == NULL ) {
                printf( "No video buffer ready... that's strange.\n" );
                return;
            }
            dst = buf->data;
        }

        uint64_t t0 = get_cycle_count();

        for( int y = 0; y < rh; y += BLOCKY ) {

            for( int x = 0; x < rw; x += BLOCKX ) {

                // wait for a core to be ready...
                uint32_t coreready = tmc_udn0_receive();

                // ...and give it the next job.
                tmc_udn_send_2( tmc_udn_header_from_cpu( coreready ), UDN0_DEMUX_TAG, (x<<16)|y, (uint64_t)dst );

            }

        }

        uint64_t t1 = get_cycle_count();

        if( display )
            mediaio_video_sink_buf_post( channel, buf );

        uint64_t frametime = t1-t0;
        total += frametime;
        total_bar += frametime;

        if( time_now % BAR_FRAMES == 0 && time_now > 0 ) {
            load = (total_bar*100.0f)/((((float)CPUFREQ)/60.0f)*BAR_FRAMES);
            total_bar = 0;
        }
        drawbar( tglob, dst, (int)(load*((float)WIDTH/100.0f)) );

        if( savepics ) {
            uint8_t *buf24 = rgb24plto24packed( dst, tglob->stride, tglob->framew, tglob->frameh );
            // need new planar to intl
            char fname[80];
            sprintf( fname, "pic%04d.bmp", time_now );
            writebmp( fname, buf24, tglob->framew, tglob->frameh );
            free( buf24 );
        }

        time_now++;
    }

    printf( "total cycles: %lld\n", (long long int)total );
    printf( "cpp: %f\n", ((float)(total)/(float)(tglob->framew*tglob->frameh))/(float)(time_quit) );
    printf( "average frame time: %f\n", (float)(total)/(float)(time_quit) );

}


static int parallelize( int count )
{
    int rc;
    cpu_set_t cpus;

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

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

    if( tmc_udn_init( &cpus ) < 0 )
        tmc_task_die( "Failure in 'tmc_udn_init(0)'." );

    if( sem_init( &framesem, 0, 0 ) ) {
        perror( "sem_init" );
        return -1;
    }

    pthread_attr_t attr;
    rc = pthread_attr_init( &attr );
    if( rc != 0 ) {
        perror( "pthread_attr_init" );
        return rc;
    }

    for( uint64_t rank = 1; (int)rank < count; rank++ ) {
        pthread_t thread_id;
        rc = pthread_create( &thread_id, &attr, &thread_start, (void *)rank );
        if( rc != 0 ) {
            perror( "pthread_create" );
            return rc;
        }
    }

    rc = pthread_attr_destroy( &attr );

    if( tmc_cpus_set_my_cpu( 0 ) != 0 )
        tmc_task_die("tmc_cpus_set_my_cpu() failed.");

    if( tmc_udn_activate() < 0 )
        tmc_task_die( "Failure in ’tmc_udn_activate()’." );

    return 0;
}



static void init_mediaio( int channel, int rw, int rh )
{

}


static void print_usage( const char *exec_name )
{
    printf( "Usage: %s [OPTIONS]\n\n", exec_name );
    printf( "Options:\n" );
    printf( "-m          Use predefined measurement config (-f 1200 -r 42 -t 5 -w 36 -d)\n" );
    printf( "-s          Save bmp files\n" );
    printf( "-f frames   Number of frames\n" );
    printf( "-r seed     Random seed\n" );
    printf( "-c channel  Mediaio channel to use\n" );
    printf( "-t skip     Time to skip between frames\n" );
    printf( "-w cores    Number of cores to run on\n" );
    printf( "-d          Disable mediaio output\n" );
}

int main( int argc, char *argv[] )
{
    int frames      = INT_MAX;
    int cpus        = 36;
    int timeskip    = 1;
    int srandval    = -1;
    int channel     = 0;
    bool savepics   = false;
    bool measurecfg = false;
    bool display    = false;

    int w = WIDTH;
    int h = HEIGHT;
    int rc;

    int opt;

    while( (opt = getopt( argc, argv, "hsmf:r:c:t:w:d" ) ) != -1 ) {
        switch( opt ) {
        case 'h':
            print_usage(argv[0]);
            return 0;
        case 's': savepics   = true;           break;
        case 'm': measurecfg = true;           break;
        case 'f': frames     = atoi( optarg ); break;
        case 'r': srandval   = atoi( optarg ); break;
        case 'c': channel    = atoi( optarg ); break;
        case 't': timeskip   = atoi( optarg ); break;
        case 'w': cpus       = atoi( optarg ); break;
        case 'd': display    = false;          break;

        default:
            printf( "yeah... I'll consider that.\n" );
            return -1;
        }
    }

    if( measurecfg ) {
        printf( "Measurement config being used\n" );
        frames   = 1200;
        cpus     = 36;
        timeskip = 5;
        srandval = 42;
        savepics = false;
        display  = false;
    }

    int rw = w + BLOCKX-1 -(w-1)%BLOCKX;
    int rh = h + BLOCKY-1 -(h-1)%BLOCKY;

    printf( "frames:    %d\n",    frames );
    printf( "cores:     %d\n",    cpus );
    printf( "blocksize: %d*%d\n", BLOCKX, BLOCKY );
    printf( "timeskip:  %d\n",    timeskip );
    printf( "randseed:  %d\n",    srandval );
    printf( "channel:   %d\n",    channel );
    printf( "display:   %s\n",    display ? "on" : "off" );
    printf( "output:    %d*%d\n", w, h );
    printf( "adjusted:  %d*%d\n", rw, rh );

    if( display )
        init_mediaio( channel, rw, rh );

    if( argc > 1 && !strcmp( (char *)argv[1], "-s" ) ) {
        printf( "saving images\n" );
        savepics = true;
    }

    if( srandval == -1 ) srandval = time( NULL );
    srand( srandval );

    tglob = (trace_global *)calloc( 1, sizeof(trace_global) );
    if( tglob == NULL ) {
        printf( "failed to allocate trace_global buffer\n" );
        return -1;
    }

    trace_init_global( tglob, w, h, rw );

    rc = parallelize( cpus );
    if( rc != 0 ) return rc;

    render_controller( tglob, channel, rw, rh, savepics, frames, timeskip, display );

    // shutdown
    for( int core = 1; core < cpus; core++ ) {
        DynamicHeader header = tmc_udn_header_from_cpu( core );
        tmc_udn_send_1( header, UDN0_DEMUX_TAG, 0xffff );
    }

    return 0;
}
