//! Ctrl+click on the canvas (plans E3, S4-6): the zoom box, pan to origin and
//! unzoom with Ctrl alone, and the shape factor of a spline point with
//! Ctrl+Shift in the editing modes.
//
// Ported from xfig src/w_zoom.c (`zoom_selected`, `init_zoombox_drawing`,
// `do_zoom`, `cancel_zoom`), src/w_canvas.c (the Ctrl and Ctrl+Shift cases of
// `canvas_selected`), src/e_edit.c (`change_sfactor`) and src/u_search.c
// (`get_spline_point`) (xfig licence, see LICENSE-xfig).

use super::{nearest_vertex, Button, ToolState};
use crate::{Engine, EngineError};
use rustfig_geom::Point;
use rustfig_model::ObjectKind;
use serde_json::json;

/// A zoom box dragged less than this many screen pixels is a click: the
/// box stays open until the next click.
const CLICK_PX: f64 = 4.0;

impl Engine {
    /// Ctrl (Cmd on macOS) held with a button press.
    pub(super) fn ctrl_press(&mut self, pos: Point, button: Button, shift: bool) -> Result<(), EngineError> {
        if !pos.is_finite() {
            return Ok(());
        }
        if shift {
            // Only the editing modes (`cur_mode >= FIRST_EDIT_MODE`).
            if self.tool.is_drawing() {
                return Ok(());
            }
            return self.change_sfactor(pos, button);
        }
        match button {
            // A tool at work (`check_action_on`) refuses the zoom box.
            Button::Left if !self.tool_busy() => {
                self.tool_state = ToolState::ZoomBox {
                    start: pos,
                    cursor: pos,
                };
            }
            Button::Left => {}
            Button::Middle => {
                // `pan_origin`: the figure's origin at the canvas's corner.
                self.execute("view.pan_origin", &json!({}))?;
            }
            Button::Right => {
                // `unzoom`: back to 100 %, the corner stays.
                self.view.zoom_about(1.0, Point::ZERO);
            }
        }
        Ok(())
    }

    /// Events while the zoom box is open.
    pub(super) fn zoom_box_event(&mut self, ev: super::ToolEvent, start: Point) -> Result<(), EngineError> {
        use super::ToolEvent as E;
        match ev {
            E::Move { pos } if pos.is_finite() => {
                self.tool_state = ToolState::ZoomBox { start, cursor: pos };
            }
            E::Press {
                pos,
                button: Button::Left,
                ..
            }
            | E::Ctrl {
                pos,
                button: Button::Left,
                ..
            } if pos.is_finite() => {
                return self.finish_zoom_box(start, pos);
            }
            E::Release {
                pos,
                button: Button::Left,
            } if pos.is_finite() => {
                // A drag closes the box on release; a click leaves it open.
                if start.dist(pos) * self.view.px_per_fig() >= CLICK_PX {
                    return self.finish_zoom_box(start, pos);
                }
            }
            E::Press {
                button: Button::Right, ..
            }
            | E::Ctrl {
                button: Button::Right, ..
            }
            | E::Key(super::Key::Escape) => {
                self.tool_state = ToolState::Idle;
            }
            _ => {}
        }
        Ok(())
    }

    fn finish_zoom_box(&mut self, a: Point, b: Point) -> Result<(), EngineError> {
        self.tool_state = ToolState::Idle;
        self.execute("view.zoom_box", &json!({"x1": a.x, "y1": a.y, "x2": b.x, "y2": b.y}))?;
        Ok(())
    }

    /// `change_sfactor`: the spline point under the click, front-most
    /// spline first (`get_spline_point`).
    fn change_sfactor(&mut self, pos: Point, button: Button) -> Result<(), EngineError> {
        let tol = self.pick_tol();
        let mut found = None;
        for o in self.doc().objects.iter().rev() {
            if !self.is_visible(o) {
                continue;
            }
            if let ObjectKind::Spline(s) = &o.kind {
                if let Some(i) = nearest_vertex(&s.points, pos, tol) {
                    found = Some((o.id, i));
                    break;
                }
            }
        }
        let Some((id, index)) = found else {
            self.set_status("Only spline points can be edited");
            return Ok(());
        };
        let action = match button {
            Button::Left => "approximate",
            Button::Middle => "cycle",
            Button::Right => "interpolate",
        };
        match self.execute(
            "spline.shape_factor",
            &json!({"id": id.0, "index": index, "action": action}),
        ) {
            Ok(_) => Ok(()),
            Err(e) => {
                self.set_status(&e.message);
                Ok(())
            }
        }
    }
}
