1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
//! # Dispatch module
//!
//! This module is used for calling dispatchers and changing keywords
//!
//! ## Usage
//!
//! ```rust
//! use hyprland::dispatch::{dispatch_blocking, DispatchType};
//! fn main() -> std::io::Result<()> {
//!    dispatch_blocking(DispatchType::Exec("kitty".to_string()))?;
//!
//!    Ok(())
//! }
//! ````

use crate::shared::{
    get_socket_path, write_to_socket, write_to_socket_sync, Address, SocketType, WorkspaceId,
};
use std::io;

/// This enum is for identifying a window
#[derive(Clone)]
pub enum WindowIdentifier {
    /// The address of a window
    Address(Address),
    /// A Regular Expression to match the window class (handled by Hyprland)
    ClassRegularExpression(String),
    /// The window title
    Title(String),
    /// The window's process Id
    ProcessId(u32),
}

/// This enum holds the fullscreen types
pub enum FullscreenType {
    /// Fills the whole screen
    Real,
    /// Maximizes the window
    Maximize,
    /// Passes no param
    NoParam,
}

/// This enum holds directions, typically used for moving
#[derive(Clone)]
#[allow(missing_docs)]
pub enum Direction {
    Up,
    Down,
    Right,
    Left,
}

/// This enum is used for resizing and moving windows precisely
#[derive(Clone)]
pub enum Position {
    /// A delta
    Delta(i16, i16),
    /// The exact size
    Exact(i16, i16),
}

/// This enum holds a direction for cycling
#[allow(missing_docs)]
pub enum CycleDirection {
    Next,
    Previous,
}

/// This enum is used for identifying monitors
#[derive(Clone)]
pub enum MonitorIdentifier {
    /// The monitor that is to the specified direction of the active one
    Direction(Direction),
    /// The monitor id
    Id(u8),
    /// The monitor name
    Name(String),
}

/// This enum holds corners
#[allow(missing_docs)]
pub enum Corner {
    TopRight,
    TopLeft,
    BottomRight,
    BottomLeft,
}

/// This enum holds options that are applied to the current workspace
pub enum WorkspaceOptions {
    /// Makes all windows pseudo tiled
    AllPseudo,
    /// Makes all windows float
    AllFloat,
}

/// This enum is for identifying workspaces that also includes the special workspace
#[derive(Clone)]
pub enum WorkspaceIdentifierWithSpecial {
    /// The workspace Id
    Id(WorkspaceId),
    /// The workspace relative to the current workspace (positive)
    PositiveRelative(u8),
    /// The workspace relative to the current workspace (positive)
    NegativeRelative(u8),
    /// The workspace on the monitor relative to the current monitor (positive)
    PositiveRelativeMonitor(u8),
    /// The workspace on the monitor relative to the current monitor (negative)
    NegativeRelativeMonitor(u8),
    /// The name of the workspace
    Name(String),
    /// The special workspace
    Special,
}

/// This enum is for identifying workspaces
#[derive(Clone)]
pub enum WorkspaceIdentifier {
    /// The workspace Id
    Id(WorkspaceId),
    /// The workspace relative to the current workspace (positive)
    PositiveRelative(u8),
    /// The workspace relative to the current workspace (positive)
    NegativeRelative(u8),
    /// The workspace on the monitor relative to the current monitor (positive)
    PositiveRelativeMonitor(u8),
    /// The workspace on the monitor relative to the current monitor (negative)
    NegativeRelativeMonitor(u8),
    /// The name of the workspace
    Name(String),
}

/// This enum is the params to MoveWindow dispatcher
pub enum WindowMove {
    /// Moves the window to a specified monitor
    Monitor(MonitorIdentifier),
    /// Moves the window in a specified direction
    Direction(Direction),
}

/// This enum holds every dispatcher
pub enum DispatchType {
    /// This dispatcher changes a keyword
    Keyword(
        /// The keyword key
        String,
        /// The value to set the keyword to
        String,
    ),
    /// This dispatcher changes the current cursor
    SetCursor(
        /// The cursor theme
        String,
        /// The size
        u16,
    ),
    /// This dispatcher executes a program
    Exec(String),
    /// This dispatcher kills the active window/client
    KillActiveWindow,
    /// This dispatcher changes the current workspace
    Workspace(WorkspaceIdentifierWithSpecial),
    /// This dispatcher moves the focused window to a specified workspace, and
    /// changes the active workspace aswell
    MoveFocusedWindowToWorkspace(WorkspaceIdentifier),
    /// This dispatcher moves the focused window to a specified workspace, and
    /// does not change workspaces
    MoveFocusedWindowToWorkspaceSilent(WorkspaceIdentifier),
    /// This dispatcher floats the current window
    ToggleFloating,
    /// This toggles the current window fullscreen state
    ToggleFullscreen(FullscreenType),
    /// This dispatcher toggles pseudo tiling for the current window
    TogglePseudo,
    /// This dispatcher moves the window focus in a specified direction
    MoveFocus(Direction),
    /// This dispatcher moves the current window to a monitor or in a specified direction
    MoveWindow(WindowMove),
    /// This dispatcher resizes the active window using a [`Position`][Position] enum
    ResizeActive(Position),
    /// This dispatcher moves the active window using a [`Position`][Position] enum
    MoveActive(Position),
    /// This dispatcher cycles windows using a specified direction
    CycleWindow(CycleDirection),
    /// This dispatcher focuses a specified window
    FocusWindow(WindowIdentifier),
    /// This dispatcher focuses a specified monitor
    FocusMonitor(MonitorIdentifier),
    /// This dispatcher changed the split ratio
    ChangeSplitRatio(f32),
    /// This dispatcher toggle opacity for the current window/client
    ToggleOpaque,
    /// This dispatcher moves the cursor to a specified corner of a window
    MoveCursorToCorner(Corner),
    /// This dispatcher applied a option to all windows in a workspace
    WorkspaceOption(WorkspaceOptions),
    /// This exits Hyprland **(DANGEROUS)**
    Exit,
    /// This dispatcher forces the renderer to reload
    ForceRendererReload,
    /// This dispatcher moves the current workspace to a specified monitor
    MoveCurrentWorkspaceToMonitor(MonitorIdentifier),
    /// This dispatcher moves a specified workspace to a specified monitor
    MoveWorkspaceToMonitor(WorkspaceIdentifier, MonitorIdentifier),
    /// This toggles the special workspace (AKA scratchpad)
    ToggleSpecialWorkspace,
}

fn match_workspace_identifier(identifier: WorkspaceIdentifier) -> String {
    match identifier {
        WorkspaceIdentifier::Id(id) => format!("{id}"),
        WorkspaceIdentifier::Name(name) => format!("name:{name}"),
        WorkspaceIdentifier::PositiveRelative(int) => format!("+{int}"),
        WorkspaceIdentifier::PositiveRelativeMonitor(int) => format!("m+{int}"),
        WorkspaceIdentifier::NegativeRelative(int) => format!("-{int}"),
        WorkspaceIdentifier::NegativeRelativeMonitor(int) => format!("m-{int}"),
    }
}

fn match_workspace_identifier_special(identifier: WorkspaceIdentifierWithSpecial) -> String {
    match identifier {
        WorkspaceIdentifierWithSpecial::Id(id) => format!("{id}"),
        WorkspaceIdentifierWithSpecial::Name(name) => format!("name:{name}"),
        WorkspaceIdentifierWithSpecial::PositiveRelative(int) => format!("+{int}"),
        WorkspaceIdentifierWithSpecial::PositiveRelativeMonitor(int) => format!("m+{int}"),
        WorkspaceIdentifierWithSpecial::NegativeRelative(int) => format!("-{int}"),
        WorkspaceIdentifierWithSpecial::NegativeRelativeMonitor(int) => format!("m-{int}"),
        WorkspaceIdentifierWithSpecial::Special => "special".to_string(),
    }
}

fn match_mon_indentifier(identifier: MonitorIdentifier) -> String {
    match identifier {
        MonitorIdentifier::Direction(dir) => match_dir(dir),
        MonitorIdentifier::Id(id) => id.to_string(),
        MonitorIdentifier::Name(name) => name,
    }
}

fn match_dir(dir: Direction) -> String {
    match dir {
        Direction::Left => "l",
        Direction::Right => "r",
        Direction::Down => "d",
        Direction::Up => "u",
    }
    .to_string()
}

fn position_to_string(pos: Position) -> String {
    match pos {
        Position::Delta(x, y) => format!("{x},{y}"),
        Position::Exact(w, h) => format!("exact {w} {h}"),
    }
}

fn match_window_identifier(iden: WindowIdentifier) -> String {
    match iden {
        WindowIdentifier::Address(addr) => format!("address:{}", addr),
        WindowIdentifier::ProcessId(id) => format!("pid:{}", id),
        WindowIdentifier::ClassRegularExpression(regex) => regex,
        WindowIdentifier::Title(title) => format!("title:{}", title),
    }
}

fn gen_dispatch_str(cmd: DispatchType) -> io::Result<String> {
    let string_to_pass = match &cmd {
        DispatchType::Exec(sh) => format!("exec {sh}"),
        DispatchType::KillActiveWindow => "killactive".to_string(),
        DispatchType::Workspace(identifier) => format!(
            "workspace {}",
            match_workspace_identifier_special(identifier.clone())
        ),
        DispatchType::MoveFocusedWindowToWorkspace(identifier) => {
            format!(
                "workspace {}",
                match_workspace_identifier(identifier.clone())
            )
        }
        DispatchType::MoveFocusedWindowToWorkspaceSilent(identifier) => {
            format!(
                "workspace {}",
                match_workspace_identifier(identifier.clone())
            )
        }
        DispatchType::ToggleFloating => "togglefloating".to_string(),
        DispatchType::ToggleFullscreen(fullscreen_type) => format!(
            "fullscreen {}",
            match fullscreen_type {
                FullscreenType::Real => "0",
                FullscreenType::Maximize => "1",
                FullscreenType::NoParam => "",
            }
        ),
        DispatchType::TogglePseudo => "pseudo".to_string(),
        DispatchType::MoveFocus(dir) => format!(
            "movefocus {}",
            match dir {
                Direction::Down => "d",
                Direction::Up => "u",
                Direction::Right => "r",
                Direction::Left => "l",
            }
        ),
        DispatchType::MoveWindow(iden) => format!(
            "movewindow {}",
            match iden {
                WindowMove::Direction(dir) => match_dir(dir.clone()),
                WindowMove::Monitor(mon) => format!("mon:{}", match_mon_indentifier(mon.clone())),
            }
        ),
        DispatchType::ResizeActive(pos) => {
            format!("resizeactive {}", position_to_string(pos.clone()))
        }
        DispatchType::MoveActive(pos) => format!("moveactive {}", position_to_string(pos.clone())),
        DispatchType::CycleWindow(dir) => format!(
            "cyclenext {}",
            match dir {
                CycleDirection::Next => "",
                CycleDirection::Previous => "prev",
            }
        ),
        DispatchType::FocusWindow(win) => {
            format!("focuswindow {}", match_window_identifier(win.clone()))
        }
        DispatchType::FocusMonitor(mon) => {
            format!("focusmonitor {}", match_mon_indentifier(mon.clone()))
        }
        DispatchType::ChangeSplitRatio(ratio) => format!("splitratio {}", ratio),
        DispatchType::ToggleOpaque => "toggleopaque".to_string(),
        DispatchType::MoveCursorToCorner(corner) => format!(
            "movecursortocorner {}",
            match corner {
                Corner::BottomLeft => "0",
                Corner::BottomRight => "1",
                Corner::TopRight => "2",
                Corner::TopLeft => "3",
            }
        ),
        DispatchType::WorkspaceOption(opt) => format!(
            "workspaceopt {}",
            match opt {
                WorkspaceOptions::AllFloat => "allfloat",
                WorkspaceOptions::AllPseudo => "allpseudo",
            }
        ),
        DispatchType::Exit => "exit".to_string(),
        DispatchType::ForceRendererReload => "forcerendererreload".to_string(),
        DispatchType::MoveCurrentWorkspaceToMonitor(mon) => {
            format!(
                "movecurrentworkspacetomonitor {}",
                match_mon_indentifier(mon.clone())
            )
        }
        DispatchType::MoveWorkspaceToMonitor(work, mon) => format!(
            "movecurrentworkspacetomonitor {} {}",
            match_workspace_identifier(work.clone()),
            match_mon_indentifier(mon.clone())
        ),
        DispatchType::ToggleSpecialWorkspace => "togglespecialworkspace".to_string(),
        DispatchType::Keyword(key, val) => {
            format!("{key} {val}", key = key.clone(), val = val.clone())
        }
        DispatchType::SetCursor(theme, size) => {
            format!("{theme} {size}", theme = theme.clone(), size = *size)
        }
    };
    if let DispatchType::Keyword(_, _) = cmd {
        Ok(format!("keyword {string_to_pass}"))
    } else if let DispatchType::SetCursor(_, _) = cmd {
        Ok(format!("setcursor {string_to_pass}"))
    } else {
        Ok(format!("dispatch {string_to_pass}"))
    }
}

/// This function calls a specified dispatcher (blocking)
///
/// ```rust
/// # fn main() -> std::io::Result<()> {
/// use hyprland::dispatch::{DispatchType,dispatch_blocking};
/// // This is an example of just one dispatcher, there are many more!
/// dispatch_blocking(DispatchType::Exec("something".to_string()))
/// # }
/// ```
pub fn dispatch_blocking(dispatch_type: DispatchType) -> io::Result<()> {
    let socket_path = get_socket_path(SocketType::Command);
    let output = write_to_socket_sync(socket_path, gen_dispatch_str(dispatch_type)?.as_bytes());

    match output {
        Ok(msg) => match msg.as_str() {
            "ok" => Ok(()),
            msg => panic!(
                "Hyprland returned a non `ok` value to the dispatcher, this is usually a error, output:({msg})"
            ),
        },
        Err(error) => panic!("A error occured when running the dispatcher: {error:#?}"),
    }
}

/// This function calls a specified dispatcher (async)
///
/// ```rust
/// # async fn function() -> std::io::Result<()> {
/// use hyprland::dispatch::{DispatchType,dispatch};
/// // This is an example of just one dispatcher, there are many more!
/// dispatch(DispatchType::Exec("kitty".to_string())).await?;
/// # Ok(())
/// # }
/// ```
pub async fn dispatch(dispatch_type: DispatchType) -> io::Result<()> {
    let socket_path = get_socket_path(SocketType::Command);
    let output = write_to_socket(socket_path, gen_dispatch_str(dispatch_type)?.as_bytes()).await;

    match output {
        Ok(msg) => match msg.as_str() {
            "ok" => Ok(()),
            msg => panic!(
                "Hyprland returned a non `ok` value to the dispatcher, this is usually a error, output:({msg})"
            ),
        },
        Err(error) => panic!("A error occured when running the dispatcher: {error:#?}"),
    }
}