use std::cmp;
use super::*;
use {LogicalPosition, LogicalSize};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AaRect {
    x: i64,
    y: i64,
    width: i64,
    height: i64,
}
impl AaRect {
    pub fn new((x, y): (i32, i32), (width, height): (u32, u32)) -> Self {
        let (x, y) = (x as i64, y as i64);
        let (width, height) = (width as i64, height as i64);
        AaRect { x, y, width, height }
    }
    pub fn contains_point(&self, x: i64, y: i64) -> bool {
        x >= self.x && x <= self.x + self.width && y >= self.y && y <= self.y + self.height
    }
    pub fn get_overlapping_area(&self, other: &Self) -> i64 {
        let x_overlap = cmp::max(
            0,
            cmp::min(self.x + self.width, other.x + other.width) - cmp::max(self.x, other.x),
        );
        let y_overlap = cmp::max(
            0,
            cmp::min(self.y + self.height, other.y + other.height) - cmp::max(self.y, other.y),
        );
        x_overlap * y_overlap
    }
}
#[derive(Debug)]
pub struct TranslatedCoords {
    pub x_rel_root: c_int,
    pub y_rel_root: c_int,
    pub child: ffi::Window,
}
#[derive(Debug)]
pub struct Geometry {
    pub root: ffi::Window,
    
    
    
    
    
    
    pub x_rel_parent: c_int,
    pub y_rel_parent: c_int,
    
    pub width: c_uint,
    pub height: c_uint,
    
    
    pub border: c_uint,
    pub depth: c_uint,
}
#[derive(Debug, Clone)]
pub struct FrameExtents {
    pub left: c_ulong,
    pub right: c_ulong,
    pub top: c_ulong,
    pub bottom: c_ulong,
}
impl FrameExtents {
    pub fn new(left: c_ulong, right: c_ulong, top: c_ulong, bottom: c_ulong) -> Self {
        FrameExtents { left, right, top, bottom }
    }
    pub fn from_border(border: c_ulong) -> Self {
        Self::new(border, border, border, border)
    }
    pub fn as_logical(&self, factor: f64) -> LogicalFrameExtents {
        let logicalize = |value: c_ulong| value as f64 / factor;
        LogicalFrameExtents {
            left: logicalize(self.left),
            right: logicalize(self.right),
            top: logicalize(self.top),
            bottom: logicalize(self.bottom),
        }
    }
}
#[derive(Debug, Clone)]
pub struct LogicalFrameExtents {
    pub left: f64,
    pub right: f64,
    pub top: f64,
    pub bottom: f64,
}
#[derive(Debug, Clone, PartialEq)]
pub enum FrameExtentsHeuristicPath {
    Supported,
    UnsupportedNested,
    UnsupportedBordered,
}
#[derive(Debug, Clone)]
pub struct FrameExtentsHeuristic {
    pub frame_extents: FrameExtents,
    pub heuristic_path: FrameExtentsHeuristicPath,
}
impl FrameExtentsHeuristic {
    pub fn inner_pos_to_outer(&self, x: i32, y: i32) -> (i32, i32) {
        use self::FrameExtentsHeuristicPath::*;
        if self.heuristic_path != UnsupportedBordered {
            (x - self.frame_extents.left as i32, y - self.frame_extents.top as i32)
        } else {
            (x, y)
        }
    }
    pub fn inner_pos_to_outer_logical(&self, mut logical: LogicalPosition, factor: f64) -> LogicalPosition {
        use self::FrameExtentsHeuristicPath::*;
        if self.heuristic_path != UnsupportedBordered {
            let frame_extents = self.frame_extents.as_logical(factor);
            logical.x -= frame_extents.left;
            logical.y -= frame_extents.top;
        }
        logical
    }
    pub fn inner_size_to_outer(&self, width: u32, height: u32) -> (u32, u32) {
        (
            width.saturating_add(
                self.frame_extents.left.saturating_add(self.frame_extents.right) as u32
            ),
            height.saturating_add(
                self.frame_extents.top.saturating_add(self.frame_extents.bottom) as u32
            ),
        )
    }
    pub fn inner_size_to_outer_logical(&self, mut logical: LogicalSize, factor: f64) -> LogicalSize {
        let frame_extents = self.frame_extents.as_logical(factor);
        logical.width += frame_extents.left + frame_extents.right;
        logical.height += frame_extents.top + frame_extents.bottom;
        logical
    }
}
impl XConnection {
    
    pub fn translate_coords(&self, window: ffi::Window, root: ffi::Window) -> Result<TranslatedCoords, XError> {
        let mut translated_coords: TranslatedCoords = unsafe { mem::uninitialized() };
        unsafe {
            (self.xlib.XTranslateCoordinates)(
                self.display,
                window,
                root,
                0,
                0,
                &mut translated_coords.x_rel_root,
                &mut translated_coords.y_rel_root,
                &mut translated_coords.child,
            );
        }
        
        self.check_errors().map(|_| translated_coords)
    }
    
    pub fn get_geometry(&self, window: ffi::Window) -> Result<Geometry, XError> {
        let mut geometry: Geometry = unsafe { mem::uninitialized() };
        let _status = unsafe {
            (self.xlib.XGetGeometry)(
                self.display,
                window,
                &mut geometry.root,
                &mut geometry.x_rel_parent,
                &mut geometry.y_rel_parent,
                &mut geometry.width,
                &mut geometry.height,
                &mut geometry.border,
                &mut geometry.depth,
            )
        };
        
        self.check_errors().map(|_| geometry)
    }
    fn get_frame_extents(&self, window: ffi::Window) -> Option<FrameExtents> {
        let extents_atom = unsafe { self.get_atom_unchecked(b"_NET_FRAME_EXTENTS\0") };
        if !hint_is_supported(extents_atom) {
            return None;
        }
        
        
        
        let extents: Option<Vec<c_ulong>> = self.get_property(
            window,
            extents_atom,
            ffi::XA_CARDINAL,
        ).ok();
        extents.and_then(|extents| {
            if extents.len() >= 4 {
                Some(FrameExtents {
                    left: extents[0],
                    right: extents[1],
                    top: extents[2],
                    bottom: extents[3],
                })
            } else {
                None
            }
        })
    }
    pub fn is_top_level(&self, window: ffi::Window, root: ffi::Window) -> Option<bool> {
        let client_list_atom = unsafe { self.get_atom_unchecked(b"_NET_CLIENT_LIST\0") };
        if !hint_is_supported(client_list_atom) {
            return None;
        }
        let client_list: Option<Vec<ffi::Window>> = self.get_property(
            root,
            client_list_atom,
            ffi::XA_WINDOW,
        ).ok();
        client_list.map(|client_list| client_list.contains(&window))
    }
    fn get_parent_window(&self, window: ffi::Window) -> Result<ffi::Window, XError> {
        let parent = unsafe {
            let mut root: ffi::Window = mem::uninitialized();
            let mut parent: ffi::Window = mem::uninitialized();
            let mut children: *mut ffi::Window = ptr::null_mut();
            let mut nchildren: c_uint = mem::uninitialized();
            
            let _status = (self.xlib.XQueryTree)(
                self.display,
                window,
                &mut root,
                &mut parent,
                &mut children,
                &mut nchildren,
            );
            
            if children != ptr::null_mut() {
                (self.xlib.XFree)(children as *mut _);
            }
            parent
        };
        self.check_errors().map(|_| parent)
    }
    fn climb_hierarchy(&self, window: ffi::Window, root: ffi::Window) -> Result<ffi::Window, XError> {
        let mut outer_window = window;
        loop {
            let candidate = self.get_parent_window(outer_window)?;
            if candidate == root {
                break;
            }
            outer_window = candidate;
        }
        Ok(outer_window)
    }
    pub fn get_frame_extents_heuristic(&self, window: ffi::Window, root: ffi::Window) -> FrameExtentsHeuristic {
        use self::FrameExtentsHeuristicPath::*;
        
        
        
        
        let (inner_y_rel_root, child) = {
            let coords = self.translate_coords(window, root).expect("Failed to translate window coordinates");
            (
                coords.y_rel_root,
                coords.child,
            )
        };
        let (width, height, border) = {
            let inner_geometry = self.get_geometry(window).expect("Failed to get inner window geometry");
            (
                inner_geometry.width,
                inner_geometry.height,
                inner_geometry.border,
            )
        };
        
        
        
        
        
        let nested = !(window == child || self.is_top_level(child, root) == Some(true));
        
        if let Some(mut frame_extents) = self.get_frame_extents(window) {
            
            
            
            if !nested {
                frame_extents = FrameExtents::new(0, 0, 0, 0);
            }
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            FrameExtentsHeuristic {
                frame_extents,
                heuristic_path: Supported,
            }
        } else if nested {
            
            
            
            let outer_window = self.climb_hierarchy(window, root).expect("Failed to climb window hierarchy");
            let (outer_y, outer_width, outer_height) = {
                let outer_geometry = self.get_geometry(outer_window).expect("Failed to get outer window geometry");
                (
                    outer_geometry.y_rel_parent,
                    outer_geometry.width,
                    outer_geometry.height,
                )
            };
            
            
            let diff_x = outer_width.saturating_sub(width);
            let diff_y = outer_height.saturating_sub(height);
            let offset_y = inner_y_rel_root.saturating_sub(outer_y) as c_uint;
            let left = diff_x / 2;
            let right = left;
            let top = offset_y;
            let bottom = diff_y.saturating_sub(offset_y);
            let frame_extents = FrameExtents::new(
                left.into(),
                right.into(),
                top.into(),
                bottom.into(),
            );
            FrameExtentsHeuristic {
                frame_extents,
                heuristic_path: UnsupportedNested,
            }
        } else {
            
            
            let frame_extents = FrameExtents::from_border(border.into());
            FrameExtentsHeuristic {
                frame_extents,
                heuristic_path: UnsupportedBordered,
            }
        }
    }
}