A KVM switch emulator using UDP/IP
use crate::io::*;
use crate::util;

use log::warn;
use serde::{Serialize, Deserialize};

use std::net::SocketAddr;

pub type Index = u8;

#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Cluster {
    local_screen: Index,
    screens: Vec<Screen>,
    focus: Focus,
    selections: Vec<Index>,
}

#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
pub struct Focus {
    index: Index,
    pos: Dimensions,
}

impl Cluster {
    pub fn new(width: i32, height: i32, x: i32, y: i32) -> Self {
        use std::fs::File;

        // This is probably weird place to load a file from
        let app_dir = util::user_app_dir("elemeld").unwrap();
        let screens = match File::open(app_dir.join("screens.json")) {
            Ok(file) => serde_json::from_reader(file).map_err(|err| {
                warn!("Failed to parse screens.json: {}", err);
            }).ok(),
            Err(err) => {
                warn!("Failed to open screens.json {}", err);
                None
            },
        };
        
        Self {
            local_screen: 0,
            screens: match screens {
                Some(screens) => screens,
                None => vec![Screen::new(width, height)],
            },
            focus: Focus {
                index: 0,
                pos: Dimensions { x, y },
            },
            selections: vec![0, 0],
        }
    }
    
    pub fn process_host_output_event(&mut self, event: HostOutputEvent) -> Vec<InputEvent> {
        match event {
            HostOutputEvent::Motion(event) => {
                if event.dx != 0 || event.dy != 0 {
                    let focus = Focus {
                        index: self.focus.index,
                        pos: Dimensions {
                            x: self.focus.pos.x + event.dx,
                            y: self.focus.pos.y + event.dy,
                        }
                    };

                    let mut events = vec![];
                    if let Some(grab) = self.refocus(focus) {
                        events.push(InputEvent::Host(HostInputEvent::Grab(grab)));
                    }

                    events.push(InputEvent::Net(NetEvent::Focus(focus)));
                    events
                } else {
                    vec![]
                }
            },
            HostOutputEvent::Selection(event) => {
                println!("{:?}", event);
                vec![]
            },
            event => {
                if !self.locally_focused() {
                    match event {
                        HostOutputEvent::Button(event) => vec![InputEvent::Net(NetEvent::Button(event))],
                        HostOutputEvent::Key(event) => vec![InputEvent::Net(NetEvent::Key(event))],
                        _ => vec![],
                    }
                } else {
                    vec![]
                }
            },
        }
    }

    pub fn process_net_output_event(&mut self, event: NetEvent) -> Vec<InputEvent> {
        if self.locally_focused() {
            match event {
                NetEvent::Button(event) => vec![InputEvent::Host(HostInputEvent::Button(event))],
                NetEvent::Key(event) => vec![InputEvent::Host(HostInputEvent::Key(event))],
                _ => vec![],
            }
        } else {
            vec![]
        }
    }

    pub fn focused_screen(&self) -> &Screen {
        &self.screens[self.focus.index as usize]
    }
    
    fn locally_focused(&self) -> bool {
        self.focus.index == self.local_screen
    }

    fn reset_local_screen(&mut self) {
        for ip in util::get_host_ips().unwrap() {
            for (i, screen) in self.screens.iter().enumerate() {
                for addr in &screen.addrs {
                    if addr.ip() == ip {
                        self.local_screen = i as Index;
                        return;
                    }
                }
            }
        }

        panic!("Local IP was not found in cluster");
    }

    pub fn refocus(&mut self, focus: Focus) -> Option<GrabEvent> {
        let was_focused = self.locally_focused();
        self.focus = self.normalize_focus(focus);
        let focused = self.locally_focused();
        if focused != was_focused {
            Some(GrabEvent { grab: focused })
        } else {
            None
        }
    }

    /// Walk through the screens untill the x and y are contained within a screen
    /// TODO: Use macros to avoid the insane amount of repetition
    fn normalize_focus(&self, focus: Focus) -> Focus {
        self.normalize_y(self.normalize_x(focus))
    }

    fn normalize_x(&self, focus: Focus) -> Focus {
        let screen = &self.screens[focus.index as usize];
        if focus.pos.x <= 0 {
            match screen.edges.left {
                Some(index) => {
                    let new_screen = &self.screens[index as usize];
                    return self.normalize_x(Focus {
                        index,
                        pos: Dimensions {
                            x: focus.pos.x + new_screen.size.x - 2,
                            y: focus.pos.y * new_screen.size.y / screen.size.y,
                        }
                    })
                },
                None => if focus.pos.x < 0 {
                    return Focus {
                        index: focus.index,
                        pos: Dimensions {
                            x: 0,
                            y: focus.pos.y,
                        }
                    }
                },
            }
        } else if focus.pos.x >= screen.size.x - 1 {
            match screen.edges.right {
                Some(index) => {
                    let new_screen = &self.screens[index as usize];
                    return self.normalize_x(Focus {
                        index,
                        pos: Dimensions {
                            x: focus.pos.x - screen.size.x + 2,
                            y: focus.pos.y * new_screen.size.y / screen.size.y,
                        }
                    })
                },
                None => if focus.pos.x > screen.size.x - 1 {
                    return Focus {
                        index: focus.index,
                        pos: Dimensions {
                            x: screen.size.x - 1,
                            y: focus.pos.y,
                        }
                    }
                },
            }
        }
            
        focus
    }

    fn normalize_y(&self, focus: Focus) -> Focus {
        let screen = &self.screens[focus.index as usize];
        if focus.pos.y <= 0 {
            match screen.edges.top {
                Some(index) => {
                    let new_screen = &self.screens[index as usize];
                    return self.normalize_y(Focus {
                        index,
                        pos: Dimensions {
                            x: focus.pos.x * new_screen.size.x / screen.size.x,
                            y: focus.pos.y + new_screen.size.y - 2,
                        }
                    })
                },
                None => if focus.pos.y < 0 {
                    return Focus {
                        index: focus.index,
                        pos: Dimensions {
                            x: focus.pos.x,
                            y: 0,
                        }
                    }
                },
            }
        } else if focus.pos.y >= screen.size.y - 1 {
            match screen.edges.bottom {
                Some(index) => {
                    let new_screen = &self.screens[index as usize];
                    return self.normalize_y(Focus {
                        index,
                        pos: Dimensions {
                            x: focus.pos.x * new_screen.size.x / screen.size.x,
                            y: focus.pos.y - screen.size.y + 2,
                        }
                    })
                },
                None => if focus.pos.y > screen.size.y - 1 {
                    return Focus {
                        index: focus.index,
                        pos: Dimensions {
                            x: focus.pos.x,
                            y: screen.size.y - 1,
                        }
                    }
                },
            }
        }
            
        focus
    }

    /// Add a new screen to the far right of the cluster
    fn add(&mut self, mut new_screen: Screen) {
        let new_index = self.screens.len() as Index;
        let mut index = 0 as Index;
        
        loop {
            let screen = &mut self.screens[index as usize];
            index = match screen.edges.right {
                Some(index) => index,
                None => {
                    screen.edges.right = Some(new_index);
                    new_screen.edges.left = Some(index);
                    break;
                }
            }
        }
        
        self.screens.push(new_screen);
    }

    /// Attempt to merge two clusters together
    pub fn merge(&mut self, other: Self) {
        'outer: for other_screen in other.screens {
            for other_addr in &other_screen.addrs {
                for screen in &self.screens {
                    for addr in &screen.addrs {
                        if addr.ip() == other_addr.ip() {
                            // TODO: Merge screens
                            continue 'outer;
                        }
                    }
                }
            }

            // If new address, add new screen 
            self.add(other_screen);
        }
    }

    /// Replace an existing cluster with a new cluster
    pub fn replace(&mut self, mut other: Self) -> Option<GrabEvent> {
        other.reset_local_screen();
        let event = other.refocus(other.focus);
        *self = other;
        event
    }

    pub fn get_screens(&self) -> &[Screen] {
        &self.screens
    }

    pub fn set_screens(&mut self, screens: Vec<Screen>) {
        self.screens = screens;
        self.reset_local_screen();
    }
}

#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Screen {
    name: String,
    size: Dimensions,
    edges: Edges,
    addrs: Vec<SocketAddr>,
}

impl Screen {
    pub fn new(width: i32, height: i32) -> Self {
        let port = 24242; // FIXME: Get from config
        Self {
            name: util::get_host_name().unwrap(),
            addrs: util::get_host_ips().unwrap()
                .filter_map(|addr| {
                    if !addr.is_loopback() {
                        Some(SocketAddr::new(addr, port))
                    } else {
                        None
                    }
                })
                .collect(),
            size: Dimensions { x: width, y: height },
            edges: Edges {
                top: None,
                right: None,
                bottom: None,
                left: None
            }
        }
    }

    pub fn default_route(&self) -> &SocketAddr {
        &self.addrs[0]
    }
}

#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
struct Dimensions {
    x: i32,
    y: i32,
}

#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
struct Edges {
    top: Option<Index>,
    right: Option<Index>,
    bottom: Option<Index>,
    left: Option<Index>,
}