#import "modules/std.sx";
#import "modules/math";
#import "modules/ui/types.sx";
#import "modules/ui/events.sx";

GesturePhase :: enum {
    possible;
    began;
    changed;
    ended;
    cancelled;
    failed;
}

// --- TapGesture ---

TapValue :: struct {
    location: Point;
    count: i32;
}

TapGesture :: struct {
    count: i32;
    on_tap: ?Closure();
    phase: GesturePhase;
    tap_count: i32;
    start_position: Point;

    TAP_THRESHOLD :f32: 10.0;

    handle_event :: (self: *TapGesture, event: *Event, frame: Frame) -> bool {
        if event.* == {
            case .mouse_down: (d) {
                if frame.contains(d.position) {
                    self.phase = .began;
                    self.start_position = d.position;
                    return true;
                }
            }
            case .mouse_moved: (d) {
                if self.phase == .began {
                    if self.start_position.distance(d.position) > TapGesture.TAP_THRESHOLD {
                        self.phase = .failed;
                    }
                }
            }
            case .mouse_up: (d) {
                if self.phase == .began {
                    if frame.contains(d.position) {
                        self.tap_count += 1;
                        if self.tap_count >= self.count {
                            if handler := self.on_tap { handler(); }
                            self.tap_count = 0;
                        }
                    }
                }
                self.phase = .possible;
                return true;
            }
        }
        false
    }
}

// --- DragGesture ---

DragValue :: struct {
    location: Point;
    start_location: Point;
    translation: Point;
}

DragGesture :: struct {
    min_distance: f32;
    on_changed: ?Closure(DragValue);
    on_ended: ?Closure(DragValue);
    phase: GesturePhase;
    start_location: Point;
    current_location: Point;

    make_value :: (self: *DragGesture) -> DragValue {
        DragValue.{
            location = self.current_location,
            start_location = self.start_location,
            translation = self.current_location.sub(self.start_location)
        }
    }

    handle_event :: (self: *DragGesture, event: *Event, frame: Frame) -> bool {
        if event.* == {
            case .mouse_down: (d) {
                if frame.contains(d.position) {
                    self.phase = .possible;
                    self.start_location = d.position;
                    self.current_location = d.position;
                    return true;
                }
            }
            case .mouse_moved: (d) {
                if self.phase == .possible {
                    self.current_location = d.position;
                    if self.start_location.distance(d.position) >= self.min_distance {
                        self.phase = .began;
                        if handler := self.on_changed { handler(self.make_value()); }
                    }
                    return true;
                }
                if self.phase == .began or self.phase == .changed {
                    self.current_location = d.position;
                    self.phase = .changed;
                    if handler := self.on_changed { handler(self.make_value()); }
                    return true;
                }
            }
            case .mouse_up: (d) {
                if self.phase == .began or self.phase == .changed {
                    self.current_location = d.position;
                    self.phase = .ended;
                    if handler := self.on_ended { handler(self.make_value()); }
                    self.phase = .possible;
                    return true;
                }
                self.phase = .possible;
            }
        }
        false
    }
}
