use {
    std::sync::Arc,
    std::sync::RwLock,
    cgmath::{Point2},
    crate::{
        core::{
            View,
            ViewPort,
            OuterViewPort,
            InnerViewPort,
            Observer,
            ObserverBroadcast
        },
        sequence::{SequenceView},
        terminal::{TerminalStyle, TerminalView, make_label},
        projection::ProjectionHelper
    }
};

//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>

pub struct ListDecorator {
    opening_port: OuterViewPort<dyn TerminalView>,
    closing_port: OuterViewPort<dyn TerminalView>,
    delim_port: OuterViewPort<dyn TerminalView>,
    items: Arc<dyn SequenceView<Item = OuterViewPort<dyn TerminalView>>>,

    list_style: TerminalStyle,
    item_style: TerminalStyle,

    cast: Arc<RwLock<ObserverBroadcast<dyn SequenceView<Item = OuterViewPort<dyn TerminalView>>>>>,
    proj_helper: ProjectionHelper<Self>
}

impl View for ListDecorator {
    type Msg = usize;
}

impl SequenceView for ListDecorator {
    type Item = OuterViewPort<dyn TerminalView>;

    fn len(&self) -> Option<usize> {
        let l = self.items.len()?;
        Some(if l == 0 { 2 } else { 2*l + 2 })
    }

    fn get(&self, idx: &usize) -> Option<Self::Item> {
        let item_idx = idx / 2;
        let list_style = self.list_style.clone();
        let item_style = self.item_style.clone();
        let l = self.items.len().unwrap_or(0);
        Some(
            if *idx == 0 {
                self.opening_port.clone()
                    .map_item(move |_, atom| atom.add_style_back(list_style))
            } else if (l == 0 && *idx == 1) || *idx == 2*l {
                self.closing_port.clone()
                    .map_item(move |_, atom| atom.add_style_back(list_style))
            } else if idx % 2 == 0 {
                self.delim_port.clone()
                    .map_item(move |_, atom| atom.add_style_back(list_style))
            } else {
                self.items
                    .get(&item_idx)?
                    .map_item(move |_, atom| atom.add_style_back(item_style))
            }
        )
    }
}

impl ListDecorator {
    pub fn new(
        opening: &str,
        closing: &str,
        delim: &str,
        level: usize,
        items_port: OuterViewPort<dyn SequenceView<Item = OuterViewPort<dyn TerminalView>>>,
        out_port: InnerViewPort<dyn SequenceView<Item = OuterViewPort<dyn TerminalView>>>
    ) -> Arc<RwLock<Self>> {
        let mut proj_helper = ProjectionHelper::new(out_port.0.update_hooks.clone());
        
        let li = Arc::new(RwLock::new(ListDecorator {
            opening_port: make_label(opening),
            closing_port: make_label(closing),
            delim_port: make_label(delim),
            items: proj_helper.new_sequence_arg(
                items_port,
                |s: &mut Self, item_idx| {
                    s.cast.notify(
                        &(*item_idx * 2 + 1)
                    );
                    s.cast.notify(
                        &(*item_idx * 2 + 2)
                    );
                }
            ),
            list_style: TerminalStyle::fg_color(
                match level {
                    0 => (200, 120, 10),
                    1 => (120, 200, 10),
                    _ => (255, 255, 255)
                }
            ),
            item_style: TerminalStyle::fg_color(
                match level {
                    _ => (255, 255, 255)
                }
            ),
            cast: out_port.get_broadcast(),
            proj_helper
        }));

        li.write().unwrap().proj_helper.set_proj(&li);
        
        out_port.set_view(Some(li.clone()));
        li
    }
}

//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>

use crate::{index::IndexView, grid::GridView};

pub struct VerticalSexprDecorator {
    opening_port: OuterViewPort<dyn TerminalView>,
    closing_port: OuterViewPort<dyn TerminalView>,
    items: Arc<dyn SequenceView<Item = OuterViewPort<dyn TerminalView>>>,

    list_style: TerminalStyle,
    item_style: TerminalStyle,

    cast: Arc<RwLock<ObserverBroadcast<dyn GridView<Item = OuterViewPort<dyn TerminalView>>>>>,
    proj_helper: ProjectionHelper<Self>
}

impl View for VerticalSexprDecorator {
    type Msg = Point2<i16>;
}

impl IndexView<Point2<i16>> for VerticalSexprDecorator {
    type Item = OuterViewPort<dyn TerminalView>;

    fn area(&self) -> Option<Vec<Point2<i16>>> {
        let mut area = (0 .. self.items.len()?).map(|i| Point2::new(1 as i16, i as i16)).collect::<Vec<_>>();
        area.push(Point2::new(0, 0));
        area.push(Point2::new(2, self.items.len()? as i16 - 1));
        Some(area)
    }

    fn get(&self, pt: &Point2<i16>) -> Option<Self::Item> {
        let item_idx = pt.y as usize;
        let list_style = self.list_style.clone();
        let item_style = self.item_style.clone();
        let l = self.items.len().unwrap_or(0);

        match pt.x {
            0 => {
                if pt.y == 0 {
                    Some(self.opening_port.clone()
                         .map_item(move |_, atom| atom.add_style_back(list_style)))
                } else {
                    None
                }
            }
            1 => {
                if item_idx < l {
                    Some(self.items.get(&item_idx)?.map_item(move |_, atom| atom.add_style_back(item_style)))
                } else {
                    None
                }
            }
            2 => {
                if (l == 0 && pt.y == 0) || (item_idx+1 == l) {
                    Some(self.closing_port.clone()
                         .map_item(move |_, atom| atom.add_style_back(list_style)))
                } else {
                    None
                }
            }
            _ => None
        }
    }
}

impl VerticalSexprDecorator {
    pub fn new(
        opening: &str,
        closing: &str,
        level: usize,
        items_port: OuterViewPort<dyn SequenceView<Item = OuterViewPort<dyn TerminalView>>>,
        out_port: InnerViewPort<dyn GridView<Item = OuterViewPort<dyn TerminalView>>>
    ) -> Arc<RwLock<Self>> {
        let mut proj_helper = ProjectionHelper::new(out_port.0.update_hooks.clone());
        
        let li = Arc::new(RwLock::new(VerticalSexprDecorator {
            opening_port: make_label(opening),
            closing_port: make_label(closing),
            items: proj_helper.new_sequence_arg(
                items_port,
                |s: &mut Self, item_idx| {
                    s.cast.notify(
                        &Point2::new(1, *item_idx as i16)
                    );
                    s.cast.notify(
                        &Point2::new(2, *item_idx as i16 - 1)
                    );
                    s.cast.notify(
                        &Point2::new(2, *item_idx as i16)
                    );
                }
            ),
            list_style: TerminalStyle::fg_color(
                match level {
                    0 => (200, 120, 10),
                    1 => (120, 200, 10),
                    _ => (255, 255, 255)
                }
            ),
            item_style: TerminalStyle::fg_color(
                match level {
                    _ => (255, 255, 255)
                }
            ),
            cast: out_port.get_broadcast(),
            proj_helper
        }));

        li.write().unwrap().proj_helper.set_proj(&li);
        
        out_port.set_view(Some(li.clone()));
        li
    }
}


pub trait SExprView {
    fn horizontal_sexpr_view(&self, level: usize) -> OuterViewPort<dyn TerminalView>;
    fn vertical_bar_view(&self, level: usize) -> OuterViewPort<dyn TerminalView>;
    fn vertical_sexpr_view(&self, level: usize) -> OuterViewPort<dyn TerminalView>;
}

impl SExprView for OuterViewPort<dyn SequenceView<Item = OuterViewPort<dyn TerminalView>>> {
    fn horizontal_sexpr_view(&self, level: usize) -> OuterViewPort<dyn TerminalView> {
        let port = ViewPort::new();
        ListDecorator::new(
            "(", ")", " ",
            level,
            self.clone(),
            port.inner()
        );

        port.into_outer()
            .to_grid_horizontal()
            .flatten()
    }

    fn vertical_bar_view(&self, level: usize) -> OuterViewPort<dyn TerminalView> {
        let port = ViewPort::new();
        ListDecorator::new(
            "Λ", "V", "|",
            level,
            self.clone(),
            port.inner()
        );

        port.into_outer()
            .to_grid_vertical()
            .flatten()
    }

    fn vertical_sexpr_view(&self, level: usize) -> OuterViewPort<dyn TerminalView> {
        let port = ViewPort::new();
        VerticalSexprDecorator::new(
            "(", ")",
            level,
            self.clone(),
            port.inner()
        );
        port.into_outer()
            .flatten()        
    }
}