#include "block.h"
#include "render.h"
#include "land/util2d.h"
// x/z is the plane
// y is up/down
static int tag = 0;
static bool is_left(float * v, float xp, float yp, int i1, int i2);
static float sgn(float a, float x);
void blocks_list_destroy(LandArray * (* list)) {
    if (* list) {
        {
            LandArrayIterator __iter0__ = LandArrayIterator_first(* list);
            for (Block * b = LandArrayIterator_item(* list, &__iter0__); LandArrayIterator_next(* list, &__iter0__); b = LandArrayIterator_item(* list, &__iter0__)) {
                b->block_type->destroy(b);
            }
        }
        land_array_destroy(* list);
        * list = NULL;
    }
}
void blocks_clear(Blocks * self) {
    blocks_list_destroy(& self->fixed);
    blocks_list_destroy(& self->dynamic);
    blocks_list_destroy(& self->transparent);
    if (self->animated) {
        land_array_destroy(self->animated);
        self->animated = NULL;
    }
}
void blocks_reset(Blocks * self) {
    blocks_clear(self);
    self->fixed = land_array_new();
    self->dynamic = land_array_new();
    self->transparent = land_array_new();
    self->animated = land_array_new();
    self->rebuild_static_cache = 1;
    self->rebuild_dynamic_cache = 1;
}
Blocks* blocks_new(void) {
    Blocks * b;
    land_alloc(b);
    return b;
}
void blocks_destroy(Blocks * self) {
    blocks_clear(self);
    land_free(self);
}
Block* blocks_pick(Blocks * self, float xp, float yp, Viewport * viewport) {
    Block * best = NULL;
    xp /= viewport->zoom;
    yp /= viewport->zoom;
    LandArray * arrays [] = {self->transparent, self->dynamic, self->fixed};
    for (int i = 0; i < 3; i += 1) {
        LandArray * array = arrays [i];
        {
            LandArrayIterator __iter0__ = LandArrayIterator_first(array);
            for (Block * block = LandArrayIterator_item(array, &__iter0__); LandArrayIterator_next(array, &__iter0__); block = LandArrayIterator_item(array, &__iter0__)) {
                int side = block_is_inside(block, xp, yp, viewport);
                if (side) {
                    if (best) {
                        if (block_sort_order(block, best, viewport) > 0) {
                            best = block;
                        }
                    }
                    else {
                        best = block;
                    }
                }
            }
        }
    }
    return best;
}
void block_init(Block * self, Blocks * blocks, float x, float y, float z, BlockType * block_type) {
    self->blocks = blocks;
    self->x = x;
    self->y = y;
    self->z = z;
    self->block_type = block_type;
    if (block_type) {
        self->xs = block_type->xs;
        self->ys = block_type->ys;
        self->zs = block_type->zs;
    }
    self->r = 1;
    self->g = 1;
    self->b = 1;
    self->a = 1;
    self->frame = 0;
    self->dx = 0;
    self->dy = 0;
    self->dz = 0;
    self->ground = 0;
    self->recursion_prevention = 0;
    self->no_fall = 0;
    self->cache = land_array_new();
}
Block* block_new(Blocks * blocks, float x, float y, float z, BlockType * block_type) {
    Block * self = block_type->allocate();
    block_init(self, blocks, x, y, z, block_type);
    self->block_type->post_init(self);
    return self;
}
void block_destroy(Block * self) {
    land_array_destroy(self->cache);
    land_free(self);
}
Block* block_allocate(void) {
    Block * self;
    land_alloc(self);
    return self;
}
void block_post_init(Block * self) {
    return ;
}
void block_add(Block * self) {
    self->bid = 1 + land_array_count(self->blocks->fixed) + land_array_count(self->blocks->dynamic);
    if (self->block_type && self->block_type->dynamic) {
        land_array_add(self->blocks->dynamic, self);
    }
    else if (self->block_type && self->block_type->transparent) {
        land_array_add(self->blocks->transparent, self);
    }
    else {
        land_array_add(self->blocks->fixed, self);
    }
    if (self->block_type && self->block_type->animated) {
        land_array_add(self->blocks->animated, self);
    }
}
void block_add_if_no_overlap(Block * self) {
    block_add(self);
    LandArray * cl = block_colliders(self);
    if (! land_array_is_empty(cl)) {
        block_del(self);
    }
    land_array_destroy(cl);
}
Block* block_change_type(Block * self, int d) {
    int n = land_array_count(block_types);
    int btid = self->block_type->btid + d;
    if (btid >= n) {
        btid -= n;
    }
    if (btid < 0) {
        btid += n;
    }
    BlockType * bt = land_array_get_nth(block_types, btid);
    Blocks * blocks = self->blocks;
    float x = self->x;
    float y = self->y;
    float z = self->z;
    block_del(self);
    self = block_new(blocks, x, y, z, bt);
    block_add(self);
    return self;
}
Block* block_change_type_to(Block * self, char const * text) {
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(block_types);
        for (BlockType * bt = LandArrayIterator_item(block_types, &__iter0__); LandArrayIterator_next(block_types, &__iter0__); bt = LandArrayIterator_item(block_types, &__iter0__)) {
            if (land_equals(bt->name, text)) {
                return block_change_type(self, bt->btid - self->block_type->btid);
            }
        }
    }
    return self;
}
void block_del(Block * self) {
    LandArray * array;
    if (self->block_type && self->block_type->dynamic) {
        array = self->blocks->dynamic;
    }
    else if (self->block_type && self->block_type->transparent) {
        array = self->blocks->transparent;
    }
    else {
        array = self->blocks->fixed;
    }
    int i = land_array_find(array, self);
    if (i < 0) {
        return ;
    }
    land_array_swap(array, i, - 1);
    land_array_pop(array);
    i = land_array_find(self->blocks->animated, self);
    if (i >= 0) {
        land_array_swap(self->blocks->animated, i, - 1);
        land_array_pop(self->blocks->animated);
    }
    self->blocks->rebuild_static_cache = 1;
    self->blocks->rebuild_dynamic_cache = 1;
    self->block_type->destroy(self);
}
bool block_overlaps(Block * self, Block * other) {
    if ((other->x + other->xs > self->x && self->x + self->xs > other->x && other->y + other->ys > self->y && self->y + self->ys > other->y && other->z + other->zs > self->z && self->z + self->zs > other->z)) {
        return 1;
    }
    return 0;
}
bool block_center_overlaps(Block * super, Block * c) {
    float cx = super->x + super->xs / 2;
    float cz = super->z + super->zs / 2;
    if (cx > c->x && cx < c->x + c->xs && cz > c->z && cz < c->z + c->zs) {
        return 1;
    }
    return 0;
}
LandArray* block_colliders(Block * self) {
    int n1, n2, n3;
    LandArray * r = land_array_new();
    n1 = land_array_count(self->blocks->fixed);
    n2 = land_array_count(self->blocks->dynamic);
    n3 = land_array_count(self->blocks->transparent);
    for (int i = 0; i < n1 + n2 + n3; i += 1) {
        Block * other;
        if (i < n1) {
            other = land_array_get_nth(self->blocks->fixed, i);
        }
        else if (i - n1 < n2) {
            other = land_array_get_nth(self->blocks->dynamic, i - n1);
        }
        else {
            other = land_array_get_nth(self->blocks->transparent, i - n1 - n2);
        }
        if (other == self) {
            continue;
        }
        if (block_overlaps(self, other)) {
            land_array_add(r, other);
        }
    }
    return r;
}
float check_ground(Block * self, float x, float z) {
    float y = - 9000;
    int n1, n2, n3;
    n1 = land_array_count(game->blocks->fixed);
    n2 = land_array_count(game->blocks->dynamic);
    n3 = land_array_count(game->blocks->transparent);
    for (int i = 0; i < n1 + n2 + n3; i += 1) {
        Block * other;
        if (i < n1) {
            other = land_array_get_nth(game->blocks->fixed, i);
        }
        else if (i - n1 < n2) {
            other = land_array_get_nth(game->blocks->dynamic, i - n1);
        }
        else {
            other = land_array_get_nth(game->blocks->transparent, i - n1 - n2);
        }
        if (other == self) {
            continue;
        }
        if ((x >= other->x && x < other->x + other->xs && z >= other->z && z < other->z + other->zs)) {
            if (other->y + other->ys > y) {
                y = other->y + other->ys;
            }
        }
    }
    return y;
}
bool block_move(Block * self, float dx, float dy, float dz) {
    tag += 1;
    self->recursion_prevention = tag;
    self->mover = 1;
    bool r = block_push(self, dx, dy, dz);
    self->mover = 0;
    return r;
}
void move_on_top(Block * self, float dx, float dy, float dz) {
    float on_top_y = self->y;
    self->y += 1;
    LandArray * top = block_colliders(self);
    self->y = on_top_y;
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(top);
        for (Block * c = LandArrayIterator_item(top, &__iter0__); LandArrayIterator_next(top, &__iter0__); c = LandArrayIterator_item(top, &__iter0__)) {
            if (c->block_type->dynamic && ! c->block_type->fixed) {
                if (c->recursion_prevention != tag) {
                    c->recursion_prevention = tag;
                    block_push(c, dx, dy, dz);
                }
            }
        }
    }
    land_array_destroy(top);
}
float block_distance(Block * self, Block * other) {
    float dx = 0;
    float dy = 0;
    float dz = 0;
    if (self->x > other->x + other->xs) {
        dx = self->x - (other->x + other->xs);
    }
    if (other->x > self->x + self->xs) {
        dx = other->x - (self->x + self->xs);
    }
    if (self->y > other->y + other->ys) {
        dy = self->y - (other->y + other->ys);
    }
    if (other->y > self->y + self->ys) {
        dy = other->y - (self->y + self->ys);
    }
    if (self->z > other->z + other->zs) {
        dz = self->z - (other->z + other->zs);
    }
    if (other->z > self->z + self->zs) {
        dz = other->z - (self->z + self->zs);
    }
    return sqrt(dx * dx + dy * dy + dz * dz);
}
bool block_push(Block * self, float odx, float ody, float odz) {
    bool r;
    float ox = self->x;
    float oy = self->y;
    float oz = self->z;
    bool first_after_push = 1;
    bool first_ramp_up = 1;
    int first_single_dir = 2;
    retry:;
    float dx = first_single_dir != 0 ? odx : 0;
    float dy = ody;
    float dz = first_single_dir != 1 ? odz : 0;
    self->x += dx;
    if (self->y < 9000) {
        self->y += dy;
    }
    self->z += dz;
    if (self->y < - 9000) {
        self->y = - 9000;
    }
    LandArray * cs = block_colliders(self);
    if (land_array_count(cs)) {
        bool retry_after_push = 0;
        self->x = ox;
        self->y = oy;
        self->z = oz;
        {
            LandArrayIterator __iter0__ = LandArrayIterator_first(cs);
            for (Block * c = LandArrayIterator_item(cs, &__iter0__); LandArrayIterator_next(cs, &__iter0__); c = LandArrayIterator_item(cs, &__iter0__)) {
                bool can_push = self->block_type->touch(self, c, dx, dy, dz);
                // don't get stuck at the very top pixel
                if (self->y <= c->y + c->ys && self->y > c->y + c->ys - 1) {
                    self->y = c->y + c->ys;
                    continue;
                }
                if (! can_push) {
                    continue;
                }
                // prevent "pushing" something that's actually on top
                if ((dy > 0 || c->y < self->y + self->ys) && c->block_type->dynamic && ! c->block_type->fixed) {
                    if (c->recursion_prevention != tag) {
                        c->recursion_prevention = tag;
                        if (block_push(c, dx, dy, dz)) {
                            //print("%d %s -> %s %f", tag, self->block_type->name, c->block_type->name, dy)
                            self->pushed_something = 1;
                            retry_after_push = 1;
                        }
                    }
                }
            }
        }
        r = 0;
        land_array_destroy(cs);
        if (first_after_push && retry_after_push) {
            first_after_push = 0;
            goto retry;
        }
        else if (first_ramp_up && dy == 0) {
            first_ramp_up = 0;
            self->y += 1;
            goto retry;
        }
        // only an active mover can do single directions, otherwise
        // pushing in xz will behave very weird
        else if (self->mover && first_single_dir > 0) {
            first_single_dir--;
            goto retry;
        }
    }
    else {
        if (dx || dz) {
            move_on_top(self, dx, dy, dz);
        }
        self->blocks->rebuild_dynamic_cache = 1;
        r = 1;
        land_array_destroy(cs);
    }
    return r;
}
int block_recursion_tag(void) {
    tag += 1;
    return tag;
}
bool block_pull(Block * self, float dx, float dy, float dz) {
    bool first_ramp_up = 1;
    float ox = self->x;
    float oy = self->y;
    float oz = self->z;
    retry:;
    self->x += dx;
    self->y += dy;
    self->z += dz;
    LandArray * cs = block_colliders(self);
    if (land_array_count(cs)) {
        self->x = ox;
        self->y = oy;
        self->z = oz;
        land_array_destroy(cs);
        if (first_ramp_up) {
            first_ramp_up = 0;
            self->y += 1;
            goto retry;
        }
        return 0;
    }
    land_array_destroy(cs);
    move_on_top(self, dx, dy, dz);
    self->blocks->rebuild_dynamic_cache = 1;
    return 1;
}
static bool is_left(float * v, float xp, float yp, int i1, int i2) {
    float ax = v [i2 * 2 + 0] - v [i1 * 2 + 0];
    float ay = v [i2 * 2 + 1] - v [i1 * 2 + 1];
    float bx = xp - v [i1 * 2 + 0];
    float by = yp - v [i1 * 2 + 1];
    float c = ax * by - ay * bx;
    return c < 0;
}
int block_is_inside(Block * self, float xp, float yp, Viewport * viewport) {
    /* 0 no
     * 1 top
     * 2 left
     * 3 right
     */
    float x2 = self->x + self->xs;
    float y2 = self->y + self->ys;
    float z2 = self->z + self->zs;
    float v [14];
    project(viewport, self->x, y2, self->z, v + 0, v + 1);
    project(viewport, self->x, y2, z2, v + 2, v + 3);
    project(viewport, x2, y2, z2, v + 4, v + 5);
    project(viewport, x2, y2, self->z, v + 6, v + 7);
    project(viewport, self->x, self->y, z2, v + 8, v + 9);
    project(viewport, x2, self->y, z2, v + 10, v + 11);
    project(viewport, x2, self->y, self->z, v + 12, v + 13);
    //
    //      0
    //    /   .
    //  1       3
    //  | \   / |
    //  4   2   6
    //    \ | /
    //      5
    if (! is_left(v, xp, yp, 0, 1)) {
        return 0;
    }
    if (self->ys) {
        if (! is_left(v, xp, yp, 1, 4)) {
            return 0;
        }
    }
    if (! is_left(v, xp, yp, 4, 5)) {
        return 0;
    }
    if (! is_left(v, xp, yp, 5, 6)) {
        return 0;
    }
    if (self->ys) {
        if (! is_left(v, xp, yp, 6, 3)) {
            return 0;
        }
    }
    if (! is_left(v, xp, yp, 3, 0)) {
        return 0;
    }
    if (is_left(v, xp, yp, 1, 2) && is_left(v, xp, yp, 2, 3)) {
        return 1;
    }
    if (is_left(v, xp, yp, 2, 5)) {
        return 3;
    }
    return 2;
}
void block_get_bounding_rect(Block * self, Viewport * viewport, float * x1, float * y1, float * x2, float * y2) {
    float _;
    project(viewport, self->x, self->y, self->z + self->zs, x1, & _);
    project(viewport, self->x + self->xs, self->y, self->z, x2, & _);
    project(viewport, self->x, self->y + self->ys, self->z, & _, y1);
    project(viewport, self->x + self->xs, self->y, self->z + self->zs, & _, y2);
}
int block_sort_order(Block * self, Block * other, Viewport * viewport) {
    if (self == other) {
        return 0;
    }
    float e = 0.1;
    float slx, suy, srx, sdy;
    float olx, ouy, orx, ody;
    block_get_bounding_rect(self, viewport, & slx, & suy, & srx, & sdy);
    block_get_bounding_rect(other, viewport, & olx, & ouy, & orx, & ody);
    // bounding box does not overlap -> don't care about order
    if (slx >= orx) {
        return 0;
    }
    if (srx <= olx) {
        return 0;
    }
    if (suy >= ody) {
        return 0;
    }
    if (sdy <= ouy) {
        return 0;
    }
    // Has to be drawn later if completely in front or above.
    if (self->z + e >= other->z + other->zs) {
        return 1;
    }
    if (self->y + e >= other->y + other->ys) {
        return 1;
    }
    if (self->x + e >= other->x + other->xs) {
        return 1;
    }
    // Has to be drawn earlier if completely behind or below.
    if (other->z + e >= self->z + self->zs) {
        return - 1;
    }
    if (other->y + e >= self->y + self->ys) {
        return - 1;
    }
    if (other->x + e >= self->x + self->xs) {
        return - 1;
    }
    // Note: They overlap...
    if (self->y > other->y) {
        return 1;
    }
    //if self->z + self->zs >= other->z + other->zs: return 1
    //if self->y + self->ys >= other->y + other->ys: return 1
    //if self->x + self->xs >= other->x + other->xs: return 1
    return - 1;
}
static float sgn(float a, float x) {
    if (a <= 0) {
        return 0;
    }
    if (x < 0) {
        if (a >= 1) {
            return - 1;
        }
        return - a;
    }
    if (x > 0) {
        if (a >= 1) {
            return 1;
        }
        return a;
    }
    return 0;
}
void block_tick(Block * self) {
    if (! self->block_type->animated) {
        return ;
    }
    if (! self->no_fall && ! self->block_type->fixed) {
        self->dy -= sqrt(2);
    }
    float ax = fabs(self->dx);
    float ay = fabs(self->dy);
    float az = fabs(self->dz);
    while (ax > 0 || ay > 0 || az > 0) {
        float sx = sgn(ax, self->dx);
        float sy = sgn(ay, self->dy);
        float sz = sgn(az, self->dz);
        if (sy) {
            if (! block_move(self, 0, sy, 0)) {
                self->dy = 0;
                if (sy < 0) {
                    self->ground = 1;
                }
            }
            else {
                self->ground = 0;
            }
        }
        if (sx || sz) {
            block_move(self, sx, 0, sz);
        }
        ax -= 1;
        ay -= 1;
        az -= 1;
    }
    self->dx *= 0.4;
    self->dy *= 0.9;
    self->dz *= 0.4;
    if (fabs(self->dx) < 0.1) {
        self->dx = 0;
    }
    if (fabs(self->dy) < 0.1) {
        self->dy = 0;
    }
    if (fabs(self->dz) < 0.1) {
        self->dz = 0;
    }
}
bool block_touch(Block * self, Block * c, float dx, float dy, float dz) {
    return 1;
}
bool blocks_preload(Blocks * self) {
    int n1, n2, n3;
    n1 = land_array_count(self->fixed);
    n2 = land_array_count(self->dynamic);
    n3 = land_array_count(self->transparent);
    for (int i = 0; i < n1 + n2 + n3; i += 1) {
        Block * other;
        if (i < n1) {
            other = land_array_get_nth(self->fixed, i);
        }
        else if (i - n1 < n2) {
            other = land_array_get_nth(self->dynamic, i - n1);
        }
        else {
            other = land_array_get_nth(self->transparent, i - n1 - n2);
        }
        BlockType * bt = other->block_type;
        if (blocktype_preload(bt)) {
            return 1;
        }
    }
    return 0;
}
void blocks_shift(Blocks * self, int dx, int dy, int dz) {
    int n1, n2, n3;
    n1 = land_array_count(self->fixed);
    n2 = land_array_count(self->dynamic);
    n3 = land_array_count(self->transparent);
    for (int i = 0; i < n1 + n2 + n3; i += 1) {
        Block * other;
        if (i < n1) {
            other = land_array_get_nth(self->fixed, i);
        }
        else if (i - n1 < n2) {
            other = land_array_get_nth(self->dynamic, i - n1);
        }
        else {
            other = land_array_get_nth(self->transparent, i - n1 - n2);
        }
        other->x += dx;
        other->y += dy;
        other->z += dz;
    }
}
