win-auto-utils 0.1.1

Universal Windows automation utilities with memory, window, input, and color operations
//! Process Context - Windows resource management for script execution
//!
//! This module provides direct field access to frequently-used Windows handles
//! (HWND, HDC, HANDLE) within the VM context, eliminating HashMap lookup overhead.

//! # Usage
//! ```no_run
//!
//! use win_auto_utils::script_engine::process_context::ProcessContext;
//!
//! // Create new context
//! let mut ctx = ProcessContext::new();
//!
//! // Set window handle
//! let hwnd = unsafe { windows::Win32::Foundation::HWND(std::ptr::null_mut()) };
//! ctx.set_hwnd(hwnd);
//!
//! // Get window handle (fast direct access)
//! if let Some(h) = ctx.hwnd {
//!     // Use hwnd directly
//! }
//! ```

use std::option::Option;

use windows::Win32::Foundation::{HANDLE, HWND};

use windows::Win32::Graphics::Gdi::HDC;

/// Cached window geometry for fast coordinate conversion
///
/// This structure stores pre-computed window position and border offsets,
/// eliminating the need for repeated Windows API calls during script execution.
///
/// # Fields
/// - `hwnd`: The window handle this geometry belongs to (for validation)
/// - `window_left`: Window rect left position on screen
/// - `window_top`: Window rect top position on screen
/// - `offset_x`: Horizontal offset from window edge to client area (borders/title bar)
/// - `offset_y`: Vertical offset from window edge to client area (borders/title bar)
///
/// # Coordinate Conversion Formula
/// ```text
/// client_x = window_rel_x - offset_x
/// client_y = window_rel_y - offset_y
/// ```
#[derive(Debug, Clone, Copy)]
pub struct WindowGeometry {
    /// Window handle for validation
    pub hwnd: HWND,
    /// Window left position on screen
    pub window_left: i32,
    /// Window top position on screen
    pub window_top: i32,
    /// Offset from window edge to client area (X axis)
    pub offset_x: i32,
    /// Offset from window edge to client area (Y axis)
    pub offset_y: i32,
}

/// Process context containing Windows resource handles
///
/// This structure holds frequently-accessed Windows handles that are needed
/// during script execution. By storing them as direct fields instead of in a
/// HashMap, we achieve ~50x faster access times.
///
/// # Fields
/// - `hwnd`: Target window handle for PostMessage operations (background automation)
/// - `window_geometry`: Cached window geometry for fast coordinate conversion
/// - `hdc`: Device context handle for GDI drawing/color operations (requires `hdc` feature)
/// - `process_handle`: Process handle for memory read/write operations
///
/// All fields are `Option` types to support partial initialization and optional usage.

#[derive(Debug, Clone, Copy)]
pub struct ProcessContext {
    /// Target window handle for background mode (PostMessage)
    pub hwnd: Option<HWND>,

    /// Cached window geometry (auto-computed when hwnd is set)
    pub window_geometry: Option<WindowGeometry>,

    /// Device context handle for GDI operations (optional, requires hdc feature)
    pub hdc: Option<HDC>,

    /// Process handle for memory operations
    pub process_handle: Option<HANDLE>,
}

impl ProcessContext {
    /// Create a new empty process context
    ///
    /// All handles are initialized to `None`.
    #[inline]
    pub fn new() -> Self {
        Self {
            hwnd: None,
            window_geometry: None,
            hdc: None,
            process_handle: None,
        }
    }

    /// Set the target window handle and automatically cache its geometry
    ///
    /// This method not only sets the window handle but also immediately queries
    /// and caches the window's geometric information (position, borders, title bar offsets).
    /// This eliminates the need for repeated Windows API calls during script execution.
    ///
    /// **Performance Impact:**
    /// - One-time cost: ~1-5μs for Windows API calls (GetWindowRect, GetClientRect)
    /// - Per-instruction savings: ~200-500ns per HashMap lookup eliminated
    /// - For 1000 instructions: Saves ~200-500μs total
    ///
    /// # Example
    /// ```no_run
    ///
    /// use win_auto_utils::script_engine::process_context::ProcessContext;
    ///
    /// let mut ctx = ProcessContext::new();
    /// let hwnd = unsafe { windows::Win32::Foundation::HWND(std::ptr::null_mut()) };
    /// ctx.set_hwnd(hwnd); // Geometry automatically cached here
    /// ```
    pub fn set_hwnd(&mut self, hwnd: HWND) {
        self.hwnd = Some(hwnd);

        // Automatically calculate and cache window geometry
        unsafe {
            use windows::Win32::Foundation::RECT;
            use windows::Win32::UI::WindowsAndMessaging::{GetClientRect, GetWindowRect};

            let mut window_rect = RECT::default();
            let mut client_rect = RECT::default();

            if GetWindowRect(hwnd, &mut window_rect).is_ok()
                && GetClientRect(hwnd, &mut client_rect).is_ok()
            {
                self.window_geometry = Some(WindowGeometry {
                    hwnd,
                    window_left: window_rect.left,
                    window_top: window_rect.top,
                    offset_x: window_rect.left - client_rect.left,
                    offset_y: window_rect.top - client_rect.top,
                });
            } else {
                // If API calls fail, clear geometry cache
                self.window_geometry = None;
            }
        }
    }

    /// Get the target window handle
    ///
    /// Returns `None` if not set. For error-handling version, see `get_hwnd_or_err()`.
    #[inline]
    pub fn get_hwnd(&self) -> Option<HWND> {
        self.hwnd
    }

    /// Get the target window handle or return an error
    ///
    /// This is a convenience method for instruction handlers that need clear error messages.
    ///
    /// # Errors
    /// Returns `ExecutionError` if hwnd is not set.
    #[inline]
    pub fn get_hwnd_or_err(&self) -> Result<HWND, crate::script_engine::instruction::ScriptError> {
        self.hwnd.ok_or_else(|| {
            crate::script_engine::instruction::ScriptError::ExecutionError(
                "No target window set. Call process_ctx.set_hwnd(hwnd) before using background mode.".into()
            )
        })
    }

    /// Check if target window handle has been set
    #[inline]
    pub fn has_hwnd(&self) -> bool {
        self.hwnd.is_some()
    }

    /// Set the device context handle
    #[inline]
    pub fn set_hdc(&mut self, hdc: HDC) {
        self.hdc = Some(hdc);
    }

    /// Get the device context handle
    #[inline]
    pub fn get_hdc(&self) -> Option<HDC> {
        self.hdc
    }

    /// Set the process handle
    #[inline]
    pub fn set_process_handle(&mut self, handle: HANDLE) {
        self.process_handle = Some(handle);
    }

    /// Get the process handle
    #[inline]
    pub fn get_process_handle(&self) -> Option<HANDLE> {
        self.process_handle
    }

    /// Clear all handles and cached geometry
    #[inline]
    pub fn clear(&mut self) {
        self.hwnd = None;
        self.window_geometry = None;
        self.process_handle = None;

        self.hdc = None;
    }

    /// Check if any handle is set
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.hwnd.is_none()
            && self.window_geometry.is_none()
            && self.hdc.is_none()
            && self.process_handle.is_none()
    }
}

impl Default for ProcessContext {
    fn default() -> Self {
        Self::new()
    }
}