icmd 0.1.1

A command-line software framework
Documentation
use std::{any::TypeId, fmt::Debug, io::Write, sync::Arc, thread, time::Duration};

use crossterm::{
    cursor, execute,
    style::{self},
};
use parking_lot::{Mutex, RwLock};

use crate::{
    node::{LayoutAttribute, Node, StyleAttribute},
    style::TString,
    utils::NodeRef,
};

use super::{
    interactor::Dispatcher,
    message::{Content, Message, Participant, RawEvent, Sender},
    ICmdWindow,
};

#[derive(Debug, Clone)]
pub struct FrameBuffer {
    pub data: Vec<TString>,
    pub left: i32,
    pub top: i32,
    pub right: i32,
    pub bottom: i32,
}

impl FrameBuffer {
    pub fn new(data: Vec<TString>, left: i32, top: i32, right: i32, bottom: i32) -> Self {
        FrameBuffer {
            data,
            left,
            top,
            right,
            bottom,
        }
    }
    pub fn from_node_measure(data: Vec<TString>, node: &dyn Node) -> Self {
        let left = Node::get_left(node);
        let top = Node::get_top(node);
        let right = Node::get_right(node);
        let bottom = Node::get_bottom(node);
        FrameBuffer {
            data,
            left,
            top,
            right,
            bottom,
        }
    }

    pub fn extend_top(&mut self, top: i32) {
        self.top = top;
    }
}

pub trait Renderable: Debug + Send {
    fn render(&self) -> FrameBuffer;
    fn get_visibility(&self) -> &RwLock<bool>;
    fn get_layout_attributes(&self) -> &RwLock<LayoutAttribute>;
    fn get_style_attributes(&self) -> Option<&Mutex<StyleAttribute>>;
    fn parent(&self) -> Option<&NodeRef>;
    fn children(&self) -> &RwLock<Vec<NodeRef>>;
    fn get_left(&self) -> i32 {
        self.get_layout_attributes().read().l.get()
    }
    fn get_top(&self) -> i32 {
        self.get_layout_attributes().read().t.get()
    }
    fn get_right(&self) -> i32 {
        self.get_layout_attributes().read().r.get()
    }
    fn get_bottom(&self) -> i32 {
        self.get_layout_attributes().read().b.get()
    }
}

pub struct Renderer {
    pub window: Arc<RwLock<ICmdWindow>>,

    win_width: u16,
    win_height: u16,
    timer: Mutex<u64>,
    bg_color: String,
}

impl Debug for Renderer {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Renderer")
            .field("win_width", &self.win_width)
            .field("win_height", &self.win_height)
            .field("bg_color", &self.bg_color)
            .finish()
    }
}

impl Renderer {
    pub fn new(window: Arc<RwLock<ICmdWindow>>) -> Self {
        let window_locked = window.read();
        let (win_width, win_height) = window_locked.get_size();
        let bg_color = window_locked.bg_color.clone();
        drop(window_locked);

        Renderer {
            window,
            win_width,
            win_height,
            timer: 0.into(),
            bg_color,
        }
    }

    pub fn render(&self, node: NodeRef) {
        let mut nodes = self.get_all_subnodes(&node);
        let mut base = nodes.remove(0).render();
        let overlays = nodes.iter().map(|n| n.render()).collect::<Vec<_>>();
        draw_onto(&mut base, overlays);
        self.update(base);
    }

    pub fn launch(&self, node: NodeRef) {
        loop {
            if !Participant::update(self, node.clone()) {
                thread::sleep(Duration::from_millis(1));
                break;
            }
        }
    }

    fn update(&self, raw: FrameBuffer) {
        let stdout = &self.window.read().stdout;
        let mut stdout = stdout.lock();
        stdout.flush().unwrap();

        // Reset the cursor position
        execute!(stdout, cursor::MoveTo(0, 0)).unwrap();

        // std::process::Command::new("clear")
        //     .status()
        //     .expect("Failed to clear the screen");

        // Write the frame to the stdout
        for y in 0..raw.data.len() {
            let line = raw.data[y as usize].make_string();
            execute!(stdout, cursor::MoveTo(0, y as u16)).unwrap();
            execute!(stdout, style::Print(line)).unwrap();
            // write!(stdout, "{}", line).unwrap();
            stdout.flush().unwrap();
        }
    }

    fn get_all_subnodes(&self, node: &NodeRef) -> Vec<NodeRef> {
        let mut result = Vec::new();
        if !node.is_visible() {
            return result;
        }
        result.push(node.clone());
        node.children().read().iter().for_each(|child| {
            result.extend(self.get_all_subnodes(child));
        });
        result
    }
}

impl Sender for Renderer {
    fn get_window(&self) -> Arc<RwLock<ICmdWindow>> {
        self.window.clone()
    }
}

impl Participant for Renderer {
    type Params = NodeRef;
    fn resolve(&self, msg: Message, params: Self::Params) -> bool {
        match msg.content {
            Content::IsReady => {
                self.send(Message {
                    from: self.get_id(),
                    to: msg.from,
                    content: Content::Ready,
                });
                true
            }
            Content::Update => {
                self.render(params);
                self.send(Message {
                    from: self.get_id(),
                    to: TypeId::of::<Dispatcher>(),
                    content: Content::Event(RawEvent::Update(self.timer.lock().clone())),
                });
                *self.timer.lock() += 1;
                true
            }
            Content::Quit => false,
            _ => true,
        }
    }
}

pub fn draw_onto(base: &mut FrameBuffer, overlays: Vec<FrameBuffer>) {
    for overlay in &overlays {
        let (left, top, right, bottom) = (overlay.left, overlay.top, overlay.right, overlay.bottom);
        if left >= right || top >= bottom || overlay.data.is_empty() {
            continue;
        }

        for y in top..bottom {
            for x in left..right {
                if x < base.left
                    || y < base.top
                    || x > base.right
                    || y > base.bottom
                    || x < left
                    || y < top
                    || x > right
                    || y > bottom
                {
                    continue;
                }

                let x_base = (x - base.left) as usize;
                let y_base = (y - base.top) as usize;
                let x_overlay = (x - left) as usize;
                let y_overlay = (y - top) as usize;

                // Safety
                if x_overlay >= overlay.data[0].chars.len()
                    || y_overlay >= overlay.data.len()
                    || x_base >= base.data[0].chars.len()
                    || y_base >= base.data.len()
                {
                    continue;
                }

                base.data[y_base].chars[x_base] = overlay.data[y_overlay].chars[x_overlay].clone();
            }
        }
    }
}