use std::fmt::Write;
use std::str;

pub const DEFAULT_WIDTH: usize = 80;

pub trait Emittable: std::fmt::Debug {
    fn write_on(&self, f: &mut Formatter);
}

#[derive(Clone, PartialEq, Eq)]
pub enum VerticalMode {
    Variable,
    Normal,
    ExtraNewline,
}

pub trait Vertical {
    fn set_vertical_mode(&mut self, mode: VerticalMode);
    fn write_vertically_on(&self, f: &mut Formatter);
}

pub type Item = std::rc::Rc<dyn Emittable>;

#[derive(Clone)]
pub struct Sequence {
    pub items: Vec<Item>,
    pub vertical_mode: VerticalMode,
    pub separator: &'static str,
    pub terminator: &'static str,
}

#[derive(Clone)]
pub struct Grouping {
    pub sequence: Sequence,
    pub open: &'static str,
    pub close: &'static str,
}

pub struct Formatter {
    pub width: usize,
    indent_delta: String,
    current_indent: String,
    pub buffer: String,
}

impl Formatter {
    pub fn new() -> Self {
        Formatter {
            width: DEFAULT_WIDTH,
            indent_delta: "    ".to_owned(),
            current_indent: "\n".to_owned(),
            buffer: String::new(),
        }
    }

    pub fn copy_empty(&self) -> Formatter {
        Formatter {
            width: self.width,
            indent_delta: self.indent_delta.clone(),
            current_indent: self.current_indent.clone(),
            buffer: String::new(),
        }
    }

    pub fn indent_size(self) -> usize {
        self.indent_delta.len()
    }

    pub fn set_indent_size(&mut self, n: usize) {
        self.indent_delta = str::repeat(" ", n)
    }

    pub fn write<E: Emittable>(&mut self, e: E) {
        e.write_on(self)
    }

    pub fn newline(&mut self) {
        self.buffer.push_str(&self.current_indent)
    }

    pub fn to_string<E: Emittable>(e: E) -> String {
        let mut f = Formatter::new();
        f.write(e);
        f.buffer
    }

    pub fn with_indent<R, F: FnOnce(&mut Self) -> R>(&mut self, f: F) -> R {
        let old_indent = self.current_indent.clone();
        self.current_indent += &self.indent_delta;
        let r = f(self);
        self.current_indent = old_indent;
        r
    }
}

impl Default for VerticalMode {
    fn default() -> Self {
        Self::Variable
    }
}

//---------------------------------------------------------------------------

impl Emittable for &str {
    fn write_on(&self, f: &mut Formatter) {
        f.buffer.push_str(self)
    }
}

impl Emittable for String {
    fn write_on(&self, f: &mut Formatter) {
        f.write(self.as_str())
    }
}

impl<'a, E: Emittable> Emittable for &'a Vec<E> where &'a E: Emittable {
    fn write_on(&self, f: &mut Formatter) {
        for e in self.iter() {
            f.write(e)
        }
    }
}

impl Emittable for Sequence {
    fn write_on(&self, f: &mut Formatter) {
        if self.vertical_mode != VerticalMode::Variable {
            self.write_vertically_on(f)
        } else {
            let mut need_sep = false;
            for e in self.items.iter() {
                if need_sep {
                    self.separator.write_on(f)
                } else {
                    need_sep = true
                }
                e.write_on(f)
            }
            if !self.items.is_empty() {
                self.terminator.write_on(f)
            }
        }
    }
}

impl Vertical for Sequence {
    fn set_vertical_mode(&mut self, vertical_mode: VerticalMode) {
        self.vertical_mode = vertical_mode;
    }

    fn write_vertically_on(&self, f: &mut Formatter) {
        let mut i = self.items.len();
        let mut first = true;
        for e in self.items.iter() {
            if !first {
                if self.vertical_mode == VerticalMode::ExtraNewline {
                    f.write("\n");
                }
                f.newline();
            }
            first = false;
            e.write_on(f);
            let delim = if i == 1 { self.terminator } else { self.separator };
            delim.trim_end_matches(|c: char| c.is_whitespace() && c != '\n').write_on(f);
            i = i - 1;
        }
    }
}

impl Emittable for Grouping {
    fn write_on(&self, f: &mut Formatter) {
        if self.sequence.vertical_mode != VerticalMode::Variable {
            self.write_vertically_on(f)
        } else {
            let mut g = f.copy_empty();
            self.open.write_on(&mut g);
            g.write(&self.sequence);
            self.close.write_on(&mut g);
            let s = g.buffer;
            if s.len() <= f.width {
                f.write(&s)
            } else {
                self.write_vertically_on(f)
            }
        }
    }
}

impl Vertical for Grouping {
    fn set_vertical_mode(&mut self, vertical_mode: VerticalMode) {
        self.sequence.set_vertical_mode(vertical_mode);
    }

    fn write_vertically_on(&self, f: &mut Formatter) {
        self.open.write_on(f);
        if !self.sequence.items.is_empty() {
            f.with_indent(|f| {
                f.newline();
                self.sequence.write_vertically_on(f)
            });
            f.newline()
        }
        self.close.write_on(f);
    }
}

impl<'a, E: Emittable> Emittable for &'a E {
    fn write_on(&self, f: &mut Formatter) {
        (*self).write_on(f)
    }
}

impl Emittable for Item {
    fn write_on(&self, f: &mut Formatter) {
        (**self).write_on(f)
    }
}

impl std::fmt::Debug for Sequence {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
        f.write_str(&Formatter::to_string(self))
    }
}

impl std::fmt::Debug for Grouping {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
        f.write_str(&Formatter::to_string(self))
    }
}

//---------------------------------------------------------------------------

pub fn escape_string(s: &str) -> String {
    let mut buf = String::new();
    buf.push('"');
    for c in s.chars() {
        match c {
            '\\' => buf.push_str("\\\\"),
            '"' => buf.push_str("\\\""),
            _ if c >= ' ' && c <= '~' => buf.push(c),
            _ => write!(&mut buf, "\\u{{{:x}}}", c as i32).expect("no IO errors building a string"),
        }
    }
    buf.push('"');
    buf
}

pub fn escape_bytes(bs: &[u8]) -> String {
    let mut buf = String::new();
    buf.push_str("b\"");
    for b in bs {
        let c = *b as char;
        match c {
            '\\' => buf.push_str("\\\\"),
            '"' => buf.push_str("\\\""),
            _ if c >= ' ' && c <= '~' => buf.push(c),
            _ => write!(&mut buf, "\\x{{{:02x}}}", b).expect("no IO errors building a string"),
        }
    }
    buf.push('"');
    buf
}

//---------------------------------------------------------------------------

pub mod constructors {
    use super::Sequence;
    use super::Grouping;
    use super::Item;
    use super::Emittable;
    use super::VerticalMode;
    use super::Vertical;

    pub fn item<E: 'static + Emittable>(i: E) -> Item {
        std::rc::Rc::new(i)
    }

    pub fn name(pieces: Vec<Item>) -> Sequence {
        Sequence { items: pieces, vertical_mode: VerticalMode::default(), separator: "::", terminator: "" }
    }

    pub fn seq(items: Vec<Item>) -> Sequence {
        Sequence { items: items, vertical_mode: VerticalMode::default(), separator: "", terminator: "" }
    }

    pub fn commas(items: Vec<Item>) -> Sequence {
        Sequence { items: items, vertical_mode: VerticalMode::default(), separator: ", ", terminator: "" }
    }

    pub fn parens(items: Vec<Item>) -> Grouping {
        Grouping { sequence: commas(items), open: "(", close: ")" }
    }

    pub fn brackets(items: Vec<Item>) -> Grouping {
        Grouping { sequence: commas(items), open: "[", close: "]" }
    }

    pub fn anglebrackets(items: Vec<Item>) -> Grouping {
        Grouping { sequence: commas(items), open: "<", close: ">" }
    }

    pub fn braces(items: Vec<Item>) -> Grouping {
        Grouping { sequence: commas(items), open: "{", close: "}" }
    }

    pub fn block(items: Vec<Item>) -> Grouping {
        Grouping {
            sequence: Sequence {
                items: items,
                vertical_mode: VerticalMode::default(),
                separator: " ",
                terminator: "",
            },
            open: "{",
            close: "}",
        }
    }

    pub fn codeblock(items: Vec<Item>) -> Grouping {
        vertical(false, block(items))
    }

    pub fn semiblock(items: Vec<Item>) -> Grouping {
        Grouping {
            sequence: Sequence {
                items: items,
                vertical_mode: VerticalMode::default(),
                separator: "; ",
                terminator: "",
            },
            open: "{",
            close: "}",
        }
    }

    pub fn vertical<V: Vertical>(spaced: bool, mut v: V) -> V {
        v.set_vertical_mode(if spaced { VerticalMode::ExtraNewline } else { VerticalMode::Normal });
        v
    }

    pub fn indented(sequence: Sequence) -> Grouping {
        Grouping { sequence, open: "", close: "" }
    }
}

pub mod macros {
    #[macro_export]
    macro_rules! name {
        ($($item:expr),*) => {$crate::syntax::block::constructors::name(vec![$(std::rc::Rc::new($item)),*])}
    }

    #[macro_export]
    macro_rules! seq {
        ($($item:expr),*) => {$crate::syntax::block::constructors::seq(vec![$(std::rc::Rc::new($item)),*])}
    }

    #[macro_export]
    macro_rules! commas {
        ($($item:expr),*) => {$crate::syntax::block::constructors::commas(vec![$(std::rc::Rc::new($item)),*])}
    }

    #[macro_export]
    macro_rules! parens {
        ($($item:expr),*) => {$crate::syntax::block::constructors::parens(vec![$(std::rc::Rc::new($item)),*])}
    }

    #[macro_export]
    macro_rules! brackets {
        ($($item:expr),*) => {$crate::syntax::block::constructors::brackets(vec![$(std::rc::Rc::new($item)),*])}
    }

    #[macro_export]
    macro_rules! anglebrackets {
        ($($item:expr),*) => {$crate::syntax::block::constructors::anglebrackets(vec![$(std::rc::Rc::new($item)),*])}
    }

    #[macro_export]
    macro_rules! braces {
        ($($item:expr),*) => {$crate::syntax::block::constructors::braces(vec![$(std::rc::Rc::new($item)),*])}
    }

    #[macro_export]
    macro_rules! block {
        ($($item:expr),*) => {$crate::syntax::block::constructors::block(vec![$(std::rc::Rc::new($item)),*])}
    }

    #[macro_export]
    macro_rules! codeblock {
        ($($item:expr),*) => {$crate::syntax::block::constructors::codeblock(vec![$(std::rc::Rc::new($item)),*])}
    }

    #[macro_export]
    macro_rules! semiblock {
        ($($item:expr),*) => {$crate::syntax::block::constructors::semiblock(vec![$(std::rc::Rc::new($item)),*])}
    }
}
