use std::{
    io,
    ops::{Deref, DerefMut},
    os::fd::{AsRawFd, OwnedFd, RawFd},
};

use cross::mem::FileMapping;

use crate::{
    error::Error,
    geometry::{Point, Rectangle},
};

// TODO support for different pixel formats
pub trait Surface: DerefMut<Target = [u32]> {
    /// Number of elements between the start of one row and the start of the next one.
    fn stride(&self) -> usize;
    fn width(&self) -> u32;
    fn height(&self) -> u32;
    fn resize(&mut self, width: u32, height: u32) -> Result<(), Error>;

    fn clip_rectangle(&self, input: Rectangle<u32>) -> Option<Rectangle<u32>> {
        input.intersection(&Rectangle {
            x: 0,
            y: 0,
            w: self.width(),
            h: self.height(),
        })
    }

    #[inline]
    fn copy_rect<S: Surface>(
        &mut self,
        source: &S,
        source_rectangle: Rectangle<u32>,
        destination: Point<u32>,
    ) -> Option<Rectangle<u32>> {
        self.blend_rect(source, source_rectangle, destination, blend_copy)
    }

    fn blend_rect<S: Surface, F: Fn(&mut [u32], &[u32])>(
        &mut self,
        foreground: &S,
        source_rectangle: Rectangle<u32>,
        destination: Point<u32>,
        blend_row: F,
    ) -> Option<Rectangle<u32>> {
        let source_stride = foreground.stride();
        let destination_stride = self.stride();

        let destination_rectangle = self.clip_rectangle(Rectangle::from_origin_size(
            destination,
            source_rectangle.w,
            source_rectangle.h,
        ))?;
        let source_rectangle = foreground.clip_rectangle(source_rectangle)?;
        let clip_rectangle = source_rectangle.intersection(&destination_rectangle)?;

        for y in 0..clip_rectangle.h {
            let source_i =
                (y + source_rectangle.y) as usize * source_stride + source_rectangle.x as usize;
            let destination_i = (y + destination_rectangle.y) as usize * destination_stride
                + destination_rectangle.y as usize;
            let source_row = &foreground[source_i..source_i + clip_rectangle.w as usize];
            let destination_row =
                &mut self[destination_i..destination_i + clip_rectangle.w as usize];
            blend_row(destination_row, source_row);
        }

        Some(clip_rectangle)
    }

    fn fill_rect(&mut self, rectangle: Rectangle<u32>, color: u32) {
        let Some(clip) = self.clip_rectangle(rectangle) else {
            return;
        };
        let stride = self.stride();

        for y in 0..clip.h {
            let i = (y + clip.y) as usize * stride + clip.x as usize;
            self[i..i + clip.w as usize].fill(color);
        }
    }

    fn set_pixel(&mut self, x: u32, y: u32, color: u32) {
        let i = y as usize * self.stride() + x as usize;
        self[i] = color;
    }
}

pub struct MappedSurface {
    mapping: FileMapping,
    data: *mut u32,
    width: u32,
    height: u32,
    len: usize,
}

pub struct VecSurface {
    data: Vec<u32>,
    width: u32,
    height: u32,
}

impl MappedSurface {
    pub fn from_fd(fd: OwnedFd, width: u32, height: u32, len: usize) -> Result<Self, io::Error> {
        assert!(len >= (width * height) as usize);

        let mut mapping = FileMapping::map(fd, len * 4, true)?;
        // Won't happen: FileMapping is at least page-aligned
        let data: *mut u32 = mapping.as_mut_ptr().cast();
        log::info!("Map surface: {width}x{height} ({len}) -> {data:p}");

        if !data.is_aligned() {
            unreachable!("FileMapping is not properly aligned to u32");
        }

        Ok(Self {
            mapping,
            data,
            width,
            height,
            len,
        })
    }
}

impl Deref for MappedSurface {
    type Target = [u32];

    fn deref(&self) -> &Self::Target {
        unsafe { core::slice::from_raw_parts(self.data, self.len) }
    }
}

impl DerefMut for MappedSurface {
    fn deref_mut(&mut self) -> &mut Self::Target {
        unsafe { core::slice::from_raw_parts_mut(self.data, self.len) }
    }
}

impl Surface for MappedSurface {
    fn width(&self) -> u32 {
        self.width
    }

    fn height(&self) -> u32 {
        self.height
    }

    fn stride(&self) -> usize {
        self.width as usize
    }

    fn resize(&mut self, width: u32, height: u32) -> Result<(), Error> {
        log::info!(
            "Resize surface {}x{} -> {}x{}",
            self.width,
            self.height,
            width,
            height
        );
        if (width * height) as usize > self.len {
            todo!("Proper MappedSurface resize");
        }
        self.width = width;
        self.height = height;
        Ok(())
    }
}

impl AsRawFd for MappedSurface {
    fn as_raw_fd(&self) -> RawFd {
        self.mapping.as_raw_fd()
    }
}

impl VecSurface {
    pub fn new(width: u32, height: u32) -> Self {
        let len = (width * height) as usize;
        let data = vec![0; len];
        Self {
            data,
            width,
            height,
        }
    }
}

impl Deref for VecSurface {
    type Target = [u32];

    fn deref(&self) -> &Self::Target {
        &self.data[..]
    }
}

impl DerefMut for VecSurface {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.data[..]
    }
}

impl Surface for VecSurface {
    fn stride(&self) -> usize {
        self.width as usize
    }

    fn width(&self) -> u32 {
        self.width
    }

    fn height(&self) -> u32 {
        self.height
    }

    fn resize(&mut self, width: u32, height: u32) -> Result<(), Error> {
        let new_len = (width * height) as usize;
        self.data.resize(new_len, 0);
        Ok(())
    }
}

#[inline]
pub fn blend_copy(dst: &mut [u32], src: &[u32]) {
    dst.copy_from_slice(src);
}
