#import "Basic";
#import "String";
#import "File";
#import "Math";

solve_day24 :: (test: bool) {
    input := read_entire_file("inputs/day24.txt");
    string_lines := split(input, "\n");

    lines: [..]Line;
    lines_f: [..]Line_F;

    parse(string_lines, *lines, *lines_f);

    part_1 := 0;

    for l1: 0..lines_f.count - 2 {
        for l2: l1 + 1..lines_f.count - 1 {
            pos, success := xy_line_intersect(lines_f[l1], lines_f[l2]);

            if success && pos.x >= 200_000_000_000_000 && pos.x <= 400_000_000_000_000
                       && pos.y >= 200_000_000_000_000 && pos.y <= 400_000_000_000_000 {
                part_1 += 1;
            }


            if success && pos.x >= 7 && pos.x <= 27
                       && pos.y >= 7 && pos.y <= 27 {
                part_1 += 1;
            }
        }
    }

    print("Part 1: %\n", part_1);

    l1 := lines[0];
    l2 := lines[1];
    l3 := lines[2];
    l4 := lines[3];


    l2.dir = subtract(l2.dir, l1.dir);
    l3.dir = subtract(l3.dir, l1.dir);
    l4.dir = subtract(l4.dir, l1.dir);

    ll := subtract(l2.point, l1.point);

    plane := Plane.{
        point = l1.point,
        dir = cross(ll, add(ll, l2.dir)),
    };

    i1, d1 := plane_line_intersect(plane, l3);
    i2, d2 := plane_line_intersect(plane, l4);

    stone_dir := div(subtract(i1, i2), d2 - d1);

    stone_pos := add(mul(stone_dir, d1), i1);

    f := stone_pos.x + stone_pos.y + stone_pos.z;

    print("Part 2: %\n", f.low);

}

parse :: (input: []string, lines: *[..]Line, lines_f: *[..]Line_F) {
    for input {
        if it.count == 0  continue;
        sections := split(it, " @ ");
        coords := split(sections[0], ", ");
        dir := split(sections[1], ", ");

        xp_f,_ := string_to_float64(coords[0]);
        yp_f,_ := string_to_float64(coords[1]);
        zp_f,_ := string_to_float64(coords[2]);

        xd_f,_ := string_to_float64(dir[0]);
        yd_f,_ := string_to_float64(dir[1]);
        zd_f,_ := string_to_float64(dir[2]);

        xp,_ := string_to_int(coords[0]);
        yp,_ := string_to_int(coords[1]);
        zp,_ := string_to_int(coords[2]);

        xd,_ := string_to_int(dir[0]);
        yd,_ := string_to_int(dir[1]);
        zd,_ := string_to_int(dir[2]);

        array_add(lines_f, .{
            point = .{
                x = xp_f,
                y = yp_f,
                z = zp_f,
            },
            dir = .{
                x = xd_f,
                y = yd_f,
                z = zd_f,
            },
        });

        array_add(lines, .{
            point = .{
                x = to_s128(xp),
                y = to_s128(yp),
                z = to_s128(zp),
            },
            dir = .{
                x = to_s128(xd),
                y = to_s128(yd),
                z = to_s128(zd),
            },
        });
    }
}


Vec3f64 :: struct {
    x: float64;
    y: float64;
    z: float64;
}

Vec3i :: struct {
    x: S128;
    y: S128;
    z: S128;
}

Plane :: struct {
    point: Vec3i;
    dir: Vec3i;
}

Line_F :: struct {
    point: Vec3f64;
    dir:   Vec3f64;
}

Line :: struct {
    point: Vec3i;
    dir: Vec3i;
}

dot :: (v1: Vec3i, v2: Vec3i) -> S128 {
    return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
}

cross :: (v1: Vec3i, v2: Vec3i) -> Vec3i {
    return .{
        x = v1.y * v2.z - v1.z * v2.y,
        y = v1.z * v2.x - v1.x * v2.z,
        z = v1.x * v2.y - v1.y * v2.x,
    };
}

div :: (v: Vec3i, scalar: S128) -> Vec3i {
    return .{
        x = v.x / scalar,
        y = v.y / scalar,
        z = v.z / scalar,
    };
}

add :: (v1: Vec3i, v2: Vec3i) -> Vec3i {
    return .{
        x = v1.x + v2.x,
        y = v1.y + v2.y,
        z = v1.z + v2.z,
    };
}

mul :: (v: Vec3i, scalar: S128) -> Vec3i {
    return .{
        x = v.x * scalar,
        y = v.y * scalar,
        z = v.z * scalar,
    };
}

subtract :: (v1: Vec3i, v2: Vec3i) -> Vec3i {
    return .{
        x = v1.x - v2.x,
        y = v1.y - v2.y,
        z = v1.z - v2.z,
    };
}

plane_line_intersect :: (plane: Plane, line: Line) -> Vec3i, S128 {
    s := dot(subtract(plane.point, line.point), plane.dir) / dot(plane.dir, line.dir);

    return add(line.point, mul(line.dir, s)), s;

}

xy_line_intersect :: (l1: Line_F, l2: Line_F) -> (pos: Vec3f64, success: bool) {

    det := l2.dir.x * l1.dir.y - l2.dir.y * l1.dir.x;

    u := ((l2.point.y - l1.point.y) * l2.dir.x - (l2.point.x - l1.point.x) * l2.dir.y) / det;
    v := ((l2.point.y - l1.point.y) * l1.dir.x - (l2.point.x - l1.point.x) * l1.dir.y) / det;

    if u < 0 || v < 0  return .{x = 0, y = 0, z = 0}, false;

    result := Vec3f64.{
        x = l2.point.x + l2.dir.x * v,
        y = l2.point.y + l2.dir.y * v,
    };

    return result, true;

}