use {
    crate::{
        allocator::{Allocator, AllocatorError},
        async_engine::SpawnedFuture,
        backend::{
            AxisSource, Backend, BackendConnectorState, BackendEvent, ButtonState, Connector,
            ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, InputDevice,
            InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod, InputDeviceId,
            InputEvent, KeyState, Mode, MonitorInfo, ScrollAxis, TransformMatrix,
            transaction::{
                BackendAppliedConnectorTransaction, BackendConnectorTransaction,
                BackendConnectorTransactionError, BackendConnectorTransactionType,
                BackendConnectorTransactionTypeDyn, BackendPreparedConnectorTransaction,
            },
        },
        cmm::cmm_primaries::Primaries,
        compositor::TestFuture,
        drm_feedback::DrmFeedback,
        fixed::Fixed,
        format::XRGB8888,
        gfx_api::GfxError,
        gfx_apis::create_vulkan_allocator,
        ifs::wl_output::OutputId,
        it::{
            test_error::TestResult, test_gfx_api::TestGfxCtx, test_utils::test_expected_event::TEEH,
        },
        state::State,
        udmabuf::Udmabuf,
        utils::{
            clonecell::CloneCell,
            copyhashmap::CopyHashMap,
            errorfmt::ErrorFmt,
            numcell::NumCell,
            on_change::OnChange,
            oserror::{OsError, OsErrorExt2},
            syncqueue::SyncQueue,
        },
        video::{
            drm::{ConnectorType, Drm, DrmError},
            gbm::{GbmDevice, GbmError},
        },
    },
    ahash::AHashMap,
    bstr::ByteSlice,
    std::{
        any::Any,
        cell::{Cell, RefCell},
        error::Error,
        io,
        os::unix::ffi::OsStrExt,
        pin::Pin,
        rc::Rc,
    },
    thiserror::Error,
    uapi::c,
};

#[derive(Debug, Error)]
pub enum TestBackendError {
    #[error("Could not read /dev/dri")]
    ReadDri(#[source] io::Error),
    #[error("There are no drm nodes in /dev/dri")]
    NoDrmNode,
    #[error("Could not open drm node {0}")]
    OpenDrmNode(String, #[source] OsError),
    #[error("Could not open the drm device")]
    OpenDrmDevice(#[source] DrmError),
    #[error("Could not create a render context")]
    RenderContext(#[source] GfxError),
    #[error("Could not create a gbm device")]
    CreateGbmDevice(#[source] GbmError),
    #[error("Could not create any allocator")]
    CreateAllocator,
    #[error("Could not create a vulkan allocator")]
    CreateVulkanAllocator(#[source] AllocatorError),
}

pub struct TestBackend {
    pub state: Rc<State>,
    pub test_future: TestFuture,
    pub default_monitor_info: MonitorInfo,
    pub default_connector: Rc<TestConnector>,
    pub default_mouse: Rc<TestBackendMouse>,
    pub default_kb: Rc<TestBackendKb>,
    pub render_context_installed: Cell<bool>,
}

impl TestBackend {
    pub fn new(state: &Rc<State>, future: TestFuture) -> Self {
        state.set_backend_idle(false);
        let mode = Mode {
            width: 800,
            height: 600,
            refresh_rate_millihz: 60_000,
        };
        let bcs = BackendConnectorState {
            serial: state.backend_connector_state_serials.next(),
            enabled: true,
            active: true,
            mode,
            non_desktop_override: None,
            vrr: false,
            tearing: false,
            format: XRGB8888,
            color_space: Default::default(),
            eotf: Default::default(),
            gamma_lut: Default::default(),
        };
        let default_connector = Rc::new(TestConnector {
            id: state.connector_ids.next(),
            kernel_id: ConnectorKernelId {
                ty: ConnectorType::VGA,
                idx: 1,
            },
            events: Default::default(),
            feedback: Default::default(),
            idle: Default::default(),
            damage_calls: NumCell::new(0),
            state: RefCell::new(bcs.clone()),
        });
        let default_mouse = Rc::new(TestBackendMouse {
            common: TestInputDeviceCommon {
                id: state.input_device_ids.next(),
                removed: Cell::new(false),
                events: Default::default(),
                on_change: Default::default(),
                capabilities: {
                    let chm = CopyHashMap::new();
                    chm.set(InputDeviceCapability::Pointer, ());
                    chm
                },
                name: Rc::new("default-mouse".to_string()),
                state: state.clone(),
            },
            transform_matrix: Cell::new([[1.0, 0.0], [0.0, 1.0]]),
            accel_speed: Cell::new(1.0),
            accel_profile: Cell::new(InputDeviceAccelProfile::Flat),
            left_handed: Cell::new(false),
        });
        let default_kb = Rc::new(TestBackendKb {
            common: TestInputDeviceCommon {
                id: state.input_device_ids.next(),
                removed: Cell::new(false),
                events: Default::default(),
                on_change: Default::default(),
                capabilities: {
                    let chm = CopyHashMap::new();
                    chm.set(InputDeviceCapability::Keyboard, ());
                    chm
                },
                name: Rc::new("default-keyboard".to_string()),
                state: state.clone(),
            },
        });
        let default_monitor_info = MonitorInfo {
            modes: Some(vec![mode]),
            output_id: OutputId::new("", "jay", "TestConnector", default_connector.id.to_string()),
            width_mm: 80,
            height_mm: 60,
            non_desktop: false,
            non_desktop_effective: false,
            vrr_capable: false,
            eotfs: vec![],
            color_spaces: vec![],
            primaries: Primaries::SRGB,
            luminance: None,
            state: bcs,
        };
        Self {
            state: state.clone(),
            test_future: future,
            default_monitor_info,
            default_connector,
            default_mouse,
            default_kb,
            render_context_installed: Cell::new(false),
        }
    }

    pub fn install_render_context(&self, need_drm: bool) -> TestResult {
        if self.render_context_installed.get() {
            return Ok(());
        }
        self.create_render_context(need_drm)?;
        self.render_context_installed.set(true);
        Ok(())
    }

    pub fn install_default(&self) -> TestResult {
        self.install_default2(false)
    }

    pub fn install_default2(&self, need_drm: bool) -> TestResult {
        self.install_render_context(need_drm)?;
        self.state
            .backend_events
            .push(BackendEvent::NewConnector(self.default_connector.clone()));
        self.default_connector
            .events
            .send_event(ConnectorEvent::Connected(self.default_monitor_info.clone()));
        self.state
            .backend_events
            .push(BackendEvent::NewInputDevice(self.default_kb.clone()));
        self.state
            .backend_events
            .push(BackendEvent::NewInputDevice(self.default_mouse.clone()));
        Ok(())
    }

    fn create_render_context(&self, need_drm: bool) -> Result<(), TestBackendError> {
        macro_rules! constructor {
            ($c:expr) => {
                constructor!($c, |a| Rc::new(a) as Rc<dyn Allocator>)
            };
            ($c:expr, nomap) => {
                constructor!($c, |a| a)
            };
            ($c:expr, $map:expr) => {
                (&|| $c.map($map).map_err(|e| Box::new(e) as Box<dyn Error>))
                    as &dyn Fn() -> Result<Rc<dyn Allocator>, Box<dyn Error>>
            };
        }
        let udmabuf = ("udmabuf", constructor!(Udmabuf::new()));
        let vulkan = (
            "Vulkan",
            constructor!(create_vk_allocator(&self.state), nomap),
        );
        let gbm = ("GBM", constructor!(create_gbm_allocator()));
        let allocators = match need_drm {
            true => [vulkan, gbm, udmabuf],
            false => [udmabuf, vulkan, gbm],
        };
        let mut allocator = None::<Rc<dyn Allocator>>;
        for (name, f) in allocators {
            match f() {
                Ok(a) => {
                    allocator = Some(a);
                    break;
                }
                Err(e) => {
                    log::error!("Could not create {name} allocator: {}", ErrorFmt(&*e));
                }
            }
        }
        let allocator = allocator.ok_or(TestBackendError::CreateAllocator)?;
        let ctx = match TestGfxCtx::new(allocator) {
            Ok(ctx) => ctx,
            Err(e) => return Err(TestBackendError::RenderContext(e)),
        };
        self.state.set_render_ctx(Some(ctx));
        Ok(())
    }
}

fn create_gbm_allocator() -> Result<GbmDevice, TestBackendError> {
    create_drm_allocator(|drm| GbmDevice::new(&drm).map_err(TestBackendError::CreateGbmDevice))
}

fn create_vk_allocator(state: &State) -> Result<Rc<dyn Allocator>, TestBackendError> {
    create_drm_allocator(|drm| {
        create_vulkan_allocator(&drm, &state.eventfd_cache)
            .map_err(TestBackendError::CreateVulkanAllocator)
    })
}

fn create_drm_allocator<T, F>(f: F) -> Result<T, TestBackendError>
where
    F: FnOnce(Drm) -> Result<T, TestBackendError>,
{
    let dri = match std::fs::read_dir("/dev/dri") {
        Ok(d) => d,
        Err(e) => return Err(TestBackendError::ReadDri(e)),
    };
    let mut files = vec![];
    for f in dri {
        let f = match f {
            Ok(f) => f,
            Err(e) => return Err(TestBackendError::ReadDri(e)),
        };
        files.push(f.path());
    }
    let node = 'node: {
        for f in &files {
            if let Some(file) = f.file_name()
                && file.as_bytes().starts_with_str("renderD")
            {
                break 'node f;
            }
        }
        for f in &files {
            if let Some(file) = f.file_name()
                && file.as_bytes().starts_with_str("card")
            {
                break 'node f;
            }
        }
        return Err(TestBackendError::NoDrmNode);
    };
    let file = uapi::open(node.as_path(), c::O_RDWR | c::O_CLOEXEC, 0)
        .map(Rc::new)
        .map_os_err(|e| {
            TestBackendError::OpenDrmNode(node.as_os_str().as_bytes().as_bstr().to_string(), e)
        })?;
    let drm = Drm::open_existing(file).map_err(TestBackendError::OpenDrmDevice)?;
    f(drm)
}

impl Backend for TestBackend {
    fn run(self: Rc<Self>) -> SpawnedFuture<Result<(), Box<dyn std::error::Error>>> {
        let future = (self.test_future)(&self.state);
        self.state.eng.spawn("", async move {
            let future: Pin<_> = future.into();
            future.await;
            Ok(())
        })
    }

    fn switch_to(&self, vtnr: u32) {
        let _ = vtnr;
    }

    fn supports_presentation_feedback(&self) -> bool {
        true
    }
}

pub struct TestConnector {
    pub id: ConnectorId,
    pub kernel_id: ConnectorKernelId,
    pub events: OnChange<ConnectorEvent>,
    pub feedback: CloneCell<Option<Rc<DrmFeedback>>>,
    pub idle: TEEH<bool>,
    pub damage_calls: NumCell<u32>,
    pub state: RefCell<BackendConnectorState>,
}

impl Connector for TestConnector {
    fn id(&self) -> ConnectorId {
        self.id
    }

    fn kernel_id(&self) -> ConnectorKernelId {
        self.kernel_id
    }

    fn event(&self) -> Option<ConnectorEvent> {
        self.events.events.pop()
    }

    fn on_change(&self, cb: Rc<dyn Fn()>) {
        self.events.on_change.set(Some(cb));
    }

    fn damage(&self) {
        self.damage_calls.fetch_add(1);
    }

    fn drm_dev(&self) -> Option<DrmDeviceId> {
        None
    }

    fn effectively_locked(&self) -> bool {
        // todo
        true
    }

    fn state(&self) -> BackendConnectorState {
        self.state.borrow().clone()
    }

    fn drm_feedback(&self) -> Option<Rc<DrmFeedback>> {
        self.feedback.get()
    }

    fn transaction_type(&self) -> Box<dyn BackendConnectorTransactionTypeDyn> {
        Box::new(TestBackendTransactionType)
    }

    fn create_transaction(
        &self,
    ) -> Result<Box<dyn BackendConnectorTransaction>, BackendConnectorTransactionError> {
        Ok(Box::new(TestBackendTransaction::default()))
    }
}

#[derive(Hash, Eq, PartialEq)]
struct TestBackendTransactionType;
impl BackendConnectorTransactionType for TestBackendTransactionType {}

#[derive(Default)]
struct TestBackendTransaction {
    connectors: AHashMap<ConnectorId, (Rc<TestConnector>, BackendConnectorState)>,
}
impl BackendConnectorTransaction for TestBackendTransaction {
    fn add(
        &mut self,
        connector: &Rc<dyn Connector>,
        change: BackendConnectorState,
    ) -> Result<(), BackendConnectorTransactionError> {
        let c = (connector.clone() as Rc<dyn Any>)
            .downcast::<TestConnector>()
            .unwrap();
        self.connectors.insert(c.id(), (c, change));
        Ok(())
    }
    fn prepare(
        self: Box<Self>,
    ) -> Result<Box<dyn BackendPreparedConnectorTransaction>, BackendConnectorTransactionError>
    {
        Ok(self)
    }
}
impl BackendPreparedConnectorTransaction for TestBackendTransaction {
    fn apply(
        self: Box<Self>,
    ) -> Result<Box<dyn BackendAppliedConnectorTransaction>, BackendConnectorTransactionError> {
        for (c, s) in self.connectors.values() {
            *c.state.borrow_mut() = s.clone();
            c.idle.push(!s.active);
        }
        Ok(self)
    }
}
impl BackendAppliedConnectorTransaction for TestBackendTransaction {
    fn commit(self: Box<Self>) {
        // nothing
    }
    fn rollback(self: Box<Self>) -> Result<(), BackendConnectorTransactionError> {
        unimplemented!()
    }
}

pub struct TestMouseClick {
    pub mouse: Rc<TestBackendMouse>,
    pub button: u32,
}

impl Drop for TestMouseClick {
    fn drop(&mut self) {
        self.mouse.common.event(InputEvent::Button {
            time_usec: self.mouse.common.state.now_usec(),
            button: self.button,
            state: ButtonState::Released,
        });
    }
}

pub struct TestBackendMouse {
    pub common: TestInputDeviceCommon,
    pub transform_matrix: Cell<TransformMatrix>,
    pub accel_speed: Cell<f64>,
    pub accel_profile: Cell<InputDeviceAccelProfile>,
    pub left_handed: Cell<bool>,
}

impl TestBackendMouse {
    pub fn rel(&self, dx: f64, dy: f64) {
        self.common.event(InputEvent::Motion {
            time_usec: self.common.state.now_usec(),
            dx: Fixed::from_f64(dx * self.accel_speed.get()),
            dy: Fixed::from_f64(dy * self.accel_speed.get()),
            dx_unaccelerated: Fixed::from_f64(dx),
            dy_unaccelerated: Fixed::from_f64(dy),
        })
    }

    pub fn abs(&self, connector: &TestConnector, x: f64, y: f64) {
        self.common.event(InputEvent::ConnectorPosition {
            time_usec: self.common.state.now_usec(),
            connector: connector.id,
            x: Fixed::from_f64(x),
            y: Fixed::from_f64(y),
        })
    }

    pub fn click(self: &Rc<Self>, button: u32) -> TestMouseClick {
        self.common.event(InputEvent::Button {
            time_usec: self.common.state.now_usec(),
            button,
            state: ButtonState::Pressed,
        });
        TestMouseClick {
            mouse: self.clone(),
            button,
        }
    }

    pub fn scroll(&self, dy: i32) {
        self.common.event(InputEvent::AxisSource {
            source: AxisSource::Wheel,
        });
        self.common.event(InputEvent::Axis120 {
            dist: dy * 120,
            axis: ScrollAxis::Vertical,
            inverted: false,
        });
        self.common.event(InputEvent::AxisFrame {
            time_usec: self.common.state.now_usec(),
        });
    }

    pub fn scroll_px(&self, dy: i32) {
        self.scroll_px2(dy, false);
    }

    pub fn scroll_px2(&self, dy: i32, inverted: bool) {
        self.common.event(InputEvent::AxisSource {
            source: AxisSource::Finger,
        });
        self.common.event(InputEvent::AxisPx {
            dist: Fixed::from_int(dy),
            axis: ScrollAxis::Vertical,
            inverted,
        });
        self.common.event(InputEvent::AxisFrame {
            time_usec: self.common.state.now_usec(),
        });
    }
}

pub struct TestBackendKb {
    pub common: TestInputDeviceCommon,
}

pub struct PressedKey {
    pub kb: Rc<TestBackendKb>,
    pub key: u32,
}

impl Drop for PressedKey {
    fn drop(&mut self) {
        self.kb.common.event(InputEvent::Key {
            time_usec: self.kb.common.state.now_usec(),
            key: self.key,
            state: KeyState::Released,
        });
    }
}

impl TestBackendKb {
    pub fn press(self: &Rc<Self>, key: u32) -> PressedKey {
        self.common.event(InputEvent::Key {
            time_usec: self.common.state.now_usec(),
            key,
            state: KeyState::Pressed,
        });
        PressedKey {
            kb: self.clone(),
            key,
        }
    }
}

impl TestInputDevice for TestBackendKb {
    fn common(&self) -> &TestInputDeviceCommon {
        &self.common
    }
}

impl TestInputDevice for TestBackendMouse {
    fn common(&self) -> &TestInputDeviceCommon {
        &self.common
    }

    fn set_left_handed(&self, left_handed: bool) {
        self.left_handed.set(left_handed)
    }

    fn set_accel_profile(&self, profile: InputDeviceAccelProfile) {
        self.accel_profile.set(profile)
    }

    fn set_accel_speed(&self, speed: f64) {
        self.accel_speed.set(speed)
    }

    fn set_transform_matrix(&self, matrix: TransformMatrix) {
        self.transform_matrix.set(matrix);
    }
}

pub struct TestInputDeviceCommon {
    pub id: InputDeviceId,
    pub removed: Cell<bool>,
    pub events: SyncQueue<InputEvent>,
    pub on_change: CloneCell<Option<Rc<dyn Fn()>>>,
    pub capabilities: CopyHashMap<InputDeviceCapability, ()>,
    pub name: Rc<String>,
    pub state: Rc<State>,
}

impl TestInputDeviceCommon {
    pub fn event(&self, e: InputEvent) {
        self.events.push(e);
        if let Some(oc) = self.on_change.get() {
            oc();
        }
    }
}

trait TestInputDevice: InputDevice {
    fn common(&self) -> &TestInputDeviceCommon;

    fn set_left_handed(&self, left_handed: bool) {
        let _ = left_handed;
    }

    fn set_accel_profile(&self, profile: InputDeviceAccelProfile) {
        let _ = profile;
    }

    fn set_accel_speed(&self, speed: f64) {
        let _ = speed;
    }

    fn set_transform_matrix(&self, matrix: TransformMatrix) {
        let _ = matrix;
    }

    fn set_tap_enabled(&self, enabled: bool) {
        let _ = enabled;
    }

    fn set_drag_enabled(&self, enabled: bool) {
        let _ = enabled;
    }

    fn set_drag_lock_enabled(&self, enabled: bool) {
        let _ = enabled;
    }

    fn set_natural_scrolling_enabled(&self, enabled: bool) {
        let _ = enabled;
    }

    fn set_click_method(&self, method: InputDeviceClickMethod) {
        let _ = method;
    }

    fn set_middle_button_emulation_enabled(&self, enabled: bool) {
        let _ = enabled;
    }
}

impl<T: TestInputDevice> InputDevice for T {
    fn id(&self) -> InputDeviceId {
        self.common().id
    }

    fn removed(&self) -> bool {
        self.common().removed.get()
    }

    fn event(&self) -> Option<InputEvent> {
        self.common().events.pop()
    }

    fn on_change(&self, cb: Rc<dyn Fn()>) {
        self.common().on_change.set(Some(cb));
    }

    fn grab(&self, _grab: bool) {
        // nothing
    }

    fn has_capability(&self, cap: InputDeviceCapability) -> bool {
        self.common().capabilities.contains(&cap)
    }

    fn set_left_handed(&self, left_handed: bool) {
        <Self as TestInputDevice>::set_left_handed(self, left_handed)
    }

    fn set_accel_profile(&self, profile: InputDeviceAccelProfile) {
        <Self as TestInputDevice>::set_accel_profile(self, profile)
    }

    fn set_accel_speed(&self, speed: f64) {
        <Self as TestInputDevice>::set_accel_speed(self, speed)
    }

    fn set_transform_matrix(&self, matrix: TransformMatrix) {
        <Self as TestInputDevice>::set_transform_matrix(self, matrix)
    }

    fn name(&self) -> Rc<String> {
        self.common().name.clone()
    }

    fn dev_t(&self) -> Option<c::dev_t> {
        None
    }

    fn set_tap_enabled(&self, enabled: bool) {
        <Self as TestInputDevice>::set_tap_enabled(self, enabled)
    }

    fn set_drag_enabled(&self, enabled: bool) {
        <Self as TestInputDevice>::set_drag_enabled(self, enabled)
    }

    fn set_drag_lock_enabled(&self, enabled: bool) {
        <Self as TestInputDevice>::set_drag_lock_enabled(self, enabled)
    }

    fn set_natural_scrolling_enabled(&self, enabled: bool) {
        <Self as TestInputDevice>::set_natural_scrolling_enabled(self, enabled)
    }

    fn set_click_method(&self, method: InputDeviceClickMethod) {
        <Self as TestInputDevice>::set_click_method(self, method)
    }

    fn set_middle_button_emulation_enabled(&self, enabled: bool) {
        <Self as TestInputDevice>::set_middle_button_emulation_enabled(self, enabled)
    }
}
