use std::io::Write;

use super::scope::Scope;

const INDENT_STEP: usize = 2;

/// The entry point struct for writing a DOT graph.
/// See the examples on the index for how to use it.
/// This struct must live for the livetime of writing the graph,
/// and also outlive the [`Write`] struct it points to,
/// because it's [`Drop`] trait will finish writing graph.
pub struct DotWriter<'w> {
    writer: &'w mut dyn Write,
    indent: usize,
    next_id: usize,
    digraph: bool,
    pretty_print: bool,
}

/// This is the only constructor, which requires a [`Write`]
/// to borrow and write the DOT output to.
/// Defaults to pretty printing the output (see [`DotWriter::set_pretty_print`]).
impl<'w, W: Write> From<&'w mut W> for DotWriter<'w> {
    fn from(writer: &'w mut W) -> Self {
        Self {
            writer,
            indent: 0,
            next_id: 0,
            digraph: false,
            pretty_print: true,
        }
    }
}

impl<'w> DotWriter<'w> {
    /// If set to true, then output will have additional whitespace
    /// including newlines and indentation to make the output more
    /// human friendly. If false then this will be left out and the output
    /// will be more compact. Defaults to true. For example disabled:
    ///
    /// ```
    /// use dot_writer::DotWriter;
    ///
    /// let mut bytes = Vec::new();
    /// let mut writer = DotWriter::from(&mut bytes);
    /// writer.set_pretty_print(false);
    /// writer.graph().subgraph().cluster().subgraph();
    /// println!("{}", std::str::from_utf8(&bytes).unwrap());
    /// ```
    ///
    /// Gives you:
    ///
    /// `
    /// graph {subgraph {subgraph cluster_0 {subgraph {}}}}
    /// `
    ///
    /// While enabled:
    ///
    /// ```
    /// use dot_writer::DotWriter;
    ///
    /// let mut bytes = Vec::new();
    /// let mut writer = DotWriter::from(&mut bytes);
    /// // writer.set_pretty_print(false); defaults to true anyway
    /// writer.graph().subgraph().cluster().subgraph();
    /// println!("{}", std::str::from_utf8(&bytes).unwrap());
    /// ```
    ///
    /// Gives you:
    ///
    /// ```txt
    /// graph {
    ///   subgraph {
    ///     subgraph cluster_0 {
    ///       subgraph {}
    ///     }
    ///   }
    /// }
    ///```
    pub fn set_pretty_print(&mut self, pretty_print: bool) {
        self.pretty_print = pretty_print;
    }

    /// Start a new undirection graph. This uses the edge operator `--`
    /// automatically, which means edges should be rendered without any
    /// arrowheads by default.    
    /// ```
    /// use dot_writer::DotWriter;
    ///
    /// let mut bytes = Vec::new();
    /// let mut writer = DotWriter::from(&mut bytes);
    /// writer.set_pretty_print(false);
    /// writer.graph().edge("a", "b");
    /// assert_eq!(
    ///     std::str::from_utf8(&bytes).unwrap(),
    ///     "graph{a--b;}"
    /// );
    /// ```
    pub fn graph(&mut self) -> Scope<'_, 'w> {
        self.digraph = false;
        Scope::new(self, b"graph")
    }

    /// Start a new directed graph. This uses the edge operator `->`
    /// automatically, which means edges should be redered with an
    /// arrow head pointing to the head (end) node.    
    /// ```
    /// use dot_writer::DotWriter;
    ///
    /// let mut bytes = Vec::new();
    /// let mut writer = DotWriter::from(&mut bytes);
    /// writer.set_pretty_print(false);
    /// writer.digraph().edge("a", "b");
    /// assert_eq!(
    ///     std::str::from_utf8(&bytes).unwrap(),
    ///     "digraph{a->b;}"
    /// );
    /// ```
    pub fn digraph(&mut self) -> Scope<'_, 'w> {
        self.digraph = true;
        Scope::new(self, b"digraph")
    }

    /// Uses a callback to write DOT to a [`String`]. This is useful
    /// if you just want to write your dot code to a string rather than
    /// a file or stdout, and want less boiler plate setup code.
    /// It's used internally for unit testing, so the output is not
    /// pretty printed by default (but you can overwrite that by calling
    /// [`DotWriter::set_pretty_print`] from within the callback).
    ///
    /// ```
    /// use dot_writer::DotWriter;
    ///
    /// let output = DotWriter::write_string(|writer| {
    ///     let mut graph = writer.graph();
    ///     graph.edge("a", "b");
    /// });
    /// assert_eq!(output, "graph{a--b;}");
    /// ```
    pub fn write_string<F: Fn(&mut DotWriter)>(builder: F) -> String {
        let mut bytes = Vec::new();
        let mut writer = DotWriter::from(&mut bytes);
        writer.set_pretty_print(false);
        (builder)(&mut writer);
        String::from_utf8(bytes).unwrap()
    }
}

impl<'w> DotWriter<'w> {
    pub(crate) fn write(&mut self, bytes: &[u8]) {
        if self.pretty_print {
            self.writer.write_all(bytes).unwrap();
        } else {
            for b in bytes.iter().filter(|b| !b.is_ascii_whitespace()) {
                self.writer.write_all(&[*b]).unwrap();
            }
        }
    }

    pub(crate) fn write_with_whitespace(&mut self, bytes: &[u8]) {
        self.writer.write_all(bytes).unwrap();
    }

    pub(crate) fn write_quoted(&mut self, bytes: &[u8]) {
        self.writer.write_all(b"\"").unwrap();
        self.write_with_whitespace(bytes);
        self.writer.write_all(b"\"").unwrap();
    }

    pub(crate) fn write_indent(&mut self) {
        for _ in 0..self.indent {
            self.writer.write_all(b" ").unwrap();
        }
    }

    pub(crate) fn write_edge_operator(&mut self) {
        match self.digraph {
            true => self.write(b" -> "),
            false => self.write(b" -- "),
        }
    }

    pub(crate) fn indent(&mut self) {
        if self.pretty_print {
            self.indent += INDENT_STEP;
        }
    }

    pub(crate) fn unindent(&mut self) {
        if self.pretty_print {
            self.indent -= INDENT_STEP;
        }
    }

    pub(crate) fn next_id(&mut self) -> usize {
        let next_id = self.next_id;
        self.next_id += 1;
        next_id
    }
}

//
// Statement
//

pub(crate) struct Statement<'d, 'w> {
    line: Line<'d, 'w>,
}

impl<'d, 'w> Statement<'d, 'w> {
    pub(crate) fn new(writer: &'d mut DotWriter<'w>) -> Self {
        Self {
            line: Line::new(writer),
        }
    }
}

impl<'d, 'w> std::ops::DerefMut for Statement<'d, 'w> {
    fn deref_mut(&mut self) -> &mut DotWriter<'w> {
        &mut *self.line
    }
}

impl<'d, 'w> std::ops::Deref for Statement<'d, 'w> {
    type Target = DotWriter<'w>;
    fn deref(&self) -> &DotWriter<'w> {
        &*self.line
    }
}

impl<'d, 'w> Drop for Statement<'d, 'w> {
    fn drop(&mut self) {
        self.line.write(b";");
    }
}

//
// Line
//

pub(crate) struct Line<'d, 'w> {
    writer: &'d mut DotWriter<'w>,
}

impl<'d, 'w> Line<'d, 'w> {
    pub(crate) fn new(writer: &'d mut DotWriter<'w>) -> Self {
        writer.write_indent();
        Self { writer }
    }
}

impl<'d, 'w> std::ops::DerefMut for Line<'d, 'w> {
    fn deref_mut(&mut self) -> &mut DotWriter<'w> {
        self.writer
    }
}

impl<'d, 'w> std::ops::Deref for Line<'d, 'w> {
    type Target = DotWriter<'w>;
    fn deref(&self) -> &DotWriter<'w> {
        self.writer
    }
}

impl<'a, 'b: 'a> Drop for Line<'a, 'b> {
    fn drop(&mut self) {
        if self.writer.pretty_print {
            self.writer.write(b"\n");
        }
    }
}
