//! ⚠ WARNING, USING THIS CRATE IS NOT RECOMMENDED YET ⚠
//! 
//! The fowlhouse_bay crate provides an easy to use tui client.
//! You can create windows and attach messages to these windows using channels.
//! So, different parts of your software, can send messages to different text based windows on the same terminal.
//! 
//! # Example
//! 
//! The following ASCII art example should illustrate how a possible tui could look like.
//! The windows 'Logfile' and 'Ping' where created using this library and the software
//! using it has sent messages to each of the windows.
//! The user executing the software types the string 'command x y' which will be sent to the software
//! using this library through channels.
//! 
//! ```text
//! ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
//! ┃ PowerShell                                           _ □ x ┃
//! ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
//! ┃╔Logfile:═════════════════════╗╔Ping:══════════════════════╗┃
//! ┃║loading resource file        ║║ping                       ║┃
//! ┃║successfully created item xy ║║pong                       ║┃
//! ┃╚═════════════════════════════╝║ping                       ║┃
//! ┃                               ╚═══════════════════════════╝┃
//! ┃STDIN:~$ command x y                                        ┃
//! ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
//! ```

/// This module specifies the `Window` struct, which represents a single window on the tui generated by `Term`.
pub mod window;
/// This module specifies the error types returned by the `Term` struct methods.
pub mod error;
/// This internal module specifies internal events mainly passed from the 'listener' thread to the 'render' thread
/// within a `Term` instance.
mod event;

use std::io::{stdout, Write};
use std::thread;
use std::thread::JoinHandle;
use std::time::Duration;
use std::sync::mpsc;
use std::sync::mpsc::{Sender, Receiver};
use std::sync::Arc;
use std::sync::Mutex;
use std::iter::FromIterator;

use crossterm::{
    execute,
    cursor::{MoveTo, Hide, Show},
    terminal::{Clear, ClearType, size, enable_raw_mode, disable_raw_mode},
    style::{Print, Colorize, PrintStyledContent, style, Color, Attribute},
    event::{read, Event, KeyEvent, poll, EnableMouseCapture, DisableMouseCapture, KeyCode, KeyModifiers},
    QueueableCommand, ExecutableCommand
};

use self::window::Window;
use self::window::Config;
use self::window::Style;
use self::error::TermError;
use self::event::Event as InternalEvent;


const MIN_TERMINAL_WIDTH:  u16 = 6;
const MIN_TERMINAL_HEIGHT: u16 = 4;
    
/// Term is a tui client.
/// 
/// # Supported input (stdin) characters
/// 
/// `a-z`, `A-Z`, `0-9`, ` ` (blank), `()`, `.,:;?!"'`
/// 
/// # Keyboard shortcuts
/// 
/// `Strg + r` - refresh the whole tui
/// 
/// `Strg + c` - shutdown term instance
/// 
/// `ESC`- shutdown term instance
/// 
/// # Example
/// 
/// The following example tui can be created using the instructions provided below.
/// 
///  ```text
/// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
/// ┃ PowerShell                                           _ □ x ┃
/// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
/// ┃╔Logfile:═════════════════════╗╔Ping:══════════════════════╗┃
/// ┃║loading resource file        ║║ping                       ║┃
/// ┃║successfully created item xy ║║pong                       ║┃
/// ┃╚═════════════════════════════╝║ping                       ║┃
/// ┃                               ╚═══════════════════════════╝┃
/// ┃STDIN:~$ command x y                                        ┃
/// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
/// ```
/// 
/// ``` no_run
/// use fowlhouse_bay::Term;
/// use fowlhouse_bay::window::Config;
/// use fowlhouse_bay::window::Source;
/// use fowlhouse_bay::window::Style;
/// 
/// type ApplicationResult = Result<(), Box<dyn std::error::Error + Send + Sync + 'static>>;
///
/// fn main() -> ApplicationResult {
/// 
///     let (mut term, standard_input_channel, _, _) = Term::new();
/// 
///     let conf_win_one = Config {
///         window_name: String::from("Logfile"),
///         window_width: 50,
///         window_height: 10,
///         position_x: 0,
///         position_y: 0,
///         source: Source::StandardInputChannel,
///         style: Style::Light,
///     };
/// 
///     term.create_window(conf_win_one)?;
/// 
///     standard_input_channel.send(String::from("Hello, Term!"))?;
/// 
///     term.join();
/// 
///     Ok(())
/// 
/// }
/// ```
///
pub struct Term {
    force_refresh: Arc<Mutex<bool>>,
    stdin_buffer: Arc<Mutex<Vec<char>>>,
    windows: Arc<Mutex<Vec<Window>>>,
    event_thread: Option<JoinHandle<()>>,
    render_thread: Option<JoinHandle<()>>,
    sigterm_send: Arc<Mutex<Sender<bool>>>,
    stinch_recv: Arc<Mutex<Receiver<String>>>,
    command_send: Arc<Mutex<Sender<String>>>,
    internal_listener_send: Option<Sender<bool>>,
    internal_listener_recv: Option<Receiver<bool>>,
    internal_render_send: Option<Sender<InternalEvent>>,
    internal_render_recv: Option<Receiver<InternalEvent>>,
}

impl Term {
    /// This method is used to create a new Term instance, please note that a 4-tuple (quadruple) is returned.
    /// 
    /// # Return values
    /// 
    /// * `Term` - the new term instance
    /// * `std::sync::mpsc::Sender<String>` - the sender end of the 'StandardInputChannel', this channel is used to send messages to windows with
    /// `fowlhouse_bay::window::Source::StandardInputChannel` as 'source' enum variant
    /// * `std::sync::mpsc::Receiver<String>` - the receiver end the command channel, commands are strings typed by the user over the stdin, this channel
    /// * `std::sync::mpsc::Receiver<bool>` - the receiver end of the channel sending a 'SIGTERM' (true) if the term instance is shutting down
    /// could be used to forward commands to your business logic
    /// 
    pub fn new() -> (Self, Sender<String>, Receiver<String>, Receiver<bool>) {
        
        let (stinch_send, stinch_recv): (Sender<String>, Receiver<String>) = mpsc::channel();
        let (sigterm_send, sigterm_recv): (Sender<bool>, Receiver<bool>) = mpsc::channel();
        let (command_send, command_recv): (Sender<String>, Receiver<String>) = mpsc::channel();

        let (internal_listener_send, internal_listener_recv): (Sender<bool>, Receiver<bool>) = mpsc::channel();
        let (internal_render_send, internal_render_recv): (Sender<InternalEvent>, Receiver<InternalEvent>) = mpsc::channel();
        let mut term = Term {
            force_refresh: Arc::new(Mutex::new(true)),
            stdin_buffer: Arc::new(Mutex::new(Vec::with_capacity(1024))),
            windows: Arc::new(Mutex::new(Vec::with_capacity(16))),
            event_thread: None,
            render_thread: None,
            sigterm_send: Arc::new(Mutex::new(sigterm_send)),
            stinch_recv: Arc::new(Mutex::new(stinch_recv)),
            command_send: Arc::new(Mutex::new(command_send)),
            internal_listener_send: Some(internal_listener_send),
            internal_listener_recv: Some(internal_listener_recv),
            internal_render_send: Some(internal_render_send),
            internal_render_recv: Some(internal_render_recv),
        };
        let _r = term.enable_raw_mode();
        let _l = term.listen();
        let _r = term.render();
        (term, stinch_send, command_recv, sigterm_recv)
    }

    /// This method is used to create a new window on the tui.
    /// Typically a window has properties like
    /// 
    /// * name
    /// * width, height
    /// * position
    /// * channel for message passing
    /// 
    /// These properties are passed to this method through the `fowlhouse_bay::window::WindowConfig` struct.
    /// For further instructions on how to configure a window, look at the WindowConfig struct.
    pub fn create_window(&mut self, config: Config) -> Result<(), TermError> {
        
        let window = match Window::new(config) {
            Ok(w) => w,
            Err(_) => return Err(TermError),
        };

        self.windows.lock().unwrap().push(window);

        Ok(())

    }

    /// This method is used to delete a window on the tui.
    /// Currently the caller will get no response whether or not the deletion was successful.
    pub fn delete_window(&mut self, window_name: String) {

        let mut force_refresh = self.force_refresh.lock().unwrap();
        
        let mut windows = self.windows.lock().unwrap();

        let position: i32 = match windows.iter_mut().position(|x| x.window_name().eq(&window_name)) {
            Some(p) => {
                p as i32
            },
            None => -1
        };

        if position != -1 {
            windows.remove(position as usize);
            *force_refresh = true;
        }

    }

    /// During the initialization of Term there are two threads created.
    /// One thread is listening for keyboard events and the other renders the tui.
    /// To join these threads call this method, it will block until both threads terminated.
    /// It also disables the terminal raw mode.
    pub fn join(&mut self) {
        
        self.disable_raw_mode(); // TODO: move

        match self.event_thread.take() {
            Some(t) => {
                match t.join() {
                    Ok(_) => {},
                    Err(_) => {}, // TODO: return error
                }
            },
            _ => {}
        }
        match self.render_thread.take() {
            Some(t) => {
                match t.join() {
                    Ok(_) => {},
                    Err(_) => {}, // TODO: return error
                }
            },
            _ => {}
        }
    }

    /// 
    fn listen(&mut self) -> Result<(), TermError> {
        
        let packed_send = self.internal_render_send.take();

        let internal_render_send = match packed_send {
            None => return Err(TermError),
            Some(s) => s,
        };

        let packed_recv = self.internal_listener_recv.take();

        let internal_listener_recv = match packed_recv {
            None => return Err(TermError),
            Some(r) => r,
        };

        let join_handle = thread::spawn(move || {
            let mut running = true;
            while running {

                for m in internal_listener_recv.try_iter() {
                    if !m {
                        running = false;
                        break;
                    }
                }

                if poll(Duration::from_millis(150)).unwrap() { // unwrap because result is always true/false
                    let _poll_result = match read() {
                        Ok(event) => {
                            match event {
                                // send resize event to renderer
                                Event::Resize(cols, rows) => {
                                    match internal_render_send.send(InternalEvent::Resize(cols, rows)) {
                                        _ => {},
                                    };
                                },
                                Event::Key(key_event) => {
                                    match key_event.code {
                                        // send all chars to the renderer, illegal chars need to be filtered there
                                        KeyCode::Char(_) => {
                                            match internal_render_send.send(InternalEvent::Key(KeyEvent::new(key_event.code, key_event.modifiers))) {
                                                _ => {},
                                            };
                                        },
                                        // all other key events are handed over
                                        _ => {
                                            match internal_render_send.send(InternalEvent::Key(KeyEvent::new(key_event.code, key_event.modifiers))) {
                                                _ => {},
                                            };
                                        },
                                    }
                                },
                                // TODO: handle mouse events
                                _ => {}
                            };
                        },
                        _ => continue
                    };                    
                };
            };
        });

        self.event_thread = Some(join_handle);

        Ok(())
    }

    /// 
    /// 
    /// ```text
    /// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
    /// ┃ PowerShell                                           _ □ x ┃
    /// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓←─────╮
    /// ┃(0,0)                                                       ┃      │
    /// ┃             position_x                                     ┃      │
    /// ┃             ↓                                              ┃      │
    /// ┃ position_y →╔WINDOW_TITLE:════════════════╗←─╮             ┃      │
    /// ┃             ║This is a test message.      ║  window_height ┃      │
    /// ┃             ║                             ║  │             ┃      terminal_height (rows)
    /// ┃             ╚═════════════════════════════╝←─╯             ┃      │
    /// ┃             ↑                             ↑                ┃      │
    /// ┃             ╰───────── window_width ──────╯                ┃      │
    /// ┃                                                            ┃      │
    /// ┃STDIN:~$ exit                                               ┃← stdin (1)
    /// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛←─────╯
    /// ↑                                                            ↑ 
    /// ╰────────────── terminal_width (cols) ───────────────────────╯
    /// ```
    fn render(&mut self) -> Result<(), TermError> {

        let packed_recv = self.internal_render_recv.take();

        let internal_render_recv = match packed_recv {
            None => return Err(TermError),
            Some(r) => r,
        };

        let packed_send = self.internal_listener_send.take();

        let internal_listener_send = match packed_send {
            None => return Err(TermError),
            Some(s) => s,
        };

        let sigterm_lock = self.sigterm_send.clone();
        let windows_lock = self.windows.clone();
        let stdin_buffer = self.stdin_buffer.clone();
        let force_refresh_lock = self.force_refresh.clone();
        let standard_input_channel_lock = self.stinch_recv.clone();

        let join_handle = thread::spawn(move || {

            let _clear_result = stdout().execute(Clear(ClearType::All));

            let mut running = true;

            while running {

                let mut force_refresh: bool = false;

                for m in internal_render_recv.try_iter() {
                    match m {
                        InternalEvent::Resize(_, _) => {
                            force_refresh = true;
                        },
                        InternalEvent::Shutdown => {
                            running = false;
                            break;
                        },
                        InternalEvent::Key(key_event) => {
                            match key_event.code {
                                KeyCode::Esc => {
                                    let _r = internal_listener_send.send(false);
                                    running = false;
                                    let _r = sigterm_lock.lock().unwrap().send(true);
                                    break;
                                },
                                KeyCode::Char(c) => {
                                    let shift = key_event.modifiers == KeyModifiers::SHIFT;
                                    let control = key_event.modifiers == KeyModifiers::CONTROL;
                                    let alt = key_event.modifiers == KeyModifiers::ALT;

                                    if c == 'r' && control {
                                        force_refresh = true;
                                    }
                                    else if c == 'c' && control {
                                        let _r = internal_listener_send.send(false);
                                        running = false;
                                        let _r = sigterm_lock.lock().unwrap().send(true);
                                        break;
                                    }
                                    else if c >= 'a' && c <= 'z' 
                                        || c >= 'A' && c <= 'Z' 
                                        || c >= '0' && c <= '9' 
                                        || c == ' '
                                        || c == 'ä' || c == 'ö' || c == 'ü'
                                        || c == 'Ä' || c == 'Ö' || c == 'Ü'
                                        || c == 'ß'
                                        || c == '(' || c == ')' 
                                        || c == '.' || c == ',' || c == ':' || c == ';' || c == '?' || c == '!'
                                        || c == '"' || c == '\'' {
                                        stdin_buffer.lock().unwrap().push(c);
                                    }

                                },
                                KeyCode::Enter => {
                                    let mut stdin_buffer = stdin_buffer.lock().unwrap();
                                    let stdin: String = stdin_buffer.iter().collect();
                                    for w in windows_lock.lock().unwrap().iter_mut() { // TODO: refactor
                                        w.push_message(stdin.clone());
                                    }
                                    stdin_buffer.clear();
                                },
                                KeyCode::Backspace => {
                                    let mut stdin_buffer = stdin_buffer.lock().unwrap();
                                    let stdin_buffer_len = stdin_buffer.len();
                                    if stdin_buffer_len != 0 {
                                        stdin_buffer.remove(stdin_buffer_len - 1);
                                    }
                                }
                                _ => {}
                            }
                        },
                        InternalEvent::Message(message) => {

                        },
                        _ => {}
                    }
                }

                let mut struct_force_refresh = force_refresh_lock.lock().unwrap();

                force_refresh = force_refresh || *struct_force_refresh;

                for m in standard_input_channel_lock.lock().unwrap().try_iter() {
                    for w in windows_lock.lock().unwrap().iter_mut() { // TODO: refactor
                        w.push_message(m.clone());
                    }
                    force_refresh = true;
                }

                if force_refresh {
                    let _r = Term::clear_terminal();
                    *struct_force_refresh = false;
                }

                for w in windows_lock.lock().unwrap().iter_mut() {
                    w.update_messages();
                    match Term::render_window(w, force_refresh) {
                        _ => {} // TODO: handle crossterm error
                    }
                }
                
                let stdin: String = stdin_buffer.lock().unwrap().iter().collect();
                let _r = Term::render_stdin(stdin);

            }
        });

        self.render_thread = Some(join_handle);

        Ok(())

    }

    fn clear_terminal() -> Result<(), crossterm::ErrorKind> {
        stdout().execute(Clear(ClearType::All))?;
        Ok(())
    }

    fn render_stdin(stdin: String) -> Result<(), crossterm::ErrorKind> {
        
        let (terminal_width, terminal_height) = size()?;

        // if terminal_width < MIN_TERMINAL_WIDTH  || terminal_height < MIN_TERMINAL_HEIGHT
        // then: clear terminal
        //       return
        if terminal_width < MIN_TERMINAL_WIDTH || terminal_height < MIN_TERMINAL_HEIGHT {
            Term::clear_terminal();
            return Ok(())
        }

        let name = " RAKINO ";
        let shell = ":~$ ";

        let available_space = 100000 - terminal_width as usize - name.chars().count() - shell.chars().count() - 10;
        let stdin_length = stdin.chars().count();

        let formated: String;

        if stdin_length > available_space || false {
            // TODO: utf8 slicing panic with äöü
            formated = format!("[..]{}", stdin[(stdin_length - available_space)..].to_string());
        }
        else {
            formated = stdin;
        }

        let line_length = name.chars().count() as u16 + shell.chars().count() as u16 + formated.chars().count() as u16;
        
        stdout()
            .queue(MoveTo(0, terminal_height - 1))?
            .queue(Clear(ClearType::CurrentLine))?
            .queue(PrintStyledContent(name.white().on_red()))?
            .queue(Print(shell))?
            .queue(Print(formated))?
            .queue(MoveTo(line_length, terminal_height - 1))?
            .flush()?;

        Ok(())
    }

    fn render_window(window: &mut Window, force: bool) -> Result<(), crossterm::ErrorKind> {

        if window.changes() || force {

            let (terminal_width, terminal_height) = size()?;
            let stdin = 1;

            // if terminal_width < MIN_TERMINAL_WIDTH  || terminal_height < MIN_TERMINAL_HEIGHT
            // then: clear terminal
            //       return
            if terminal_width < MIN_TERMINAL_WIDTH || terminal_height < MIN_TERMINAL_HEIGHT {
                Term::clear_terminal();
                return Ok(())
            }

            // convert to i32
            let terminal_width = terminal_width as i32;
            let terminal_height = terminal_height as i32;

            // window data
            let window_name = window.window_name();
            let mut window_width = window.window_width() as i32;
            let mut window_height = window.window_height() as i32;
            let mut position_x = window.position_x() as i32;
            let mut position_y = window.position_y() as i32;
            let mut window_messages = window.messages();

            let window_style = match window.style() {
                Style::Light => {
                    ["╭", "╮", "╰", "╯", "─", "│"]
                },
                Style::Heavy => {
                    ["┏", "┓", "┗", "┛", "━", "┃"]
                },
                Style::DoubleLine => {
                    ["╔", "╗", "╚", "╝", "═", "║"]
                }
            };

            // min_window_width  = 17
            // min_window_height = 3
            // max_window_width  = terminal_width  - position_x
            // max_window_height = terminal_height - position_y - stdin

            let min_window_width = 17;
            let min_window_height = 3;
            let max_window_width = terminal_width - position_x;
            let max_window_height = terminal_height - position_y - stdin;

            if window_width < min_window_width || window_height < min_window_height {
                window_width = 1;
                window_height = 1;
            }
            
            // if: position_x > terminal_width - 1 || position_y > terminal_height - stdin - 1
            // then: position_x = terminal_width - 1
            //       position_y = terminal_height - stdin - 1
            //       window_width  = 1
            //       window_height = 1
            
            if position_x > terminal_width - 1 || position_y > terminal_height - stdin - 1 {
                position_x = terminal_width - 1;
                position_y = terminal_height - 1;
                window_width = 1;
                window_height = 1;
            }

            // if: window_width > max_window_width && max_window_width >= min_window_width
            // then: window_width = max_window_width
            
            if window_width > max_window_width && max_window_width >= min_window_width {
                window_width = max_window_width;
            }

            // if: window_height > max_window_height && max_window_height >= min_window_height
            // then: window_height = max_window_height
            
            if window_height > max_window_height && max_window_height >= min_window_height {
                window_height = max_window_height;
            }

            // if: max_window_width < min_window_width || max_window_height < min_window_height
            // then: window_width  = 1
            //       window_height = 1
            if max_window_width < min_window_width || max_window_height < min_window_height {
                window_width = 1;
                window_height = 1;
            }

            // convert to u16
            let window_width = window_width as u16;
            let window_height = window_height as u16;
            let position_x = position_x as u16;
            let position_y = position_y as u16;

            // if: window_width == 1 && window_height == 1
            // then: ☐
            //       return
            if window_width == 1 && window_height == 1 {
                stdout()
                .queue(Hide)?
                .queue(MoveTo(position_x, position_y))?
                .queue(Print("☐"))?
                .queue(MoveTo(0,0))?
                .queue(Show)?
                .flush()?;
                return Ok(())
            }

            // stdout
            let mut cwindow = stdout();

            let logfile_cols: usize;
            let logfile_rows: usize;
            logfile_cols = (window_width - 2) as usize;
            logfile_rows = (window_height - 2) as usize;

            let window_messages = Term::row_row_row_your_boat(logfile_rows, logfile_cols, window_messages.to_vec());

            let window_title_string = format!("{}:", window_name.clone());
            let window_title_length = window_title_string.len() as u16;

            let window_title = style(window_title_string);

            // set window title
            cwindow
                .queue(Hide)?
                .queue(MoveTo(position_x + 1, position_y))?
                .queue(PrintStyledContent(window_title))?
                .queue(MoveTo(position_x + 1 + window_title_length, position_y))?;

            // render title bar
            for i in 0..window_width {
                if i == 0 {
                    cwindow
                    .queue(MoveTo(position_x, position_y))?
                    .queue(Print(window_style[0]))?;
                }
                else if i > window_title_length && i < window_width - 1 {
                    cwindow
                    .queue(MoveTo(position_x + i, position_y))?
                    .queue(Print(window_style[4]))?;
                }
                else if i == window_width - 1 {
                    cwindow
                    .queue(MoveTo(position_x + i, position_y))?
                    .queue(Print(window_style[1]))?;
                }
            }

            // render messages
            for (i, value) in window_messages.iter().enumerate() {
                let line = format!("{}{}{}", window_style[5], value, window_style[5]);
                cwindow
                    .queue(MoveTo(position_x, position_y + i as u16 + 1))?
                    .queue(Print(line))?;
            }

            // render status bar
            for i in 0..window_width {
                if i == 0 {
                    cwindow
                    .queue(MoveTo(position_x, position_y + window_height - 1))?
                    .queue(Print(window_style[2]))?;
                }
                else if i == window_width - 1 {
                    cwindow
                    .queue(MoveTo(position_x + i, position_y + window_height - 1))?
                    .queue(Print(window_style[3]))?;
                }
                else {
                    cwindow
                    .queue(MoveTo(position_x + i, position_y + window_height - 1))?
                    .queue(Print(window_style[4]))?;
                }
            }
            
            cwindow.queue(MoveTo(0,0))?;
            cwindow.queue(Show)?;
            cwindow.flush()?;

            window.updated();
        }
        Ok(())
    }

    fn enable_raw_mode(&mut self) {
        let _r = enable_raw_mode();
        let _r = stdout().execute(EnableMouseCapture);
    }

    fn disable_raw_mode(&mut self) {
        let _r = disable_raw_mode();
        let _r = stdout().execute(DisableMouseCapture);
    }

    fn message_length(message: &String) -> usize {
        message.chars().count()
    }

    fn char_utf8_byte_length(c: char) -> usize {
        c.len_utf8()
    }

    /// If a message is longer than the available cols of the window
    /// then the message is split into multiple chunks with available cols length
    /// if the last chunk is shorter then available cols length it is filled with blanks
    /// 
    /// # Returns
    /// vector with the message chunks
    fn split_or_bloat_message(available_cols: usize, message: String) -> Vec<String> {

        let mut result = Vec::<String>::new();
        let message_length = Term::message_length(&message);

        // if the window has more cols available than the message has chars
        // then fill the message with blanks until the available cols == message_length
        if available_cols > message_length {
            // the number of blanks needed to bloat the message
            let number_of_blanks = available_cols - message_length;
            // the blanks
            let blanks = std::iter::repeat(" ").take(number_of_blanks).collect::<String>();
            // merge the message with the blanks
            let merged_string = format!("{}{}", message, blanks);
            // push the merged string to the vec
            result.push(merged_string);
        }
        else {

            #[derive(PartialEq)]
            enum StateMachine { SPLIT, DONE };
            
            let mut state = StateMachine::SPLIT;
            
            let mut message = message;
            
            while state != StateMachine::DONE {
                let mut chunk = String::new();
                let mut rest = String::new();
                for c in message.chars() {
                    if Term::message_length(&chunk) < available_cols {
                        chunk.push(c);
                    }
                    else {
                        rest.push(c);
                    }
                }

                result.push(chunk);
                message = rest;
                let message_length = Term::message_length(&message);

                if message_length == 0 {
                    //
                    state = StateMachine::DONE;
                }
                else if  message_length <= available_cols {
                    // the number of blanks needed to bloat the message
                    let number_of_blanks = available_cols - message_length;
                    // the blanks
                    let blanks = std::iter::repeat(" ").take(number_of_blanks).collect::<String>();
                    // merge the message with the blanks
                    let merged_string = format!("{}{}", message, blanks);
                    // push the merged string to the vec
                    result.push(merged_string);
                    //
                    state = StateMachine::DONE;
                }
            }
            
        }

        result

    }


    fn row_row_row_your_boat(available_rows: usize, available_cols: usize, messages: Vec<String>) -> Vec<String> {
        let mut result = Vec::<String>::new();
        let mut message_count = 0;

        'outer: for message in messages.iter().rev().cloned() {
            let chunks = Term::split_or_bloat_message(available_cols, message);
            for chunk in chunks.iter().rev().cloned() {
                if message_count < available_rows {
                    result.insert(0, chunk);
                    message_count += 1;
                }
                else {
                    break 'outer;
                }
            }
        }

        while result.len() < available_rows {
            let fill_message = Term::split_or_bloat_message(available_cols, String::from("")).get(0).unwrap().clone();
            result.push(fill_message);
        }

        result
    }

}

#[cfg(test)]
mod test {
    use super::Term;
    #[test]
    fn ascii_message_length() {
        let m1 = String::from("Hello, world!");
        assert_eq!(Term::message_length(&m1), 13);
        let m2 = String::from("0123456789 abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ");
        assert_eq!(Term::message_length(&m2), 64);
        let m3 = String::from("(){}[]|&.,:;");
        assert_eq!(Term::message_length(&m3), 12);
    }
    #[test]
    fn umlaut_message_length() {
        let m1 = String::from("äöüß");
        assert_eq!(Term::message_length(&m1), 4);
    }
    #[test]
    fn utf8_char_length() {
        let c1 = String::from("a");
        assert_eq!(Term::char_utf8_byte_length(c1.chars().next().unwrap()), 1);
        let c2 = String::from("ä");
        assert_eq!(Term::char_utf8_byte_length(c2.chars().next().unwrap()), 2);
        let c3 = String::from("⚓");
        assert_eq!(Term::char_utf8_byte_length(c3.chars().next().unwrap()), 3);
        let c4 = String::from("𐌀");
        assert_eq!(Term::char_utf8_byte_length(c4.chars().next().unwrap()), 4);
    }
    #[test]
    fn split_or_bloat() {
        let available_cols = 20;

        let m1 = String::from("Short message.");
        let c1 = Term::split_or_bloat_message(available_cols, m1);
        assert_eq!(c1.len(), 1);
        assert_eq!(Term::message_length(&c1.get(0).unwrap().to_string()), available_cols);

        let m2 = String::from("äääääöööööüüüüüßßßßßäääääöööööüüüüüßßßßßäääää"); // 45
        let c2 = Term::split_or_bloat_message(available_cols, m2);
        assert_eq!(c2.len(), 3);
        assert_eq!(Term::message_length(&c2.get(0).unwrap().to_string()), available_cols);
        assert_eq!(Term::message_length(&c2.get(1).unwrap().to_string()), available_cols);
        assert_eq!(Term::message_length(&c2.get(2).unwrap().to_string()), available_cols);

        let m3 = String::from("12345678901234567890");
        let c3 = Term::split_or_bloat_message(10, m3);
        assert_eq!(c3.len(), 2);
    }
    #[test]
    fn reverse_comprehension_test() {
        let mut numbers = Vec::new();
        numbers.push(42);
        numbers.push(1337);
        numbers.push(8080);

        numbers.reverse();

        assert_eq!(numbers.get(0).unwrap(), &8080);
        assert_eq!(numbers.get(1).unwrap(), &1337);
        assert_eq!(numbers.get(2).unwrap(), &42);

        let mut numbers2 = Vec::new();
        numbers2.push(42);
        numbers2.push(1337);
        numbers2.push(8080);

        let mut rev = numbers2.iter().rev();

        assert_eq!(rev.next().unwrap(), &8080);
        assert_eq!(rev.next().unwrap(), &1337);
        assert_eq!(rev.next().unwrap(), &42);

        assert_eq!(numbers2.get(0).unwrap(), &42);
        assert_eq!(numbers2.get(1).unwrap(), &1337);
        assert_eq!(numbers2.get(2).unwrap(), &8080);
    }
    #[test]
    fn prepare_window_messages_01() {
        let available_rows = 4;
        let available_cols = 10;
        let mut messages = Vec::<String>::new();
        messages.push(String::from("äöüß⚓"));
        messages.push(String::from("abcdefghij"));
        messages.push(String::from("12345678901234567890"));

        let result = Term::row_row_row_your_boat(available_rows, available_cols, messages);

        assert_eq!(result.get(0).unwrap().clone(), String::from("äöüß⚓     "));
        assert_eq!(result.get(1).unwrap().clone(), String::from("abcdefghij"));
        assert_eq!(result.get(2).unwrap().clone(), String::from("1234567890"));
        assert_eq!(result.get(3).unwrap().clone(), String::from("1234567890"));
    }
    #[test]
    fn prepare_window_messages_02() {
        let available_rows = 4;
        let available_cols = 10;
        let messages = Vec::<String>::new();

        let result = Term::row_row_row_your_boat(available_rows, available_cols, messages);

        assert_eq!(result.len(), 4);
        assert_eq!(result.get(0).unwrap().clone(), String::from("          "));
        assert_eq!(result.get(1).unwrap().clone(), String::from("          "));
        assert_eq!(result.get(2).unwrap().clone(), String::from("          "));
        assert_eq!(result.get(3).unwrap().clone(), String::from("          "));
    }
}