typedef struct Flake Flake;
typedef struct Game Game;
#include <allegro5/allegro.h>
#include <allegro5/allegro_primitives.h>
#include <stdlib.h>
#include <math.h>
#define FPS 60
#define MAX_SPRITES 1024
typedef ALLEGRO_COLOR Color;
typedef ALLEGRO_BITMAP Bitmap;
Color col(float r, float g, float b, float a);
struct Flake {
    float x, y, dx, dy;
    float a, s;
    int t;
    bool remove;
};
struct Game {
    Flake sprites [MAX_SPRITES];
    Flake claus;
    bool use_memory_bitmaps;
    int blending;
    ALLEGRO_DISPLAY * display;
    int sprite_count;
    Bitmap * back;
    Bitmap * snow;
    Bitmap * sheet;
    Bitmap * tree;
    Bitmap * santa;
    Bitmap * (* snowflakes);
    int * mask;
    float direct_speed_measure;
    int ftpos;
    float frame_times [FPS];
    bool hold;
    float x, y;
    int preparation_time;
};
float rnd(float a, float b);
void flake_add(float x, float y);
void make_background(void);
void make_snow(void);
void sline(float x, float y, float x2, float y2);
void make_snowflake(Bitmap * sub, int n);
void make_snowflakes(void);
void tree_segment(float x, float y, float h);
void make_tree(Bitmap * sub);
void make_santa(Bitmap * sub);
void putmask(float x, float y, int v);
int getmask(float x, float y);
void place_tree(float x, float y);
void place_hill(float x, float y, float r);
void place_hills(void);
void place_trees(void);
void init(void);
void flake_update(Flake * f);
void santa_update(void);
void update(void);
void flake_draw(Flake * f);
void claus_draw(void);
void redraw(void);
int main(int argc, char * (* argv));

Color col(float r, float g, float b, float a) {
    return al_premul_rgba_f(r, g, b, a);
}
int w = 1280, h = 720;
int sw = 640, sh = 360;
float const pi = ALLEGRO_PI;
Game g;
float rnd(float a, float b) {
    return a + (b - a) * (rand() / (float) RAND_MAX);
}
void flake_add(float x, float y) {
    if (g.sprite_count >= MAX_SPRITES) {
        return ;
    }
    x += rnd(- 8, 8);
    if (x < 2) {
        return ;
    }
    if (x >= w - 2) {
        return ;
    }
    int i = g.sprite_count++;
    Flake * f = g.sprites + i;
    f->x = x;
    f->y = 0;
    f->dx = 0;
    f->a = rnd(0, 2 * pi);
    f->s = rnd(0.3, 1);
    f->t = rand() % 5;
    f->dy = 0.5 + f->s * 0.5 * rnd(0.8, 1.0);
    f->remove = false;
}
void make_background(void) {
    g.back = al_create_bitmap(w, h);
    ALLEGRO_LOCKED_REGION * r = al_lock_bitmap(g.back, ALLEGRO_PIXEL_FORMAT_ABGR_8888_LE, ALLEGRO_LOCK_WRITEONLY);
    for (int i = 0; i < h; i++) {
        int red = 0;
        int green = 0;
        int blue = 100 * i / h;
        for (int j = 0; j < w; j++) {
            uint8_t * p = r->data;
            p += r->pitch * i;
            p [4 * j + 0] = red;
            p [4 * j + 1] = green;
            p [4 * j + 2] = blue;
            p [4 * j + 3] = 255;
        }
    }
    al_unlock_bitmap(g.back);
}
void make_snow(void) {
    g.snow = al_create_bitmap(w, h);
    g.mask = al_calloc(sw * sh, sizeof (* g.mask));
    for (int i = 0; i < sw; i++) {
        g.mask [(sh - 1) * sw + i] = 1;
    }
    for (int i = 0; i < sh; i++) {
        g.mask [i * sw] = 1;
        g.mask [i * sw + sw - 1] = 1;
    }
}
void sline(float x, float y, float x2, float y2) {
    Color c1 = al_premul_rgba_f(.8, .8, 1, .5);
    Color c2 = al_map_rgb_f(1, 1, 1);
    al_draw_line(x, y, x2, y2, c1, 3);
    al_draw_line(x, y, x2, y2, c2, 1);
}
void make_snowflake(Bitmap * sub, int n) {
    al_set_target_bitmap(sub);
    float x = 20;
    float y = 20;
    float r = 19;
    for (int i = 0; i < 6; i++) {
        float a = pi * 2 * i / 6;
        float dx = r * cos(a);
        float dy = r * sin(a);
        float nx = dy;
        float ny = - dx;
        for (int j = 0; j < n; j++) {
            float s = (1.0 + j) / (n + 1.0);
            float x2 = x + dx * (1 - s);
            float y2 = y + dy * (1 - s);
            sline(x2 - nx * s, y2 - ny * s, x2 + nx * s, y2 + ny * s);
        }
    }
    for (int i = 0; i < 6; i++) {
        float a = pi * 2 * i / 6;
        float dx = r * cos(a);
        float dy = r * sin(a);
        sline(x, y, x + dx, y + dy);
    }
    al_set_target_backbuffer(al_get_current_display());
}
void make_snowflakes(void) {
    g.snowflakes = al_calloc(32 * 18, sizeof (* g.snowflakes));
    for (int b = 0; b < 5; b++) {
        Bitmap * sub = al_create_sub_bitmap(g.sheet, b * 40, 0, 40, 40);
        g.snowflakes [b] = sub;
        make_snowflake(sub, b);
    }
}
void tree_segment(float x, float y, float h) {
    Color c = al_map_rgb(0, 150, 50);
    float v [] = {x, y - 2, x - h - 2, y + h + 2, x, y + h * 3 / 4 + 2, x + h + 2, y + h + 2};
    al_draw_filled_polygon(v, 4, c);
    c = al_map_rgb(0, 200, 0);
    float v2 [] = {x, y, x - h, y + h, x, y + h * 3 / 4, x + h, y + h};
    al_draw_filled_polygon(v2, 4, c);
}
void make_tree(Bitmap * sub) {
    al_set_target_bitmap(sub);
    float y = 50;
    float h = 40;
    for (int i = 0; i < 5; i++) {
        tree_segment(40, y, h);
        h /= 1.5;
        y -= h / 1.5;
    }
    al_set_target_backbuffer(al_get_current_display());
}
void make_santa(Bitmap * sub) {
    float h = 25;
    float x = 40;
    float y = 70;
    al_set_target_bitmap(sub);
    al_draw_filled_ellipse(x - h * 0.5, y - h, h * 0.7, h / 2, al_map_rgb_f(0.9, 0.8, 0));
    al_draw_filled_ellipse(x + h * 0.5, y - h, h * 0.5, h / 2, al_map_rgb_f(0.8, 0, 0.1));
    al_draw_filled_ellipse(x + h * 0.5, y - h * 1.5, h * 0.3, h * 0.3, al_map_rgb_f(1, 1, 1));
    al_draw_filled_ellipse(x + h * 0.7, y - h * 1.5, h * 0.1, h * 0.1, al_map_rgb_f(0, 0, 0));
    al_draw_filled_ellipse(x + h * 0.4, y - h * 1.8, h * 0.3, h * 0.3, al_map_rgb_f(0.8, 0, 0.1));
    al_draw_filled_ellipse(x, y, h * 1.2, h * 0.2, al_map_rgb_f(0.5, 0.3, 0.2));
    al_draw_filled_rectangle(x - h, y - h, x + h, y, al_map_rgb_f(0.5, 0.3, 0.2));
    al_set_target_backbuffer(al_get_current_display());
}
void putmask(float x, float y, int v) {
    int ix = x / 2;
    int iy = y / 2;
    if (ix < 0 || iy < 0 || ix >= sw || iy >= sh) {
        return ;
    }
    int * m = g.mask + sw * iy + ix;
    m [0] = v;
}
int getmask(float x, float y) {
    int ix = x / 2;
    int iy = y / 2;
    if (ix < 0 || iy < 0 || ix >= sw || iy >= sh) {
        return 0;
    }
    int * m = g.mask + sw * iy + ix;
    return m [0];
}
void place_tree(float x, float y) {
    al_set_target_bitmap(g.snow);
    al_draw_bitmap(g.tree, x - 40, y - 80, 0);
    al_set_target_backbuffer(al_get_current_display());
    for (int i = 0; i < 80; i++) {
        for (int j = 0; j < 80; j++) {
            int id = abs(i - 40);
            if (id * 4 < j) {
                putmask(x - 40 + i, y - 80 + j, 1);
            }
        }
    }
}
void place_hill(float x, float y, float r) {
    al_draw_filled_circle(x, y, r, col(1, 1, 1, 1));
    for (int i = - r; i < r; i += 1) {
        for (int j = - r; j < r; j += 1) {
            if (i * i + j * j < r * r) {
                putmask(x + i, y + j, 1);
            }
        }
    }
}
void place_hills(void) {
    al_set_target_bitmap(g.snow);
    for (int i = 0; i < 10; i++) {
        float x = rnd(w / 10, w);
        float r = rnd(w / 20, w / 10);
        float y = rnd(0, r * 0.4);
        place_hill(x, h + y, r);
    }
    al_set_target_backbuffer(al_get_current_display());
}
void place_trees(void) {
    int treesx [20], treesy [20];
    for (int i = 0; i < 20; i++) {
        float x = rnd(100, w - 100);
        for (int j = h - 1; j > 0; j--) {
            if (getmask(x, j) == 0) {
                treesx [i] = x;
                treesy [i] = j;
                break;
            }
        }
    }
    for (int i = 0; i < 20; i += 1) {
        place_tree(treesx [i], treesy [i]);
    }
}
void init(void) {
    make_background();
    make_snow();
    g.sheet = al_create_bitmap(w, h);
    make_snowflakes();
    g.tree = al_create_sub_bitmap(g.sheet, 0, 40, 80, 80);
    make_tree(g.tree);
    g.santa = al_create_sub_bitmap(g.sheet, 80, 40, 80, 80);
    make_santa(g.santa);
    place_hills();
    place_trees();
    g.claus.x = 0;
    g.claus.y = h - 10;
    g.preparation_time = 60 * 10;
}
void flake_update(Flake * f) {
    int w = al_get_display_width(g.display);
    int h = al_get_display_height(g.display);
    f->x += f->dx;
    f->y += f->dy * 2;
    int c = 1;
    int ix = f->x / 2;
    int iy = f->y / 2;
    if (iy > sh - 1) {
        iy = sh - 1;
    }
    int * m = g.mask + iy * sw + ix;
    if (* m) {
        int * ml = g.mask + iy * sw + ix - 1;
        int * mr = g.mask + iy * sw + ix + 1;
        if (! (* ml)) {
            f->x -= 2;
        }
        else if (! (* mr)) {
            f->x += 2;
        }
        else {
            * (m - sw) = 1;
            * (m - sw - sw) = 1;
            * (m - sw - 1) = 1;
            * (m - sw + 1) = 1;
            al_set_target_bitmap(g.snow);
            Color c = al_map_rgb(255, 255, 255);
            float v [] = {ix * 2 - 5, iy * 2, ix * 2 + 5, iy * 2, ix * 2, iy * 2 - 5};
            al_draw_filled_polygon(v, 3, c);
            al_set_target_backbuffer(al_get_current_display());
            f->remove = true;
        }
    }
}
void santa_update(void) {
    if (g.preparation_time > 0) {
        return ;
    }
    float rx = cos(g.claus.a);
    float ry = sin(g.claus.a);
    float ux = ry;
    float uy = - rx;
    g.claus.x += rx;
    g.claus.y += ry;
    float fx = (g.claus.x + rx * 30 + ux * 10);
    float fy = (g.claus.y + ry * 30 + uy * 10);
    if (getmask(fx, fy)) {
        if (! getmask(fx, fy - 1)) {
            g.claus.y -= 1;
        }
        else {
            g.claus.x -= rx;
            g.claus.y -= ry;
            g.claus.a -= pi / 360.0;
        }
    }
    else if (! getmask(fx, fy + 1)) {
        if (! getmask(fx - rx * 30, fy - ry * 30 + 1)) {
            g.claus.y += 1;
        }
        g.claus.a += pi / 360.0;
    }
}
void update(void) {
    int i;
    for (i = 0; i < g.sprite_count; i++) {
        flake_update(g.sprites + i);
    }
    for (i = 0; i < g.sprite_count; i++) {
        Flake * f = g.sprites + i;
        if (f->remove) {
            g.sprites [i] = g.sprites [g.sprite_count - 1];
            i--;
            g.sprite_count--;
        }
    }
    if (g.preparation_time > 0) {
        g.preparation_time--;
    }
    santa_update();
    if (g.claus.a > pi * 0.4 || g.claus.a < - pi * 0.4) {
        g.claus.x = 0;
        g.claus.y = h - 10;
        g.claus.a = 0;
    }
}
void flake_draw(Flake * f) {
    float x = f->x;
    float y = f->y;
    float s = f->s * (2 - y / h) / 2;
    al_draw_scaled_rotated_bitmap(g.snowflakes [f->t], 10, 10, x, y, s, s, f->a, 0);
}
void claus_draw(void) {
    al_draw_rotated_bitmap(g.santa, 40, 80, g.claus.x, g.claus.y, g.claus.a, 0);
}
void redraw(void) {
    al_draw_bitmap(g.back, 0, 0, 0);
    al_draw_bitmap(g.snow, 0, 0, 0);
    al_hold_bitmap_drawing(true);
    claus_draw();
    for (int i = 0; i < g.sprite_count; i++) {
        Flake * s = g.sprites + i;
        flake_draw(s);
    }
    al_hold_bitmap_drawing(false);
    al_draw_filled_rectangle(0, h - g.preparation_time * h / 600, 8, h, col(1, 0, 0, 1));
}
int main(int argc, char * (* argv)) {
    ALLEGRO_TIMER * timer;
    ALLEGRO_EVENT_QUEUE * queue;
    bool done = false;
    bool need_redraw = true;
    al_init();
    al_init_primitives_addon();
    al_set_new_bitmap_flags(ALLEGRO_MIN_LINEAR);
    g.display = al_create_display(w, h);
    al_install_keyboard();
    al_install_mouse();
    init();
    timer = al_create_timer(1.0 / FPS);
    queue = al_create_event_queue();
    al_register_event_source(queue, al_get_keyboard_event_source());
    al_register_event_source(queue, al_get_mouse_event_source());
    al_register_event_source(queue, al_get_timer_event_source(timer));
    al_register_event_source(queue, al_get_display_event_source(g.display));
    al_start_timer(timer);
    while (! done) {
        float x, y;
        ALLEGRO_EVENT event;
        w = al_get_display_width(g.display);
        h = al_get_display_height(g.display);
        if (need_redraw && al_is_event_queue_empty(queue)) {
            al_clear_to_color(al_map_rgb_f(0, 0, 0));
            redraw();
            al_flip_display();
            need_redraw = false;
        }
        al_wait_for_event(queue, & event);
        switch ((event.type)) {
            case ALLEGRO_EVENT_KEY_CHAR: {
                if (event.keyboard.keycode == ALLEGRO_KEY_ESCAPE) {
                    done = true;
                }
                break;
            }
            case ALLEGRO_EVENT_DISPLAY_CLOSE: {
                done = true;
                break;
            }
            case ALLEGRO_EVENT_TIMER: {
                update();
                need_redraw = true;
                break;
            }
            case ALLEGRO_EVENT_MOUSE_AXES: {
                g.x = event.mouse.x;
                g.y = event.mouse.y;
                break;
            }
            case ALLEGRO_EVENT_MOUSE_BUTTON_DOWN: {
                g.hold = true;
                break;
            }
            case ALLEGRO_EVENT_MOUSE_BUTTON_UP: {
                g.hold = false;
                break;
            }
        }
        if (g.hold) {
            flake_add(g.x, g.y);
        }
    }
    return 0;
}
