/*
 * Copyright (c) 2024. The RigelA open source project team and
 * its contributors reserve all rights.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations under the License.
 * Modified by igaisin 2025
 */

#![allow(non_upper_case_globals)]

pub(crate) mod jab_lib;

pub mod callback;
pub mod context;
pub mod hyperlink;
pub mod hypertext;
pub mod key_binding;
pub mod relation;
pub mod role;
pub mod table;
pub mod text;
pub mod version;

use crate::{
    add_event_fp, find_library_path,
    jab::{
        callback::{AccessibleCallback, AccessibleContextType},
        context::AccessibleContext,
        jab_lib::{JabLib, packages::JObject64},
    },
    utils::{StringExt, pump_waiting_messages},
};
use std::sync::{Arc, LazyLock, Mutex};
use windows::Win32::Foundation::HWND;
// use win_wrap::{common::HWND, ext::StringExt, message::pump_waiting_messages};

static LIB: LazyLock<JabLib> = LazyLock::new(|| {
    #[cfg(target_arch = "x86_64")]
    let path = find_library_path("WindowsAccessBridge-64.dll").unwrap_or_default();
    #[cfg(target_arch = "x86")]
    let path = crate::find_library_path("WindowsAccessBridge-32.dll").unwrap_or_default();

    pump_waiting_messages();
    JabLib::new(Some(path)).unwrap()
});

static FUNCS: Mutex<Vec<AccessibleCallback>> = Mutex::new(vec![]);

#[derive(Debug)]
pub struct Jab {
    _lib: &'static JabLib,
}

impl Jab {
    /**
     * Создать новый экземпляр.
     */
    pub fn new() -> Self {
        Self { _lib: &*LIB }
    }

    /**
     * Получить контекст из объекта фокуса на окне.
     * `target` - это дескриптор целевого окна.
     */
    pub fn get_context_from_hwnd(&'_ self, target: HWND) -> Option<AccessibleContext<'_>> {
        AccessibleContext::from_hwnd(&self._lib, target)
    }

    /**
     * Получить контекст из окна.
     * `h_wnd` - это дескриптор родительского окна.
     */
    pub fn get_context_with_focus(&'_ self, h_wnd: HWND) -> Option<AccessibleContext<'_>> {
        AccessibleContext::from_focus(&self._lib, h_wnd)
    }

    /**
     * Определить, является ли окно Java-окном.
     * `h_wnd` - это дескриптор окна.
     */
    pub fn is_java_window(&self, h_wnd: HWND) -> bool {
        pump_waiting_messages();
        self._lib.is_java_window(h_wnd)
    }

    /**
     * Получить количество событий, ожидающих выполнения.
     */
    pub fn get_events_waiting(&self) -> i32 {
        self._lib.get_events_waiting()
    }

    /**
     * Удалить всех слушателей.
     */
    pub fn remove_all_listeners(&self) {
        let mut lock = FUNCS.lock().unwrap();
        lock.clear();
    }
}

unsafe impl Send for Jab {}
unsafe impl Sync for Jab {}

// Добавить слушателей событий для различных событий доступности
add_event_fp!(
    general,
    LIB,
    FUNCS,
    cb_caret_update,
    add_on_caret_update_listener,
    AccessibleCallback::CaretUpdate,
    set_caret_update_fp,
    "изменение позиции каретки"
);
add_event_fp!(
    general,
    LIB,
    FUNCS,
    cb_focus_gained,
    add_on_focus_gained_listener,
    AccessibleCallback::FocusGained,
    set_focus_gained_fp,
    "получение фокуса"
);
add_event_fp!(
    general,
    LIB,
    FUNCS,
    cb_mouse_clicked,
    add_on_mouse_clicked_listener,
    AccessibleCallback::MouseClicked,
    set_mouse_clicked_fp,
    "щелчок мыши"
);
add_event_fp!(
    general,
    LIB,
    FUNCS,
    cb_mouse_entered,
    add_on_mouse_entered_listener,
    AccessibleCallback::MouseEntered,
    set_mouse_entered_fp,
    "вход мыши"
);
add_event_fp!(
    general,
    LIB,
    FUNCS,
    cb_mouse_exited,
    add_on_mouse_exited_listener,
    AccessibleCallback::MouseExited,
    set_mouse_exited_fp,
    "выход мыши"
);
add_event_fp!(
    general,
    LIB,
    FUNCS,
    cb_mouse_pressed,
    add_on_mouse_pressed_listener,
    AccessibleCallback::MousePressed,
    set_mouse_pressed_fp,
    "нажатие мыши"
);
add_event_fp!(
    general,
    LIB,
    FUNCS,
    cb_mouse_released,
    add_on_mouse_released_listener,
    AccessibleCallback::MouseReleased,
    set_mouse_released_fp,
    "отпускание мыши"
);
add_event_fp!(
    general,
    LIB,
    FUNCS,
    cb_menu_canceled,
    add_on_menu_canceled_listener,
    AccessibleCallback::MenuCanceled,
    set_menu_canceled_fp,
    "отмена меню"
);
add_event_fp!(
    general,
    LIB,
    FUNCS,
    cb_menu_deselected,
    add_on_menu_deselected_listener,
    AccessibleCallback::MenuDeselected,
    set_menu_deselected_fp,
    "снятие выделения меню"
);
add_event_fp!(
    general,
    LIB,
    FUNCS,
    cb_menu_selected,
    add_on_menu_selected_listener,
    AccessibleCallback::MenuSelected,
    set_menu_selected_fp,
    "выбор меню"
);
add_event_fp!(
    general,
    LIB,
    FUNCS,
    cb_popup_menu_canceled,
    add_on_popup_menu_canceled_listener,
    AccessibleCallback::PopupMenuCanceled,
    set_popup_menu_canceled_fp,
    "отмена всплывающего меню"
);
add_event_fp!(
    general,
    LIB,
    FUNCS,
    cb_popup_menu_will_become_invisible,
    add_on_popup_menu_will_become_invisible_listener,
    AccessibleCallback::PopupMenuWillBecomeInvisible,
    set_popup_menu_will_become_invisible_fp,
    "всплывающее меню станет невидимым"
);
add_event_fp!(
    general,
    LIB,
    FUNCS,
    cb_popup_menu_will_become_visible,
    add_on_popup_menu_will_become_visible_listener,
    AccessibleCallback::PopupMenuWillBecomeVisible,
    set_popup_menu_will_become_visible_fp,
    "всплывающее меню станет видимым"
);
add_event_fp!(
    general,
    LIB,
    FUNCS,
    cb_property_selection_change,
    add_on_property_selection_change_listener,
    AccessibleCallback::PropertySelectionChange,
    set_property_selection_change_fp,
    "изменение выбора свойства"
);
add_event_fp!(
    general,
    LIB,
    FUNCS,
    cb_property_text_change,
    add_on_property_text_change_listener,
    AccessibleCallback::PropertyTextChange,
    set_property_text_change_fp,
    "изменение текста свойства"
);
add_event_fp!(
    general,
    LIB,
    FUNCS,
    cb_property_visible_data_change,
    add_on_property_visible_data_change_listener,
    AccessibleCallback::PropertyVisibleDataChange,
    set_property_visible_data_change_fp,
    "изменение видимых данных свойства"
);
add_event_fp!(property_change, LIB, FUNCS, "изменение свойства");
add_event_fp!(
    property_x_change,
    LIB,
    FUNCS,
    cb_property_name_change,
    add_on_property_name_change_listener,
    AccessibleCallback::PropertyNameChange,
    set_property_name_change_fp,
    "изменение имени свойства"
);
add_event_fp!(
    property_x_change,
    LIB,
    FUNCS,
    cb_property_description_change,
    add_on_property_description_change_listener,
    AccessibleCallback::PropertyDescriptionChange,
    set_property_description_change_fp,
    "изменение описания свойства"
);
add_event_fp!(
    property_x_change,
    LIB,
    FUNCS,
    cb_property_state_change,
    add_on_property_state_change_listener,
    AccessibleCallback::PropertyStateChange,
    set_property_state_change_fp,
    "изменение состояния свойства"
);
add_event_fp!(
    property_x_change,
    LIB,
    FUNCS,
    cb_property_value_change,
    add_on_property_value_change_listener,
    AccessibleCallback::PropertyValueChange,
    set_property_value_change_fp,
    "изменение значения свойства"
);
add_event_fp!(
    property_caret_change,
    LIB,
    FUNCS,
    "изменение позиции каретки свойства"
);
add_event_fp!(
    property_y_change,
    LIB,
    FUNCS,
    cb_property_child_change,
    add_on_property_child_change_listener,
    AccessibleCallback::PropertyChildChange,
    set_property_child_change_fp,
    "изменение дочернего свойства"
);
add_event_fp!(
    property_y_change,
    LIB,
    FUNCS,
    cb_property_active_descendent_change,
    add_on_property_active_descendent_change_listener,
    AccessibleCallback::PropertyActiveDescendentChange,
    set_property_active_descendent_change_fp,
    "изменение активного потомка свойства"
);
add_event_fp!(
    property_x_change,
    LIB,
    FUNCS,
    cb_property_table_model_change,
    add_on_property_table_model_change_listener,
    AccessibleCallback::PropertyTableModelChange,
    set_property_table_model_change_fp,
    "изменение модели таблицы свойства"
);
add_event_fp!(java_shutdown, LIB, FUNCS, "завершение работы Java");

#[cfg(all(test, target_arch = "x86_64"))]
mod test_jab {
    use crate::jab::{Jab, role::AccessibleRole};
    use crate::set_custom_search_directory;
    use windows::Win32::Foundation::HWND;
    use windows::Win32::UI::WindowsAndMessaging::{
        DispatchMessageW, FindWindowW, GetDesktopWindow, GetMessageW, MSG, TranslateMessage,
        WM_QUIT,
    };
    use windows_core::HSTRING;

    //noinspection SpellCheckingInspection
    /**
    Извлекает сообщения из очереди сообщений вызывающего потока. Функция обрабатывает входящие отправленные сообщения,
    пока не появятся опубликованные сообщения для извлечения.
    В отличие от GetMessage, функция PeekMessage не ожидает публикации сообщений перед возвратом.
    `msg` - структура сообщения, которая получает информацию о сообщениях из очереди потока.
    `h_wnd` - дескриптор окна, сообщения которого нужно получить. Окно должно принадлежать текущему потоку.
             Если h_wnd равен NULL, GetMessage извлекает сообщения для любого окна, принадлежащего текущему потоку,
             а также любые сообщения в очереди текущего потока со значением h_wnd равным NULL.
             Таким образом, если h_wnd равен NULL, обрабатываются как оконные сообщения, так и сообщения потока.
             Если h_wnd равен -1, GetMessage извлекает только сообщения в очереди текущего потока со значением h_wnd равным NULL,
             то есть сообщения потока, опубликованные через PostMessage (когда параметр h_wnd равен NULL) или PostThreadMessage.
    `msg_filter_min` - целочисленное значение минимального номера сообщения для извлечения.
                      Используйте WM_KEYFIRST (0x0100) для указания первого сообщения клавиатуры
                      или WM_MOUSEFIRST (0x0200) для указания первого сообщения мыши.
                      Используйте WM_INPUT здесь и в msg_filter_max, чтобы указать только сообщения WM_INPUT.
                      Если и msg_filter_min, и msg_filter_max равны нулю, GetMessage возвращает все доступные сообщения
                      (т.е. без фильтрации по диапазону).
    `msg_filter_max` - целочисленное значение максимального номера сообщения для извлечения.
                      Используйте WM_KEYLAST для указания последнего сообщения клавиатуры,
                      WM_MOUSELAST для указания последнего сообщения мыши.
                      Используйте WM_INPUT здесь и в msg_filter_min, чтобы указать только сообщения WM_INPUT.
                      Если и msg_filter_min, и msg_filter_max равны нулю, GetMessage возвращает все доступные сообщения
                      (т.е. без фильтрации по диапазону).
    */
    pub(crate) fn get_message(
        msg: &mut MSG,
        h_wnd: Option<HWND>,
        msg_filter_min: u32,
        msg_filter_max: u32,
    ) -> bool {
        unsafe { GetMessageW(msg, h_wnd, msg_filter_min, msg_filter_max) }.as_bool()
    }

    //noinspection SpellCheckingInspection
    /**
    Создает цикл обработки оконных сообщений в текущем потоке до получения сообщения WM_QUIT.
    `slot` - функция обратного вызова, которая уведомляет о получении сообщения,
             позволяя выполнить пользовательскую обработку сообщения.
    */
    pub(crate) fn message_loop(slot: impl Fn(&MSG)) {
        let mut msg = MSG::default();
        while get_message(&mut msg, None, 0, 0) != false {
            if msg.message == WM_QUIT {
                break;
            }
            slot(&msg);
            unsafe {
                DispatchMessageW(&msg);
                TranslateMessage(&msg).as_bool();
            }
        }
    }

    /**
    Находит окно верхнего уровня, имя класса и заголовок которого соответствуют указанным строкам.
    Эта функция не выполняет поиск среди дочерних окон.
    `class_name` - Строка, указывающая имя класса, или атом (atom), который идентифицирует строку имени класса.
                  Если этот параметр является атомом, он должен быть глобальным атомом, созданным ранее с помощью функции GlobalAddAtom.
                  Атом (16-битное значение) должен быть помещен в младший байт параметра `class_name`,
                  а старший байт `class_name` должен быть обнулен.
                  Если параметр равен NULL, будут найдены все окна, соответствующие параметру `window_name`.
    `window_name` - Строка, указывающая имя окна (заголовок окна).
                   Если этот параметр равен NULL, соответствуют все заголовки окон.
    */
    pub fn find_window(class_name: Option<&str>, window_name: Option<&str>) -> HWND {
        unsafe {
            match (class_name, window_name) {
                (Some(c), Some(w)) => FindWindowW(&HSTRING::from(c), &HSTRING::from(w)),
                (Some(c), None) => FindWindowW(&HSTRING::from(c), None),
                (None, Some(w)) => FindWindowW(None, &HSTRING::from(w)),
                _ => FindWindowW(None, None),
            }
            .unwrap_or(Default::default())
        }
    }

    #[test]
    fn main() {
        set_custom_search_directory("C:\\Users\\igaisin\\AppData\\Roaming\\NCALayer\\jre\\bin".parse().unwrap());
        let jab = Jab::new();
        assert!(!jab.is_java_window(unsafe { GetDesktopWindow() }));
        let h_wnd = find_window(Some("SunAwtFrame"), None);
        assert!(jab.is_java_window(h_wnd));
        dbg!(jab.get_events_waiting());
        jab.add_on_focus_gained_listener(|src| {
            dbg!(src);
        });
        jab.add_on_property_active_descendent_change_listener(|src, old, new| {
            dbg!(src);
            dbg!(old);
            dbg!(new);
        });
        let context = jab.get_context_from_hwnd(h_wnd).unwrap();
        dbg!(&context);
        assert_eq!(h_wnd, context.get_hwnd());
        dbg!(context.get_at(100, 100));
        let context = jab.get_context_with_focus(h_wnd).unwrap();
        dbg!(&context);
        dbg!(context.get_child(0));
        dbg!(context.get_parent());
        dbg!(context.get_parent_with_role(&AccessibleRole::ToolBar));
        dbg!(context.get_parent_with_role_else_root(&AccessibleRole::ToolBar));
        dbg!(context.get_top_level());
        dbg!(context.get_active_descendent());
        dbg!(context.get_depth());
        dbg!(context.get_version());
        dbg!(context.get_states_en_us());
        dbg!(context.get_bound_rectangle());
        dbg!(context.get_index_in_parent());
        dbg!(context.get_child_count());
        let actions = context.get_actions();
        dbg!(&actions);
        dbg!(context.do_actions(&actions));
        dbg!(context.get_relations());
        dbg!(context.get_key_bindings());
        dbg!(context.get_icons());
        dbg!(context.get_virtual_name(10));
        dbg!(context.get_current_value(10));
        dbg!(context.get_maximum_value(10));
        dbg!(context.get_minimum_value(10));
        dbg!(context.request_focus());
        dbg!(context.get_visible_child_count());
        dbg!(context.get_visible_children(0));
        dbg!(context.set_caret_position(2));
        dbg!(context.get_caret_location(2));
        if let Some(table) = context.get_table() {
            dbg!(&table);
            dbg!(table.get_column_description(0));
            dbg!(table.get_row_description(0));
            dbg!(table.get_row_header());
            dbg!(table.get_column_header());
            dbg!(table.is_row_selected(0));
            dbg!(table.is_column_selected(0));
            dbg!(table.get_cell(0, 0));
            dbg!(table.get_column(0));
            dbg!(table.get_row(0));
            dbg!(table.get_column_selection_count());
            dbg!(table.get_row_selection_count());
            dbg!(table.get_index(0, 0));
            dbg!(table.get_column_selections(1));
            dbg!(table.get_row_selections(1));
        }
        if let Some(hypertext) = context.get_hypertext() {
            dbg!(&hypertext);
            let links = hypertext.get_links();
            dbg!(&links);
            if !links.is_empty() {
                dbg!(links[0].activate());
            }
            dbg!(hypertext.get_hyperlink(0));
            dbg!(hypertext.get_hyperlink_count());
            dbg!(hypertext.get_link_index(0));
        }
        dbg!(context.get_hypertext_ext(0));
        context.add_selection(0);
        context.remove_selection(0);
        context.clear_selection();
        context.select_all();
        dbg!(context.get_selection_count());
        dbg!(context.get_selection(0));
        dbg!(context.is_child_selected(0));
        dbg!(context.is_supported_selection());
        dbg!(context.select_text_range(0, 8));
        dbg!(context.get_text_attributes_in_range(0, 8));
        dbg!(context.get_text_selection());
        dbg!(context.get_text_info(100, 100));
        dbg!(context.get_text_attributes(0));
        dbg!(context.get_text_items(0));
        dbg!(context.get_text_range(0, 8));
        dbg!(context.get_text_line_bounds(0));
        dbg!(context.get_text_rect(0));
        dbg!(context.set_text_contents("Test"));
        dbg!(&jab);
        message_loop(|_| ());
    }
}
