#![no_std]
#![warn(unused)]
#![warn(missing_debug_implementations)]
#![allow(unused_unsafe)]
#![allow(unreachable_code)]
#![allow(dead_code)]
#![cfg_attr(test, allow(unused_imports))]
#![warn(missing_docs)] 
#![deny(intra_doc_link_resolution_failure)]
#[macro_use]
extern crate sunrise_libuser as libuser;
extern crate alloc;
#[macro_use]
extern crate lazy_static;
mod vbe;
mod terminal;
use crate::vbe::{VBEColor, FRAMEBUFFER, Framebuffer};
use core::cmp::{min, max};
use core::sync::atomic::{AtomicU32, Ordering};
use alloc::vec::Vec;
use alloc::boxed::Box;
use alloc::sync::{Arc, Weak};
use crate::libuser::syscalls;
use crate::libuser::futures::{WaitableManager, WorkQueue};
use crate::libuser::ipc::server::{port_handler, new_session_wrapper};
use sunrise_libuser::futures_rs::future::FutureObj;
use crate::libuser::types::*;
use spin::Mutex;
use crate::libuser::error::Error;
use crate::libuser::syscalls::MemoryPermissions;
use sunrise_libutils::align_up;
use libuser::mem::{find_free_address, PAGE_SIZE};
use crate::libuser::vi::{IBuffer as IBufferInterface, IBufferProxy, ViInterface as IViInterface};
use sunrise_libuser::window::Color;
use sunrise_libuser::twili::IPipeProxy;
#[derive(Default, Debug, Clone)]
struct ViInterface;
impl IViInterface for ViInterface {
    
    
    
    
    
    
    
    fn create_buffer(&mut self, manager: WorkQueue<'static>, sharedmem: SharedMemory, top: i32, left: i32, width: u32, height: u32,) -> Result<IBufferProxy, Error> {
        let size = align_up(width * height * 4, PAGE_SIZE as _);
        let addr = find_free_address(size as _, PAGE_SIZE)?;
        let mapped = sharedmem.map(addr, size as _, MemoryPermissions::READABLE)?;
        let buf = IBuffer {
            buffer: Arc::new(Buffer {
                mem: mapped,
                top,
                left,
                width,
                height
            })
        };
        BUFFERS.lock().push(Arc::downgrade(&buf.buffer));
        let (server, client) = syscalls::create_session(false, 0)?;
        let wrapper  = new_session_wrapper(manager.clone(), server, buf, IBuffer::dispatch);
        
        
        manager.spawn(FutureObj::new(Box::new(wrapper)));
        Ok(IBufferProxy::from(client))
    }
    
    
    
    fn get_screen_resolution(&mut self, _manager: WorkQueue<'static>) -> Result<(u32, u32,), Error> {
        let fb = FRAMEBUFFER.lock();
        Ok((fb.width() as _, fb.height() as _))
    }
    fn get_font_height(&mut self, _manager: WorkQueue<'static>) -> Result<u32, Error> {
        Ok(terminal::font_height() as u32)
    }
    fn create_terminal(&mut self, manager: WorkQueue<'static>, sharedmem: SharedMemory, top: i32, left: i32, width: u32, height: u32,) -> Result<IPipeProxy, Error> {
        use terminal::{TerminalPipe, Terminal};
        use sunrise_libuser::twili::IPipeAsync;
        let terminal = TerminalPipe::new(Terminal::new(sharedmem, top, left, width, height)?);
        let (server, client) = syscalls::create_session(false, 0)?;
        let wrapper = new_session_wrapper(manager.clone(), server, terminal, TerminalPipe::dispatch);
        manager.spawn(FutureObj::new(Box::new(wrapper)));
        Ok(IPipeProxy::from(client))
    }
}
static BUFFERS: Mutex<Vec<Weak<Buffer>>> = Mutex::new(Vec::new());
static BACKBUFFER_ARR: Mutex<[VBEColor; 3840 * 2160]> = Mutex::new([VBEColor::rgb(0, 0, 0); 3840 * 2160]);
fn get_intersect((atop, aleft, awidth, aheight): (u32, u32, u32, u32), (btop, bleft, bwidth, bheight): (u32, u32, u32, u32)) -> Option<(u32, u32, u32, u32)> {
    if atop > (btop + bheight) || btop > atop + aheight {
        return None
    };
    if aleft > (bleft + bwidth) || bleft > aleft + awidth {
        return None
    };
    let top = max(atop, btop);
    let left = max(aleft, bleft);
    let height = min(atop + aheight, btop + bheight) - top;
    let width = min(aleft + awidth, bleft + bwidth) - left;
    Some((top, left, width, height))
}
#[allow(clippy::cast_sign_loss)] 
#[allow(clippy::cast_possible_wrap)]
fn draw(buf: &Buffer, framebuffer: &mut Framebuffer<'_>, top: u32, left: u32, width: u32, height: u32) {
    
    #[allow(clippy::cast_ptr_alignment)] 
    fn cast_to_u32(mapped: &MappedSharedMemory) -> &[AtomicU32] {
        unsafe {
            
            
            
            core::slice::from_raw_parts(mapped.as_ptr() as *const AtomicU32, mapped.len() / 4)
        }
    }
    let data = cast_to_u32(&buf.mem);
    let (dtop, dleft, dwidth, dheight) = buf.get_real_bounds(framebuffer.width() as u32, framebuffer.height() as u32);
    
    if let Some(intersect) = get_intersect((dtop, dleft, dwidth, dheight), (top, left, width, height)) {
        let (top, left, width, height) = intersect;
        let mut curtop = top;
        while curtop < top + height {
            let mut curleft = left;
            while curleft < left + width {
                
                let dataidx = ((curtop as i32 - buf.top) as u32 * buf.width + (curleft as i32 - buf.left) as u32) as usize;
                let fbidx = framebuffer.get_px_offset(curleft as usize, curtop as usize) as usize;
                
                
                
                
                let color = data[dataidx].load(Ordering::Relaxed);
                let color: Color = unsafe {
                    
                    core::mem::transmute(color)
                };
                framebuffer.get_fb()[fbidx] = VBEColor::rgb(color.r, color.g, color.b);
                curleft += 1;
            }
            curtop += 1;
        }
    }
    
    
}
#[allow(clippy::cast_sign_loss)] 
#[allow(clippy::cast_possible_wrap)] 
fn get_real_bounds((top, left, width, height): (i32, i32, u32, u32), framebuffer_width: u32, framebuffer_height: u32) -> (u32, u32, u32, u32) {
    let dtop = min(max(top, 0) as u32, framebuffer_height);
    let dleft = min(max(left, 0) as u32, framebuffer_width);
    assert!(width < i32::max_value() as u32);
    assert!(height < i32::max_value() as u32);
    let dwidth = min(max(left + width as i32, 0) as u32, framebuffer_width) - dleft;
    let dheight = min(max(top + height as i32, 0) as u32, framebuffer_height) - dtop;
    (dtop, dleft, dwidth, dheight)
}
#[derive(Debug)]
#[allow(clippy::missing_docs_in_private_items)]
struct Buffer {
    top: i32,
    left: i32,
    width: u32,
    height: u32,
    mem: MappedSharedMemory
}
impl Buffer {
    
    
    
    
    
    
    fn get_real_bounds(&self, framebuffer_width: u32, framebuffer_height: u32) -> (u32, u32, u32, u32) {
        get_real_bounds((self.top, self.left, self.width, self.height), framebuffer_width, framebuffer_height)
    }
    
    fn width(&self) -> u32 {
        self.width
    }
    
    fn height(&self) -> u32 {
        self.height
    }
    
    #[allow(clippy::cast_ptr_alignment)] 
    pub fn get_buffer(&self) -> &[AtomicU32] {
        unsafe {
            
            
            
            core::slice::from_raw_parts(self.mem.as_ptr() as *const AtomicU32, self.mem.len() / 4)
        }
    }
    
    
    
    
    
    pub fn get_px_offset(&self, x: usize, y: usize) -> usize {
        assert!(y < self.height() as usize, "{} {}", y, self.height());
        assert!(x < self.width() as usize);
        y * self.width() as usize + x
    }
    
    fn draw(&self) {
        core::sync::atomic::fence(Ordering::Acquire);
        let (fullscreen_width, fullscreen_height, bpp) = {
            let fb = FRAMEBUFFER.lock();
            (fb.width(), fb.height(), fb.bpp())
        };
        
        
        let mut backbuffer_arr = BACKBUFFER_ARR.lock();
        let mut framebuffer = Framebuffer::new_buffer(&mut *backbuffer_arr, fullscreen_width, fullscreen_height, bpp);
        let (dtop, dleft, dwidth, dheight) = self.get_real_bounds(framebuffer.width() as u32, framebuffer.height() as u32);
        framebuffer.clear_at(dleft as _, dtop as _, dwidth as _, dheight as _);
        BUFFERS.lock().retain(|buffer| {
            if let Some(buffer) = buffer.upgrade() {
                draw(&*buffer, &mut framebuffer, dtop, dleft, dwidth, dheight);
                true
            } else {
                false
            }
        });
        
        let screen_in_backbuffer = &mut framebuffer.get_fb()[0..(fullscreen_width * fullscreen_height)];
        FRAMEBUFFER.lock().get_fb().copy_from_slice(screen_in_backbuffer);
    }
}
#[derive(Debug, Clone)]
struct IBuffer {
    
    buffer: Arc<Buffer>,
}
impl Drop for IBuffer {
    
    
    fn drop(&mut self) {
        let (fullscreen_width, fullscreen_height, bpp) = {
            let fb = FRAMEBUFFER.lock();
            (fb.width(), fb.height(), fb.bpp())
        };
        
        
        let mut backbuffer_arr = BACKBUFFER_ARR.lock();
        let mut framebuffer = Framebuffer::new_buffer(&mut *backbuffer_arr, fullscreen_width, fullscreen_height, bpp);
        let (dtop, dleft, dwidth, dheight) = self.buffer.get_real_bounds(framebuffer.width() as u32, framebuffer.height() as u32);
        framebuffer.clear_at(dleft as _, dtop as _, dwidth as _, dheight as _);
        BUFFERS.lock().retain(|buffer| {
            if let Some(buffer) = buffer.upgrade() {
                if Arc::ptr_eq(&self.buffer, &buffer) {
                    false
                } else {
                    draw(&*buffer, &mut framebuffer, dtop, dleft, dwidth, dheight);
                    true
                }
            } else {
                false
            }
        });
        
        let screen_in_backbuffer = &mut framebuffer.get_fb()[0..(fullscreen_width * fullscreen_height)];
        FRAMEBUFFER.lock().get_fb().copy_from_slice(screen_in_backbuffer);
    }
}
impl IBufferInterface for IBuffer {
    
    #[inline(never)]
    fn draw(&mut self, _manager: WorkQueue<'static>) -> Result<(), Error> {
        self.buffer.draw();
        Ok(())
    }
}
fn main() {
    let mut man = WaitableManager::new();
    let handler = port_handler(man.work_queue(), "vi:", ViInterface::dispatch).unwrap();
    man.work_queue().spawn(FutureObj::new(Box::new(handler)));
    man.run();
}
kip_header!(HEADER = sunrise_libuser::caps::KipHeader {
    magic: *b"KIP1",
    name: *b"vi\0\0\0\0\0\0\0\0\0\0",
    title_id: 0x020000000000002D,
    process_category: sunrise_libuser::caps::ProcessCategory::KernelBuiltin,
    main_thread_priority: 0,
    default_cpu_core: 0,
    flags: 0,
    reserved: 0,
    stack_page_count: 16,
});
capabilities!(CAPABILITIES = Capabilities {
    svcs: [
        sunrise_libuser::syscalls::nr::SleepThread,
        sunrise_libuser::syscalls::nr::ExitProcess,
        sunrise_libuser::syscalls::nr::CloseHandle,
        sunrise_libuser::syscalls::nr::WaitSynchronization,
        sunrise_libuser::syscalls::nr::OutputDebugString,
        sunrise_libuser::syscalls::nr::SetThreadArea,
        sunrise_libuser::syscalls::nr::ReplyAndReceiveWithUserBuffer,
        sunrise_libuser::syscalls::nr::AcceptSession,
        sunrise_libuser::syscalls::nr::CreateSession,
        sunrise_libuser::syscalls::nr::ClearEvent,
        sunrise_libuser::syscalls::nr::ConnectToNamedPort,
        sunrise_libuser::syscalls::nr::SendSyncRequestWithUserBuffer,
        sunrise_libuser::syscalls::nr::SetHeapSize,
        sunrise_libuser::syscalls::nr::QueryMemory,
        sunrise_libuser::syscalls::nr::MapSharedMemory,
        sunrise_libuser::syscalls::nr::UnmapSharedMemory,
        sunrise_libuser::syscalls::nr::MapFramebuffer,
    ],
});
#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn check_get_real_bounds_normal() {
        assert_eq!(get_real_bounds((0, 0, 1280, 800), 1280, 800), (0, 0, 1280, 800));
        assert_eq!(get_real_bounds((500, 700, 20, 150), 1280, 800), (500, 700, 20, 150));
    }
    
    #[test]
    fn check_get_real_bounds_width() {
        assert_eq!(get_real_bounds((0, 0, 1500, 800), 1280, 800), (0, 0, 1280, 800));
    }
    
    #[test]
    fn check_get_real_bounds_height() {
        assert_eq!(get_real_bounds((0, 0, 1280, 1000), 1280, 800), (0, 0, 1280, 800));
    }
    
    
    #[test]
    fn check_get_real_bounds_top() {
        assert_eq!(get_real_bounds((-15, 0, 1280, 50), 1280, 800), (0, 0, 1280, 35));
        assert_eq!(get_real_bounds((-15, 0, 1280, 800), 1280, 800), (0, 0, 1280, 785));
    }
    
    
    #[test]
    fn check_get_real_bounds_left() {
        assert_eq!(get_real_bounds((0, -15, 50, 800), 1280, 800), (0, 0, 35, 800));
        assert_eq!(get_real_bounds((0, -15, 1280, 800), 1280, 800), (0, 0, 1265, 800));
    }
    
    
    #[test]
    fn check_get_real_bounds_leftwidth() {
        assert_eq!(get_real_bounds((0, -15, 1580, 800), 1280, 800), (0, 0, 1280, 800));
    }
    
    
    #[test]
    fn check_get_real_bounds_topheight() {
        assert_eq!(get_real_bounds((-15, 0, 1280, 1000), 1280, 800), (0, 0, 1280, 800));
    }
    
    #[test]
    fn check_get_real_bounds_pedantic() {
        assert_eq!(get_real_bounds((-5000, 0, 1280, 1000), 1280, 800), (0, 0, 1280, 0));
    }
}