HISTORY_LEN :: 1_000_000;
AREA_WIDTH  :: 7;
AREA_HEIGHT :: 4 * HISTORY_LEN;

MAX_REPEAT :: 1_000_000_000_000;

solve_day17 :: (test: bool) {
    instructions := read_entire_file(ifx test then "inputs/day17_test.txt" else "inputs/day17.txt");

    current_height := 0;
    current_shape := 0;
    current_instruction := 0;

    part2_part := 0;
    part1 := 0;
    part2 := 0;
    last_y := 0;

    area: []u8 = NewArray(AREA_HEIGHT, u8);

    repeat_length := -1;
    start_repeat := 0;
    shape := 0;

    history := NewArray(HISTORY_LEN, History_Item);

    while true {
        defer shape += 1;
        drop := Shape_Drop.{
            shape = *SHAPES[current_shape],
            x = 2,
            y = current_height + 4,
        };

        while can_drop_shape(drop, area) {
            drop.y -= 1;
            if can_shift(drop, area, instructions[current_instruction]) {
                drop.x += ifx instructions[current_instruction] == #char "<" then -1 else 1;
            }
            current_instruction = (current_instruction + 1) % instructions.count;
        } 

        for y: 0..drop.shape.height - 1 {
            area[drop.y + y] |=  drop.shape.data[y] << drop.x;
        }
        current_height = max(current_height, drop.y + drop.shape.height);
        current_shape = (current_shape + 1) % SHAPES.count;

        history[shape] = .{
            x = drop.x,
            y_diff = drop.y - last_y,
            height = current_height,
        };

        last_y = drop.y;

        if repeat_length == -1 && shape > 101 {
            repeat_length = check_for_repeat(history, shape);


            if repeat_length != -1 {
                start_repeat = shape;
                f := (MAX_REPEAT - shape) / repeat_length;
                missing := (MAX_REPEAT - shape) % f;
                missing_height := history[shape - repeat_length + missing].height - history[shape - repeat_length].height;

                height_diff := history[shape].height - history[shape - repeat_length].height;

                part2 = height_diff * f + history[shape].height + missing_height - 1;
            }
        }

        if shape == 2021  part1 = current_height;

        if shape > 2021 && repeat_length != -1  break;

    }

    print("Part 1: %\n", part1);
    print("Part 2: %\n", part2);
}

check_for_repeat :: (history: []History_Item, newest: s64) -> s64 {

    i := newest;

    while true {
        i -= SHAPES.count;

        if i < 0  break;

        offset := 0;

        while i - offset > 0 && history[newest - offset].y_diff == history[i - offset].y_diff && history[newest - offset].x == history[i - offset].x {
            if newest - offset == i {
                return newest - i; 
            }
            offset += 1;
        }
    }

    return - 1;
}

can_drop_shape :: (drop: Shape_Drop, area: []u8) -> bool {

    if drop.y - 1 < 0  return false;

    for y: 0..drop.shape.height - 1 {
        if area[drop.y + y - 1] & (drop.shape.data[y] << drop.x)  return false;
    }

    return true;
}


can_shift :: (drop: Shape_Drop, area: []u8, instruction: u8) -> bool {
    way_to_shift := ifx instruction == #char "<" then -1 else 1;

    if drop.shape.width + drop.x + way_to_shift > AREA_WIDTH || drop.x + way_to_shift < 0  return false;

    for y: 0..drop.shape.height - 1 {
        if area[drop.y + y] & (drop.shape.data[y] << (drop.x + way_to_shift))  return false;
    }

    return true;
}

print_area :: (area: []u8, height: s64) {
    for < y: height..0 {
        print("|");
        for 0..AREA_WIDTH - 1 {
            if area[y] & ((cast(u8)1) << it) != 0  print("#");
            else print(".");
        }
        print("|\n");
    }

    print("+-------+\n\n");
}

SHAPES :: Shape.[.{ 4, 1, u8.[0b1111] }, .{ 3, 3, u8.[0b010, 0b111, 0b010] }, .{3, 3, u8.[0b111, 0b100, 0b100] }, .{ 1, 4, u8.[0b1, 0b1, 0b1, 0b1]}, .{ 2, 2, u8.[0b11, 0b11] }];

Shape_Drop :: struct {
    shape: *Shape;
    x: s64 = 2;
    y: s64 = 0;
}

Shape :: struct {
    width: s64;
    height: s64;
    data: []u8;
}

History_Item :: struct {
    x: s64;
    y_diff: s64;
    height: s64;
}