#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#include <GLUT/glut.h>

#include <chipmunk.h>

#define max(x, y) ({                \
    __typeof__(x) __x = x, __y = y; \
    (__x < __y ? __y : __x);        \
})

#define min(x, y) ({                \
    __typeof__(x) __x = x, __y = y; \
    (__x > __y ? __y : __x);        \
})

enum
{
    TILE_KIND_WALL,
    TILE_KIND_EMPTY
};

typedef uint32_t Tile;

#define LEVEL_WIDTH 40
#define LEVEL_HEIGHT 25

#define TILE_SIZE 20

static Tile level[LEVEL_WIDTH * LEVEL_HEIGHT];

enum
{
    ENTITY_KIND_FAUCET
};

#define TILE_AT(x, y) (*({             \
    uint32_t __x = x;                  \
    uint32_t __y = y;                  \
    level + (__x + LEVEL_WIDTH * __y); \
}))

typedef uint32_t Entity_Kind;

typedef struct Entity
{
    Entity_Kind kind;
    uint16_t x, y;
    
    union
    {
        struct
        {
            uint32_t flow;
            uint32_t tick;
        }
        faucet;
    }
    entity;
}
Entity;

#define MAX_ENTITIES 10

static uint32_t entity_count;
static Entity entities[MAX_ENTITIES];

#define CYCLE_MS 10
#define CYCLES_PER_SEC 100

static int last_frame_time;
static int time_left_over;

cpSpace *space;
cpBody  *scenery;

#define MAX_WATER 10000

uint32_t water_count;
cpBody *water[MAX_WATER];

#define WATER_RADIUS 3.0

void draw_tile(uint32_t x, uint32_t y)
{
    Tile tile = TILE_AT(x, y);
    
    if (tile == TILE_KIND_WALL)
    {
        glColor3f(1, 0, 0);
        glBegin(GL_QUADS);
        glVertex2f((x + 0) * TILE_SIZE, (y + 0) * TILE_SIZE);
        glVertex2f((x + 1) * TILE_SIZE, (y + 0) * TILE_SIZE);
        glVertex2f((x + 1) * TILE_SIZE, (y + 1) * TILE_SIZE);
        glVertex2f((x + 0) * TILE_SIZE, (y + 1) * TILE_SIZE);
        glEnd();
        glColor3f(1, 1, 1);
    }
    else if (tile == TILE_KIND_EMPTY)
    {
        
    }
    else
    {
        abort();
    }
}

void draw_entity(uint32_t i)
{
    Entity_Kind kind = entities[i].kind;
    float x = entities[i].x;
    float y = entities[i].y;
    
    if (kind == ENTITY_KIND_FAUCET)
    {
        glColor3f(0, 1, 1);
        glBegin(GL_QUADS);
        glVertex2f((x + 0.25f) * TILE_SIZE, (y + 0.25f) * TILE_SIZE);
        glVertex2f((x + 0.75f) * TILE_SIZE, (y + 0.25f) * TILE_SIZE);
        glVertex2f((x + 0.75f) * TILE_SIZE, (y + 0.75f) * TILE_SIZE);
        glVertex2f((x + 0.25f) * TILE_SIZE, (y + 0.75f) * TILE_SIZE);
        glEnd();
        glColor3f(1, 1, 1);
    }
    else
    {
        abort();
    }
}

void draw_water(uint32_t i)
{
    cpVect p = water[i]->p;
    glColor3f(0, 0, 1);
    glBegin(GL_QUADS);
    glVertex2f(p.x - WATER_RADIUS, p.y - WATER_RADIUS);
    glVertex2f(p.x + WATER_RADIUS, p.y - WATER_RADIUS);
    glVertex2f(p.x + WATER_RADIUS, p.y + WATER_RADIUS);
    glVertex2f(p.x - WATER_RADIUS, p.y + WATER_RADIUS);
    glEnd();
}

void update(void)
{
    for (uint32_t i = 0; i < entity_count; ++i)
    {
        if (entities[i].kind == ENTITY_KIND_FAUCET)
        {
            ++entities[i].entity.faucet.tick;
            if (water_count < MAX_WATER &&
                entities[i].entity.faucet.tick ==
                    entities[i].entity.faucet.flow)
            {
                entities[i].entity.faucet.tick = 0;
                
                cpBody *dropletBody = cpBodyNew(1.0, INFINITY);
                dropletBody->p = cpv(
                    TILE_SIZE * (entities[i].x + 0.5) + rand() % 3 - 1,
                    TILE_SIZE * (entities[i].y + 0.5) + rand() % 3 - 1);
                cpSpaceAddBody(space, dropletBody);
                cpShape *dropletShape = cpCircleShapeNew(
                    dropletBody, WATER_RADIUS, cpvzero);
                dropletShape->e = 1.0;
                dropletShape->u = 0.0;
                cpSpaceAddShape(space, dropletShape);
                water[water_count++] = dropletBody;
            }
        }
        else
        {
            abort();
        }
    }
    
    cpSpaceStep(space, CYCLE_MS * 0.001);
}

void display(void)
{
    int now = glutGet(GLUT_ELAPSED_TIME);
    int dt = now - last_frame_time + time_left_over;
    last_frame_time = now;
    
    while (dt >= CYCLE_MS)
    {
        update();
        dt -= CYCLE_MS;
    }
    time_left_over = dt;
    
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, 800, 500, 0, -1, 1);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    
    for (uint32_t y = 0; y < LEVEL_HEIGHT; ++y)
    {
        for (uint32_t x = 0; x < LEVEL_WIDTH; ++x)
        {
            draw_tile(x, y);
        }
    }
    
    for (uint32_t i = 0; i < entity_count; ++i)
    {
        draw_entity(i);
    }
    
    for (uint32_t i = 0; i < water_count; ++i)
    {
        draw_water(i);
    }
    
    glutSwapBuffers();
}

void reshape(int width, int height)
{
    glViewport(0, 0, width, height);
}

void idle(void)
{
    glutPostRedisplay();
}

cpVect WALL_VERTS[4] = {
    { 0, TILE_SIZE }, { TILE_SIZE, TILE_SIZE }, { TILE_SIZE, 0 }, { 0, 0 }
};

void read_level(char const *path)
{
    FILE *f = fopen(path, "rb");
    if (!f)
    {
        fprintf(stderr, "can't open %s\n", path);
        exit(EXIT_FAILURE);
    }
    for (uint32_t y = 0; y < LEVEL_HEIGHT; ++y)
    {
        for (uint32_t x = 0; x < LEVEL_WIDTH; ++x)
        {
            int c = fgetc(f);
            if (c == '#')
            {
                TILE_AT(x, y) = TILE_KIND_WALL;
                
                cpShape *shape = cpPolyShapeNew(
                    scenery,
                    4,
                    WALL_VERTS,
                    cpv(x * TILE_SIZE, y * TILE_SIZE));
                cpSpaceAddStaticShape(space, shape);
            }
            else
            {
                TILE_AT(x, y) = TILE_KIND_EMPTY;
                
                if (c == ' ')
                {
                    
                }
                else if (c == 'F')
                {
                    if (entity_count == MAX_ENTITIES)
                    {
                        fprintf(stderr, "exceeded max entities (%c) in %s\n", c, path);
                        exit(EXIT_FAILURE);
                    }
                    entities[entity_count].kind = ENTITY_KIND_FAUCET;
                    entities[entity_count].x = x;
                    entities[entity_count].y = y;
                    entities[entity_count].entity.faucet.flow = 10;
                    entities[entity_count].entity.faucet.tick = 0;
                    entity_count ++;
                }
                else
                {
                    fprintf(stderr, "syntax error (%c) in %s\n", c, path);
                    exit(EXIT_FAILURE);
                }
            }
        }
        int c = fgetc(f);
        if (c != '\n')
        {
            fprintf(stderr, "syntax error (%c) in %s\n", c, path);
            exit(EXIT_FAILURE);
        }
    }
}

int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    
    glutInitDisplayMode(GLUT_RGB | GLUT_ALPHA | GLUT_DOUBLE | GLUT_DEPTH);
    glutInitWindowSize(800, 500);
    
    glutCreateWindow("WT");
    
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutIdleFunc(idle);
    
    cpInitChipmunk();
    space = cpSpaceNew();
    space->gravity = cpv(0.0, 5.0 * TILE_SIZE);
    scenery = cpBodyNew(INFINITY, INFINITY);
    
    read_level("level.txt");
    last_frame_time = glutGet(GLUT_ELAPSED_TIME);
    
    glutMainLoop();
    return EXIT_SUCCESS;
}
