/* ex: set tabstop=4 expandtab: */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <math.h>
#include <values.h>
#include <GL/glx.h>
#include <GL/glu.h>
#include "udpSocket.h"

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

#define MAX_VY -0.625
#define ACCEL_Y -6.25
#define LIFETIME 3.5

typedef struct
    {
    float x0,y0, x1,y1;
    float u0,v0, u1,v1;
    float vx, vy;
    float vu, vv;
    enum { NORMAL, BOUNCED, OFFLINE } state;
    float removeTime;
    } particle_t;


typedef struct _waterfall
    {
    int numParticles;
    int cols;
    float pWidth, pHeight;
    float rowCycle;
    int maxAge;
    particle_t *particle;
    } waterfall_t;


static void checkShadowCollision(waterfall_t *w);
static void checkMouseCollision(waterfall_t *w);
static int checkCollision(particle_t *particle,float x,float y,float radius);
static void injectNewRow(waterfall_t *w);

typedef struct
    {
    int rows, cols;
    unsigned char data[1];
    } shadowPacket_t;
static shadowPacket_t *shadowPacket;
static int shadowPacketMaxSize;
static int shadowSocket;


waterfall_t * createWaterfall(int rows,int cols)
    {
    int numParticles = rows*cols*2;
    int i,j,p;
    float now = currentTime();
    waterfall_t * w = (waterfall_t *) malloc(sizeof(waterfall_t));
    w->numParticles = numParticles;
    w->cols = cols;
    w->particle = (particle_t *) malloc(numParticles * sizeof(particle_t));
    w->pWidth = 1.0 / (cols-1);
    w->pHeight = 1.0 / rows;
    for (i=0; i < w->numParticles; i++)
        w->particle[i].state = OFFLINE;
    for (i=0, p=0; i < cols; i++)
        {
        for (j=0; j < rows; j++, p++)
            {
            w->particle[p].x0 = i * w->pWidth;
            w->particle[p].y0 = j * w->pHeight;
            w->particle[p].x1 = (i+1) * w->pWidth;
            w->particle[p].y1 = (j+1) * w->pHeight;
            w->particle[p].u0 = w->particle[p].x0;
            w->particle[p].v0 = w->particle[p].y0;
            w->particle[p].u1 = w->particle[p].x1;
            w->particle[p].v1 = w->particle[p].y1;
            w->particle[p].vx = 0;
            w->particle[p].vy = MAX_VY;
            w->particle[p].vu = w->particle[p].vx;
            w->particle[p].vv = w->particle[p].vy;
            w->particle[p].state = NORMAL;
            w->particle[p].removeTime = now + LIFETIME;
            }
        }
    w->rowCycle = 0;
    shadowSocket = udpOpenInputSocket(7501);
      /* pre-allocate enough space for what we assume is the largest
         possible packet */
    shadowPacketMaxSize = 640*480+2*sizeof(int);
    shadowPacket = (shadowPacket_t *) calloc(1,shadowPacketMaxSize);
    return w;
    }


static void drawShadow(void)
    {
    int row,col,i;
    float width,height;
    if ((shadowPacket->rows <= 0) || (shadowPacket->cols <= 0))
        return;
    width = 1.0 / shadowPacket->cols;
    height = 1.0 / shadowPacket->rows;
    glColor3f(1.0,0.0,0.0);
    glBegin(GL_LINES);
    for (row=0, i=0; row < shadowPacket->rows; row++)
        for (col=0; col < shadowPacket->cols; col++, i++)
            {
            if (shadowPacket->data[i])
                {
                float x0,y0;
                x0 = col * width;
                y0 = row * height;
                glVertex2f(x0-width/2.0,y0-height/2.0);
                glVertex2f(x0+width/2.0,y0+height/2.0);
                glVertex2f(x0+width/2.0,y0-height/2.0);
                glVertex2f(x0-width/2.0,y0+height/2.0);
                }
            }
    glEnd();
    }


void drawWaterfall(waterfall_t *w)
    {
    int i;
//    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
//    glLineWidth(2);
    glBegin(GL_QUADS);
    for (i=0; i < w->numParticles; i++)
        if (w->particle[i].state == NORMAL)
        {
        glTexCoord2f(w->particle[i].u0, w->particle[i].v0);
        glVertex2f(w->particle[i].x0, w->particle[i].y0);
        glTexCoord2f(w->particle[i].u1, w->particle[i].v0);
        glVertex2f(w->particle[i].x1, w->particle[i].y0);
        glTexCoord2f(w->particle[i].u1, w->particle[i].v1);
        glVertex2f(w->particle[i].x1, w->particle[i].y1);
        glTexCoord2f(w->particle[i].u0, w->particle[i].v1);
        glVertex2f(w->particle[i].x0, w->particle[i].y1);
        }
    for (i=0; i < w->numParticles; i++)
        if (w->particle[i].state == BOUNCED)
        {
        glTexCoord2f(w->particle[i].u0, w->particle[i].v0);
        glVertex2f(w->particle[i].x0, w->particle[i].y0);
        glTexCoord2f(w->particle[i].u1, w->particle[i].v0);
        glVertex2f(w->particle[i].x1, w->particle[i].y0);
        glTexCoord2f(w->particle[i].u1, w->particle[i].v1);
        glVertex2f(w->particle[i].x1, w->particle[i].y1);
        glTexCoord2f(w->particle[i].u0, w->particle[i].v1);
        glVertex2f(w->particle[i].x0, w->particle[i].y1);
        }
    glEnd();
//    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
//    glLineWidth(1);
    }


void updateWaterfall(waterfall_t *w)
    {
    int i;
    float now = currentTime();
    float dt = currentDeltaTime();
    for (i=0; i < w->numParticles; i++)
        {
        if (w->particle[i].state != OFFLINE)
            {
            w->particle[i].x0 += w->particle[i].vx * dt;
            w->particle[i].x1 += w->particle[i].vx * dt;
            w->particle[i].y0 += w->particle[i].vy * dt;
            w->particle[i].y1 += w->particle[i].vy * dt;
            w->particle[i].u0 += w->particle[i].vu * dt;
            w->particle[i].u1 += w->particle[i].vu * dt;
            w->particle[i].v0 += w->particle[i].vv * dt;
            w->particle[i].v1 += w->particle[i].vv * dt;
            w->particle[i].vx *= 0.9;
            if ((w->particle[i].y1 < 0.0) || (w->particle[i].removeTime < now))
                w->particle[i].state = OFFLINE;
            else if (w->particle[i].vy > MAX_VY+0.0001)
                w->particle[i].vy += ACCEL_Y * dt;
             }
        }
    w->rowCycle += MAX_VY * dt;
    if (w->rowCycle < 0)
        injectNewRow(w);
    checkShadowCollision(w);
    checkMouseCollision(w);
    }


static void injectNewRow(waterfall_t *w)
    {
    int i,p;
    float now = currentTime();
    float xOffset = drand48() * w->pWidth;
    for (i=0, p=0; i < w->cols; i++)
        {
        for (; p < w->numParticles; p++)
            if (w->particle[p].state == OFFLINE)
                break;
        if (p >= w->numParticles)
            break;
        w->particle[p].x0 = i * w->pWidth - xOffset;
        w->particle[p].y0 = 1.0 + w->rowCycle;
        w->particle[p].x1 = (i+1) * w->pWidth - xOffset;
        w->particle[p].y1 = w->particle[p].y0 + w->pHeight;
        w->particle[p].u0 = w->particle[p].x0;
        w->particle[p].v0 = w->particle[p].y0;
        w->particle[p].u1 = w->particle[p].x1;
        w->particle[p].v1 = w->particle[p].y1;
        w->particle[p].vx = 0;
        w->particle[p].vy = MAX_VY;
        w->particle[p].vu = w->particle[p].vx;
        w->particle[p].vv = w->particle[p].vy;
        w->particle[p].state = NORMAL;
        w->particle[p].removeTime = now + LIFETIME;
        }
    w->rowCycle += w->pHeight;
    }


static float blockX = -1.0, blockY = -1.0, blockSize;

void setWaterfallBlock(float x, float y, float size)
    {
    blockX = x;
    blockY = y;
    blockSize = size;
    }

#define SHADOW_X_OFFSET 0.12
#define SHADOW_X_DIM (0.95 - SHADOW_X_OFFSET)
#define SHADOW_Y_OFFSET 0.018
#define SHADOW_Y_DIM (0.80 - SHADOW_Y_OFFSET)
#define SHADOW_RADIUS 0.075

/* Old version saved, just in case */
#if 0
static void checkShadowCollision(waterfall_t *w)
    {
    int i;
    float dx,dy;
    int pcol, prow, bytesReceived;
    bytesReceived = udpReceive(shadowSocket,(unsigned char *)shadowPacket,
                               shadowPacketMaxSize);
    if ((shadowPacket->rows <= 0) || (shadowPacket->cols <= 0))
        return;
    for (i=0; i < w->numParticles; i++)
        {
        if (w->particle[i].vy < 0)
            {
            pcol = w->particle[i].x0 * (shadowPacket->cols-1) + 0.5;
            prow = w->particle[i].y0 * (shadowPacket->rows-1) + 0.5 - 1;
            pcol = (w->particle[i].x0-SHADOW_X_OFFSET)/SHADOW_X_DIM * (shadowPacket->cols-1) + 0.5;
            prow = (w->particle[i].y0-SHADOW_Y_OFFSET)/SHADOW_Y_DIM * (shadowPacket->rows-1) + 0.5 - 1;
            if ((pcol >= 0) && (pcol < shadowPacket->cols) &&
                (prow >= 0) && (prow < shadowPacket->rows) &&
                (shadowPacket->data[pcol+prow*shadowPacket->cols]))
                {
                w->particle[i].vx = (drand48() - 0.5) * MAX_VY;
                w->particle[i].vy = -MAX_VY;
                w->particle[i].state = BOUNCED;
                }
            }
        }
    }
#else
static void checkShadowCollision(waterfall_t *w)
    {
    int bytesReceived;
    int row,col,i,p,loop;
    float width,height;
    bytesReceived = udpReceive(shadowSocket,(unsigned char *)shadowPacket,
                               shadowPacketMaxSize);
    if ((shadowPacket->rows <= 0) || (shadowPacket->cols <= 0))
        return;
    width = SHADOW_X_DIM / shadowPacket->cols;
    height = SHADOW_Y_DIM / shadowPacket->rows;

    for (p=0; p < w->numParticles; p++)
        {
        if (w->particle[p].vy < 0)
            {
            for (row=0, i=0, loop=1; (row < shadowPacket->rows) && (loop); row++)
                for (col=0; (col < shadowPacket->cols) && (loop); col++, i++)
                    {
                    if (shadowPacket->data[i])
                        {
                        if (checkCollision(&w->particle[p],
                                           col * width+SHADOW_X_OFFSET,
                                           row * height+SHADOW_Y_OFFSET,
                                           SHADOW_RADIUS))
                            loop = 0;
                        }
                    }
            }
        }
    }
#endif
 
 
static void checkMouseCollision(waterfall_t *w)
    {
    int i;
    float dx,dy;
    for (i=0; i < w->numParticles; i++)
        {
        if (w->particle[i].vy < 0)
            checkCollision(&w->particle[i], blockX, blockY, blockSize);
        }
    }


static int checkCollision(particle_t *particle,float x,float y,float radius)
    {
    float dx,dy;
    dx = particle->x0 - x;
    dy = particle->y0 - y;
    if (((dx*dx+dy*dy) < radius*radius) && (dy > 0))
        {
        float len = sqrtf(dx*dx+dy*dy);
        float speed = sqrtf(particle->vx*particle->vx +
                            particle->vy*particle->vy);
        if (len < 0.01)
            len = 0.01;
        particle->vx = dx / len * speed * 0.5f;
        if (dx < 0)
            particle->vx = MAX_VY;
        else
            particle->vx = -MAX_VY;
        particle->vy = dy / len * speed * 0.5f;
        particle->state = BOUNCED;
        return 1;
        }
    return 0;
    }

