const std = @import("std");
const rl = @import("raylib");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const SeamCarver = @import("./seam-carver.zig");

fn toTraceLogLevel(log_level: std.log.Level) rl.TraceLogLevel {
    return switch (log_level) {
        .err   => rl.TraceLogLevel.log_error,
        .warn  => rl.TraceLogLevel.log_warning,
        .info  => rl.TraceLogLevel.log_info,
        .debug => rl.TraceLogLevel.log_all,
    };
}

fn convertF32ImageToU8(allocator: Allocator, image: []f32, width: usize, height: usize) ![]u8 {
    const image_u8 = try allocator.alloc(u8, width * height);
    errdefer allocator.free(image_u8);

    var min_value: f32 = image[0];
    var max_value: f32 = image[0];
    for (image[1..]) |value| {
        min_value = @min(min_value, value);
        max_value = @max(max_value, value);
    }

    for (0.., image) |i, value| {
        const value_01 = (value - min_value) / (max_value - min_value);
        image_u8[i] = @intFromFloat(value_01 * 255);
    }

    return image_u8;
}

fn insetRect(rect: rl.Rectangle, margin: f32) rl.Rectangle{
    return rl.Rectangle.init(rect.x + margin, rect.y + margin, rect.width - 2*margin, rect.height - 2*margin);
}

fn insideRect(container: rl.Rectangle, child_size: rl.Vector2) rl.Rectangle {
    const scale_x = container.width / child_size.x;
    const scale_y = container.height / child_size.y;

    const min_scale = @min(scale_x, scale_y);
    return centerRect(container, child_size.scale(min_scale));
}

fn centerRect(container: rl.Rectangle, child_size: rl.Vector2) rl.Rectangle {
    return rl.Rectangle{
        .x = container.x + (container.width - child_size.x)/2,
        .y = container.y + (container.height - child_size.y)/2,
        .width = child_size.x,
        .height = child_size.y
    };
}

fn centerOfRect(rect: rl.Rectangle) rl.Vector2 {
    return rl.Vector2.init(rect.x + rect.width/2, rect.y + rect.height/2);
}

fn distanceToLine(lineA: rl.Vector2, lineB: rl.Vector2, point: rl.Vector2) f32 {
    const ab = lineB.subtract(lineA);
    const ap = point.subtract(lineA);
    const proj = ap.dotProduct(ab);
    const d = proj / ab.lengthSqr();

    if (d <= 0) {
        return point.distance(lineA);
    } else if (d >= 1) {
        return point.distance(lineB);
    } else {
        const pointOnLine = lineA.add(ab.scale(d));
        return point.distance(pointOnLine);
    }
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    defer _ = gpa.deinit();

    rl.setTraceLogLevel(toTraceLogLevel(std.log.default_level));

    rl.setConfigFlags(.{ .window_resizable = true });
    rl.initWindow(1024, 720, "Seam carving");
    defer rl.closeWindow();

    rl.setTargetFPS(60);

    const source_image = rl.loadImageFromMemory(".jpg", @embedFile("./assets/example.jpg"));
    assert(@intFromPtr(source_image.data) != 0);
    assert(source_image.format == .pixelformat_uncompressed_r8g8b8);
    defer rl.unloadImage(source_image);

    var image = rl.imageCopy(source_image);
    defer rl.unloadImage(image);

    const source_texture = rl.loadTextureFromImage(source_image);
    defer rl.unloadTexture(source_texture);

    var resized_texture = rl.loadTextureFromImage(image);
    defer rl.unloadTexture(resized_texture);

    var seam_carver = SeamCarver.init(allocator, source_image);
    defer seam_carver.deinit();

    var resized_width: u32 = @intCast(image.width);
    var resized_height: u32 = @intCast(image.height);

    var resizing_vertically = false;
    var resizing_horizontally = false;

    while (!rl.windowShouldClose()) {
        rl.beginDrawing();
        defer rl.endDrawing();

        const window_width: f32 = @floatFromInt(rl.getScreenWidth());
        const window_height: f32 = @floatFromInt(rl.getScreenHeight());

        const margin = @min(window_width, window_height)*0.02;

        const left_side = insetRect(rl.Rectangle.init(0, 0, window_width/2, window_height), margin);
        const right_side = insetRect(rl.Rectangle.init(window_width/2, 0, window_width/2, window_height), margin);

        const left_image_rect = insideRect(left_side, .{ .x = @floatFromInt(source_image.width), .y = @floatFromInt(source_image.height) });
        const right_image_rect = insideRect(right_side, .{ .x = @floatFromInt(source_image.width), .y = @floatFromInt(source_image.height) });

        rl.clearBackground(rl.Color.ray_white);

        const scale = left_image_rect.width / @as(f32, @floatFromInt(source_image.width));

        if (seam_carver.resized_image) |resized_image| {
            rl.unloadImage(image);
            image = resized_image;

            rl.unloadTexture(resized_texture);
            resized_texture = rl.loadTextureFromImage(image);

            seam_carver.resized_image = null;
        }

        rl.drawTextureEx(
            source_texture,
            .{ .x = left_image_rect.x, .y = left_image_rect.y },
            0,
            scale,
            rl.Color.white
        );

        rl.drawRectangleRec(
            right_image_rect,
            rl.Color.light_gray
        );

        const resized_visual_size = rl.Vector2{
            .x = scale * @as(f32, @floatFromInt(resized_width)),
            .y = scale * @as(f32, @floatFromInt(resized_height)),
        };
        const resized_rect = centerRect(right_image_rect, resized_visual_size);

        if (seam_carver.work_thread == null) {
            var pos = rl.Vector2{ .x = resized_rect.x, .y = resized_rect.y };
            if (resizing_horizontally or resizing_vertically) {
                pos = rl.Vector2{ .x = right_image_rect.x, .y = right_image_rect.y };
            }

            const half_image_size = rl.Vector2.init(
                @as(f32, @floatFromInt(image.width))/2,
                @as(f32, @floatFromInt(image.height))/2
            );
            rl.drawTextureEx(
                resized_texture,
                centerOfRect(right_image_rect).subtract(half_image_size.scale(scale)),
                0,
                scale,
                rl.Color.white
            );
        } else {
            const label: [:0]u8 = try std.fmt.allocPrintZ(allocator, "Resizing {d:.2}%", .{seam_carver.resizing_progress * 100});
            defer allocator.free(label);

            rl.drawTextEx(
                rl.getFontDefault(),
                label,
                centerOfRect(right_image_rect),
                30,
                3,
                rl.Color.black
            );
        }

        rl.drawRectangleLinesEx(
            resized_rect,
            3,
            rl.Color.black
        );

        if (seam_carver.work_thread == null) {
            const center = centerOfRect(resized_rect);

            const top_left = rl.Vector2.init(resized_rect.x, resized_rect.y);
            const top_right = rl.Vector2.init(resized_rect.x + resized_rect.width, resized_rect.y);
            const bottom_left = rl.Vector2.init(resized_rect.x, resized_rect.y + resized_rect.height);
            const bottom_right = rl.Vector2.init(resized_rect.x + resized_rect.width, resized_rect.y + resized_rect.height);

            const grab_distance = 10;
            const mouse = rl.getMousePosition();

            if (rl.isMouseButtonDown(.mouse_button_left) and resizing_vertically) {
                resized_height = @intFromFloat(@abs(center.y - mouse.y)/scale*2);
                resized_height = @min(resized_height, @as(u32, @intCast(source_image.height)));
            }
            if (rl.isMouseButtonDown(.mouse_button_left) and resizing_horizontally) {
                resized_width = @intFromFloat(@abs(center.x - mouse.x)/scale*2);
                resized_width = @min(resized_width, @as(u32, @intCast(source_image.width)));
            }

            if (distanceToLine(top_left, top_right, mouse) < grab_distance) {
                rl.drawLineEx(top_right, top_left, 3, rl.Color.red);
                if (rl.isMouseButtonPressed(.mouse_button_left)) {
                    resizing_vertically = true;
                }

            } else if (distanceToLine(bottom_left, bottom_right, mouse) < grab_distance) {
                rl.drawLineEx(bottom_right, bottom_left, 3, rl.Color.red);
                if (rl.isMouseButtonPressed(.mouse_button_left)) {
                    resizing_vertically = true;
                }
            }

            if (distanceToLine(top_left, bottom_left, mouse) < grab_distance) {
                rl.drawLineEx(top_left, bottom_left, 3, rl.Color.red);
                if (rl.isMouseButtonPressed(.mouse_button_left)) {
                    resizing_horizontally = true;
                }

            } else if (distanceToLine(bottom_right, top_right, mouse) < grab_distance) {
                rl.drawLineEx(bottom_right, top_right, 3, rl.Color.red);
                if (rl.isMouseButtonPressed(.mouse_button_left)) {
                    resizing_horizontally = true;
                }
            }

            if (rl.isMouseButtonReleased(.mouse_button_left) and (resizing_horizontally or resizing_vertically)) {
                try seam_carver.startResizing(resized_width, resized_height);
            }
        }

        if (rl.isMouseButtonReleased(.mouse_button_left)) {
            resizing_vertically = false;
            resizing_horizontally = false;
        }
    }
}
