/* ex: set tabstop=4 expandtab: */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <math.h>
#include <string.h>
#include <values.h>
#include <sys/time.h>
#include <GL/glx.h>
#include <GL/glu.h>
#include "textureObject.h"
#include "actionFall.h"
#include "actionGrow.h"
#include "actionBrownian.h"
#include "actionMover.h"
#include "textureMovie.h"
#include "udpSocket.h"


#define DEFAULT_WINDOW_WIDTH 1024
#define DEFAULT_WINDOW_HEIGHT 768

#define MAX_MOVIES 100
#define MAX_ICONS 1000
#define MAX_ACTIONS 100
#define MAX_RESPONSES 1000
#define MAX_ACTIONS_PER_RESPONSE 100

#define FRAMES_PER_REPORT 300


extern "C" {
extern void openWindow(int,int,int,int);
extern void checkXEvents(int *loop,int *play);
extern void swapBuffers(void);
extern void * initMovie(char * filename);
extern void createMovieTexture(void *p);
extern void loadNextMovieTexture(void *p);
extern void closeMovie(void *p);
extern struct _waterfall * createWaterfall(int rows,int cols);
extern void drawWaterfall(struct _waterfall *w);
extern void updateWaterfall(struct _waterfall *w);
}

extern "C" float currentTime(void);
extern "C" float currentDeltaTime(void);


typedef struct
    {
    int windowWidth, windowHeight;
    int windowX, windowY;
    char * movieInfoFile;
    char * backgroundFile;
    char * iconsFile;
    char * actionsFile;
    float aspectRatio;
    int printFrameRate;
    } options_t;


typedef struct
    {
    float intervalMin, intervalMax;
    float dissolveTime;
    int numMovies;
    char * movieFile[MAX_MOVIES];
    textureMovie * movie[MAX_MOVIES];
    int currentMovie, nextMovie;
    float dissolveState, dissolveStart, nextDissolve;
    } movieInfo_t;


typedef struct
    {
    char * filename;
    textureObject * texobj;
    } icon_t;


typedef struct
    {
    enum { BROWNIAN, FALLER, GROWER, MOVER } kind;
    textureObject * texobj;
    float startX0, startX1, startY0, startY1;
    float endX0, endX1, endY0, endY1;
    float vx, vy;
    float varyX, varyY;
    float minTime, maxTime;
    float holdTime;
    float size;
    float texDirX, texDirY;
    } actionInfo_t;


typedef struct
    {
    char * label;
    actionInfo_t ** acts;
    int numActs;
    } response_t;


static void parseCommandLine(int argc,char **argv,options_t *opts);
static void loadMovies(char *infofile,movieInfo_t *info);
static void loadIcons(options_t *opts);
static textureObject * findIcon(char *filename);
static void loadActions(options_t *opts);
static void respondToCommand(char *cmd);
static void pickAction(response_t *resp);
static void launchBrownian(actionInfo_t *act);
static void launchFaller(actionInfo_t *act);
static void launchGrower(actionInfo_t *act);
static void launchMover(actionInfo_t *act);
static void initDraw(void);
static void initActions(void);
static void drawActions(void);
static void initClock(void);
static void reportFrameRate(int frames);
static void updateTime(void);



static icon_t icons[MAX_ICONS];
static int numIcons = 0;

static response_t * responses[MAX_RESPONSES];
static int numResponses=0;

static action * activeActions[MAX_ACTIONS];


int main(int argc, char* argv[])
    {
    options_t options;
    movieInfo_t movies;
    int loop=1, play=1, frame=0, numUDPBytes;
    int udpSocket;
    unsigned char udpBuffer[8192];
    struct _waterfall * waterfall;
    textureObject * background = NULL;
    options.windowWidth = DEFAULT_WINDOW_WIDTH;
    options.windowHeight = DEFAULT_WINDOW_HEIGHT;
    options.windowX = 0;
    options.windowY = 0;
    options.movieInfoFile = NULL;
    options.iconsFile = NULL;
    options.actionsFile = NULL;
    options.backgroundFile = NULL;
    options.aspectRatio = 0.0;
    options.printFrameRate = 0;
    parseCommandLine(argc,argv,&options);
    if (!options.movieInfoFile)
        {
        fprintf(stderr,"ERROR: --movie option is required\n");
        exit(1);
        }
        
    if (options.aspectRatio == 0.0)
        options.aspectRatio = ((float)options.windowWidth) / options.windowHeight;

    openWindow(options.windowWidth,options.windowHeight,
                options.windowX,options.windowY);

    movies.intervalMin = 60;
    movies.intervalMax = 60;
    movies.dissolveTime = 1;
    movies.numMovies = 0;
    movies.currentMovie = 0;
    movies.nextMovie = 0;
    movies.dissolveState = 1.0;
    loadMovies(options.movieInfoFile, &movies);
    movies.nextDissolve = movies.intervalMin +
                        drand48() * (movies.intervalMax - movies.intervalMin);
    
    if (options.iconsFile)
        loadIcons(&options);    
    initActions();
    if (options.actionsFile)
        loadActions(&options);    
    if (options.backgroundFile)
        background = new textureObject(options.backgroundFile, 1.0);
    
    waterfall = createWaterfall(30,50);

    udpSocket = udpOpenInputSocket(7500);
    initClock();
    while (loop)
        {
        updateTime();
        checkXEvents(&loop,&play);
        initDraw();
        if (background)
            {
            glPushMatrix();
             glTranslatef(0.5, 0.5, 0.0);
             background->draw();
            glPopMatrix();
            }
        
        movies.movie[movies.currentMovie]->bind();
        drawWaterfall(waterfall);
        movies.movie[movies.currentMovie]->update(currentTime());
        if (movies.dissolveState < 1)
            {
            glEnable(GL_BLEND);
            glBlendFunc(GL_ONE_MINUS_CONSTANT_ALPHA, GL_CONSTANT_ALPHA);
            glBlendColor(1.0, 1.0, 1.0, movies.dissolveState);
            movies.movie[movies.nextMovie]->bind();
            drawWaterfall(waterfall);
            glDisable(GL_BLEND);
            movies.movie[movies.nextMovie]->update(currentTime());
            movies.dissolveState = 1.0 - (currentTime() - movies.dissolveStart) /
                                    movies.dissolveTime;
            if (movies.dissolveState <= 0)
                {
                movies.dissolveState = 1;
                movies.currentMovie = movies.nextMovie;
                movies.nextDissolve = currentTime() + movies.intervalMin +
                        drand48() * (movies.intervalMax - movies.intervalMin);
                }
            }
        else
            {
            if (currentTime() > movies.nextDissolve)
                {
                movies.dissolveStart = movies.nextDissolve;
                movies.dissolveState = 1.0 - (currentTime() - movies.dissolveStart) /
                                    movies.dissolveTime;
                while ((movies.nextMovie == movies.currentMovie) && (movies.numMovies > 1))
                    movies.nextMovie = random() % movies.numMovies;
                }
            }
        updateWaterfall(waterfall);
        
        drawActions();
        swapBuffers();
        if ((numUDPBytes = udpReceive(udpSocket, udpBuffer, sizeof(udpBuffer))) > 0)
            {
            char *s = (char *)udpBuffer, *token;
            udpBuffer[numUDPBytes+1] = '\0';  /* Python doesn't always send the ending NULL */
            while (token = strtok(s, " \t\n"))
                {
                respondToCommand(token);
                s = NULL;
                }
            }
        if (options.printFrameRate)
            {
            frame++;
            if ((frame % FRAMES_PER_REPORT) == 0)
                reportFrameRate(frame);
            }
        }
    close(udpSocket);
    }


static void loadMovies(char *infofile,movieInfo_t *info)
    {
    FILE *fp;
    char line[1024], cmd[1024];
    fp = fopen(infofile, "r");
    if (!fp)
        {
        perror(infofile);
        exit(1);
        }
    while (fgets(line,sizeof(line),fp))
        {
        if ((line[0] == '#') || (sscanf(line,"%s",cmd) <= 0))
            continue;
        if (!strcasecmp(cmd,"movieinterval"))
            sscanf(line,"%*s%f%f",&info->intervalMin,&info->intervalMax);
        else if (!strcasecmp(cmd,"moviedissolvetime"))
            sscanf(line,"%*s%f",&info->dissolveTime);
        else if (!strcasecmp(cmd,"movie"))
            {
            if (info->numMovies == MAX_MOVIES)
                fprintf(stderr,"Warning: too many movies; max is %d\n",
                        MAX_MOVIES);
            else
                {
                char filename[1024];
                float frameRate=30;
                if (sscanf(line,"%*s%s%f",filename,&frameRate) > 0)
                    {
                    info->movieFile[info->numMovies] = strdup(filename);
                    info->movie[info->numMovies] =
                            new textureMovie(filename, frameRate);
                    info->numMovies++;
                    }
                }
            }
        }
    fclose(fp);
    }


static void initDraw(void)
    {
    glClearColor(0., 0., 0., 0.); 
    glDrawBuffer(GL_BACK);
    glClear(GL_COLOR_BUFFER_BIT);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0.0, 1.0, 0.0, 1.0,-1.0,1.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glEnable(GL_TEXTURE_2D);
    glColor4f(1.0, 1.0, 1.0, 1.0);
    }


static void loadIcons(options_t *opts)
    {
    FILE *fp;
    char line[1024], iconfile[1024];
    numIcons = 0;
    fp = fopen(opts->iconsFile,"r");
    if (!fp)
        {
        perror(opts->iconsFile);
        exit(1);
        }
    while (fgets(line,sizeof(line),fp))
        {
        float aspect = 1;
        sscanf(line,"%s%f",iconfile,&aspect);
        icons[numIcons].filename = strdup(iconfile);
        icons[numIcons].texobj = new textureObject(iconfile, aspect/opts->aspectRatio);
        numIcons++;
        if (numIcons >= MAX_ICONS)
            break;
        }
    fclose(fp);
    }


static textureObject * findIcon(char *filename)
    {
    int i, length;
    char slashedFilename[1024];
    if (!filename)
        return NULL;
    sprintf(slashedFilename,"/%s",filename);
    length = strlen(slashedFilename);
    for (i=0; i < numIcons; i++)
        {
        if (!strcmp(filename, icons[i].filename))
            return icons[i].texobj;
        int startPos = strlen(icons[i].filename) - length;
        if ((startPos >= 0) &&
            (!strcmp(slashedFilename, icons[i].filename+startPos)))
            return icons[i].texobj;
        }
    return NULL;
    }


static response_t * newResponse(char *label)
    {
    response_t * r;
    if (numResponses == MAX_RESPONSES)
        {
        fprintf(stderr,"ERROR: Maximum number of responses exceeded (%d)\n",
                MAX_RESPONSES);
        exit(1);
        }
    r = new response_t;
    r->label = strdup(label);
    r->acts = (actionInfo_t **) calloc(MAX_ACTIONS_PER_RESPONSE,sizeof(actionInfo_t *));
    r->numActs = 0;
    responses[numResponses++] = r;
    return r;
    }


static void parseActionLine(char *line,response_t * resp)
    {
    char kindStr[256], iconFile[256];
    actionInfo_t * act;
    if (sscanf(line,"%s%s",kindStr,iconFile) <= 0)
        return;
    if (resp->numActs == MAX_ACTIONS_PER_RESPONSE)
        {
        fprintf(stderr,"ERROR: Maximum number of actions per response exceeded"
                " for '%s' (%d)\n",resp->label,MAX_ACTIONS_PER_RESPONSE);
        exit(1);
        }
    act = new actionInfo_t;
    act->texobj = findIcon(iconFile);
    if (!act->texobj)
        {
        fprintf(stderr,"ERROR: couldn't find icon '%s' in response '%s'\n",
                iconFile, resp->label);
        return;
        }
    if (!strcasecmp(kindStr, "brownian"))
        {
        act->kind = actionInfo_t::BROWNIAN;
        if (sscanf(line,"%*s%*s%f%f%f%f%f%f%f%f%f%f%f%f%f",
                &act->startX0, &act->startX1,
                &act->startY0, &act->startY1, &act->vx, &act->vy,
                &act->varyX, &act->varyY, &act->minTime, &act->maxTime,
                &act->size, &act->texDirX, &act->texDirY) != 13)
            fprintf(stderr,"ERROR: missing arguments for Brownian '%s' in '%s'\n",
                    iconFile, resp->label);
        }
    else if (!strcasecmp(kindStr, "faller"))
        {
        act->kind = actionInfo_t::FALLER;
        if (sscanf(line,"%*s%*s%f%f%f", &act->startX0, &act->startX1,
                &act->size) != 3)
            fprintf(stderr,"ERROR: missing arguments for Faller '%s' in '%s'\n",
                    iconFile, resp->label);
        }
    else if (!strcasecmp(kindStr, "grower"))
        {
        act->kind = actionInfo_t::GROWER;
        if (sscanf(line,"%*s%*s%f%f%f%f%f%f%f%f",
                &act->startX0, &act->startX1,
                &act->startY0, &act->startY1, &act->minTime, &act->maxTime,
                &act->holdTime, &act->size) != 8)
            fprintf(stderr,"ERROR: missing arguments for Grower '%s' in '%s'\n",
                    iconFile, resp->label);
        }
    else if (!strcasecmp(kindStr, "mover"))
        {
        act->kind = actionInfo_t::MOVER;
        if (sscanf(line,"%*s%*s%f%f%f%f%f%f%f%f%f%f%f%f%f%f",
                &act->startX0, &act->startX1,
                &act->startY0, &act->startY1, &act->endX0, &act->endX1,
                &act->endY0, &act->endY1, &act->minTime, &act->maxTime,
                &act->holdTime, &act->size, &act->texDirX, &act->texDirY) != 14)
            fprintf(stderr,"ERROR: missing arguments for Mover '%s' in '%s'\n",
                    iconFile, resp->label);
        }
    else
        {
        fprintf(stderr,"ERROR: Unknown action type '%s' in response '%s'\n",
                kindStr, resp->label);
        return;
        }
    resp->acts[resp->numActs] = act;
    resp->numActs++;
    }


static void loadActions(options_t *opts)
    {
    FILE *fp;
    char line[1024], cmd[1024], label[1024];
    response_t * currentResponse = NULL;
    numResponses = 0;
    fp = fopen(opts->actionsFile,"r");
    if (!fp)
        {
        perror(opts->actionsFile);
        exit(1);
        }
    while (fgets(line,sizeof(line),fp))
        {
        if ((line[0] == '#') || (sscanf(line,"%s",cmd) <= 0))
            continue;
        if (!strcasecmp(cmd,"response"))
            {
            sscanf(line,"%*s%s",label); 
            currentResponse = newResponse(label);
            }
        else
            parseActionLine(line,currentResponse);
        }
    fclose(fp);
    }


static void respondToCommand(char *cmd)
    {
    int i;
    for (i=0; i < numResponses; i++)
        if (!strcasecmp(cmd, responses[i]->label))
            {
            pickAction(responses[i]);
            return;
            }
    printf("Warning: no response available for command '%s'\n", cmd);
    }


static void pickAction(response_t *resp)
    {
    if (resp->numActs < 1)
        return;
    int n = random() % resp->numActs;
    actionInfo_t * act = resp->acts[n];
    if (act->kind == actionInfo_t::BROWNIAN)
        launchBrownian(act);
    else if (act->kind == actionInfo_t::FALLER)
        launchFaller(act);
    else if (act->kind == actionInfo_t::GROWER)
        launchGrower(act);
    else if (act->kind == actionInfo_t::MOVER)
        launchMover(act);
    }


static void initActions(void)
    {
    for (int i = 0; i < MAX_ACTIONS; i++)
        activeActions[i] = NULL;
    }


static void addAction(action * a)
    {
    int i;
    for (i=0; i < MAX_ACTIONS; i++)
        if (!activeActions[i])
            {
            activeActions[i] = a;
            return;
            }
    delete a;
    }


static void launchBrownian(actionInfo_t *act)
    {
    float x, y, duration;
    x = act->startX0 + drand48() * (act->startX1 - act->startX0);
    y = act->startY0 + drand48() * (act->startY1 - act->startY0);
    duration = act->minTime + drand48() * (act->maxTime - act->minTime);
    action * a = new actionBrownian(act->texobj, x, y, act->vx, act->vy,
                                act->varyX, act->varyY, duration, act->size);
    a->setTexDir(act->texDirX, act->texDirY);
    addAction(a);
    }


static void launchFaller(actionInfo_t *act)
    {
    float x = act->startX0 + drand48() * (act->startX1 - act->startX0);
    addAction(new actionFall(act->texobj, x, 1.0, act->size));
    }


static void launchGrower(actionInfo_t *act)
    {
    float x, y, duration;
    x = act->startX0 + drand48() * (act->startX1 - act->startX0);
    y = act->startY0 + drand48() * (act->startY1 - act->startY0);
    duration = act->minTime + drand48() * (act->maxTime - act->minTime);
    addAction(new actionGrow(act->texobj, x, y, duration, act->holdTime,
                            act->size));
    }


static void launchMover(actionInfo_t *act)
    {
    
    float x, y, endx, endy, duration;
    x = act->startX0 + drand48() * (act->startX1 - act->startX0);
    y = act->startY0 + drand48() * (act->startY1 - act->startY0);
    endx = act->endX0 + drand48() * (act->endX1 - act->endX0);
    endy = act->endY0 + drand48() * (act->endY1 - act->endY0);
    duration = act->minTime + drand48() * (act->maxTime - act->minTime);
    action * a = new actionMover(act->texobj, x, y, endx, endy, duration,
                                act->holdTime, act->size);
    a->setTexDir(act->texDirX, act->texDirY);
    addAction(a);
    }


static void drawActions(void)
    {
    int i;
    glColor4f(1.0, 1.0, 1.0, 1.0);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    for (i=0; i < MAX_ACTIONS; i++)
        {
        if (activeActions[i])
            {
            if (activeActions[i]->update())
                activeActions[i]->draw();
            else
                {
                delete activeActions[i];
                activeActions[i] = NULL;
                }
            }
        }
    glDisable(GL_BLEND);
    }




static int initsec, initusec;
static float deltaTime=.1;
static float nowTime=0;


static void initClock(void)
    {
    struct timeval t;
    gettimeofday(&t,NULL);
    initsec = t.tv_sec;
    initusec = t.tv_usec;
    }


static void reportFrameRate(int frames)
    {
    float interval;
    struct timeval t;
    gettimeofday(&t,NULL);
    interval = (t.tv_sec-initsec) + (t.tv_usec - initusec)*0.000001;
    printf("%.2f fps\n",frames/interval);
    }


extern "C" float currentTime(void)
    {
    return nowTime;
    }


extern "C" float currentDeltaTime(void)
    {
    return deltaTime;
    }


static void updateTime(void)
    {
    static float lastTime = 0;
    struct timeval t;
    gettimeofday(&t,NULL);
    nowTime = (t.tv_sec-initsec) + (t.tv_usec - initusec)*0.000001;
    deltaTime = nowTime - lastTime;
    lastTime = nowTime;
    }

/*********************************************************************/
/*   Command-line parsing    */

static void printUsage(char *argv0)
    {
    fprintf(stderr,"Usage:\n %s [--window width height x y]\n"
            "     [--movie movie-info-file] [--background background-image]\n"
            "     [--icons icons-file] [--actions actions-file]\n"
            "     [--aspect aspect-ratio] [--framerate]\n"
            ,argv0);
    exit(1);
    }

	
static void parseCommandLine(int argc,char **argv,options_t *opts)
    {
    int i;
    for (i=1; i < argc; i++)
        {
        if (!strncmp(argv[i],"--",2))
            {
            if (!strcasecmp(argv[i],"--window"))
                {
                if (i < argc-4)
                    {
                    opts->windowWidth = atoi(argv[++i]);
                    opts->windowHeight = atoi(argv[++i]);
                    opts->windowX = atoi(argv[++i]);
                    opts->windowY = atoi(argv[++i]);
                    }
                else
                    printUsage(argv[0]);
                }
            else if (!strcasecmp(argv[i],"--movie"))
                {
                if (i < argc-1)
                    opts->movieInfoFile = strdup(argv[++i]);
                else
                    printUsage(argv[0]);
                }
            else if (!strcasecmp(argv[i],"--icons"))
                {
                if (i < argc-1)
                    opts->iconsFile = strdup(argv[++i]);
                else
                    printUsage(argv[0]);
                }
            else if (!strcasecmp(argv[i],"--actions"))
                {
                if (i < argc-1)
                    opts->actionsFile = strdup(argv[++i]);
                else
                    printUsage(argv[0]);
                }
            else if (!strcasecmp(argv[i],"--background"))
                {
                if (i < argc-1)
                    opts->backgroundFile = strdup(argv[++i]);
                else
                    printUsage(argv[0]);
                }
            else if (!strcasecmp(argv[i],"--framerate"))
                opts->printFrameRate = 1;
            else if (!strcasecmp(argv[i],"--aspect"))
                {
                if (i < argc-1)
                    opts->aspectRatio = atof(argv[++i]);
                else
                    printUsage(argv[0]);
                }
            else if (!strcasecmp(argv[i],"--help"))
                printUsage(argv[0]);
            else
                printUsage(argv[0]);
            }
        }
    }

