use super::attribute::{Attributes, AttributesList};
use super::writer::{DotWriter, Line, Statement};

/// A [`Scope`] struct represents either a graph, digraph, subgraph or cluster.
/// Its the workhorse of the DOT writing, and can be used to create new sub-scopes,
/// add nodes, add edges, or adjust default attributes for any of the above.
///
/// The only way to construct a top level graph or digraph scope is to call the
/// [`DotWriter::graph`] or [`DotWriter::digraph`] functions on a new [`DotWriter`].
pub struct Scope<'d, 'w> {
    writer: &'d mut DotWriter<'w>,
}

impl<'d, 'w> Scope<'d, 'w> {
    pub(crate) fn new(writer: &'d mut DotWriter<'w>, prefix: &[u8]) -> Scope<'d, 'w> {
        {
            let mut line = Line::new(writer);
            line.write_with_whitespace(prefix);
            line.write(b" {");
        }
        writer.indent();
        Self { writer }
    }

    /// Starts a new nested subgraph, returning another Scope for writing to that subgraph.
    pub fn subgraph(&mut self) -> Scope<'_, 'w> {
        Scope::new(self.writer, b"subgraph")
    }

    /// Starts a new nested cluster subgraph, returning another Scope for writing to it.
    /// A cluster is a special case of a subgraph which groups its child nodes together.
    /// See the "Subgraphs and Clusters" section of the
    /// [Graphviz documentation](https://graphviz.org/doc/info/lang.html)
    /// for more information.
    pub fn cluster(&mut self) -> Scope<'_, 'w> {
        let label = format!("subgraph cluster_{}", self.writer.next_id());
        Scope::new(self.writer, label.as_bytes())
    }

    /// Returns a struct for writing the default attributes for all subgraphs from now on.
    pub fn graph_attributes(&mut self) -> AttributesList<'_, 'w> {
        let mut statement = Statement::new(self.writer);
        statement.write(b"graph");
        AttributesList::new(statement)
    }

    /// Returns a struct for writing the default attributes for all edges from now on.
    pub fn edge_attributes(&mut self) -> AttributesList<'_, 'w> {
        let mut statement = Statement::new(self.writer);
        statement.write(b"edge");
        AttributesList::new(statement)
    }

    /// Returns a struct for writing the default attributes for all nodes from now on.
    pub fn node_attributes(&mut self) -> AttributesList<'_, 'w> {
        let mut statement = Statement::new(self.writer);
        statement.write(b"node");
        AttributesList::new(statement)
    }

    /// Creates a new node, with an automatic default id of the format `node_x`
    /// where x is an incerementing integer. You don't have to declare nodes before
    /// using them in a call to [`Scope::edge`], but you do have to declare them using this
    /// function if you want to set specifc attributes for this node (font etc).
    ///
    /// The returned value can be used to get the automatically generated id,
    /// and also to set the attributes.
    ///
    /// ```
    /// use dot_writer::{DotWriter, Color, Attributes};
    ///
    /// let dot_string = DotWriter::write_string(|writer| {
    ///   writer.set_pretty_print(false);
    ///   let mut digraph = writer.digraph();
    ///   let a = digraph.node_auto().id();
    ///   let b = {
    ///     let mut node = digraph.node_auto();
    ///     node.set_color(Color::Red);
    ///     node.id()
    ///   };
    ///   digraph.edge(a, b);
    /// });
    /// assert_eq!(
    ///     dot_string,
    ///     "digraph{node_0;node_1[color=red];node_0->node_1;}"
    /// );
    /// ```
    pub fn node_auto(&mut self) -> Node<'_, 'w> {
        let id = self.writer.next_id();
        self.node_named(format!("node_{}", id))
    }

    /// Creates a new node, with the specified id. You don't have to declare nodes before
    /// using them in a call to [`Scope::edge`], but you do have to declare them using this
    /// function if you want to set specific attributes for this node (font etc).
    ///
    /// The returned value can be used to get the assigned name,
    /// and also to set the attributes.
    ///
    /// ```
    /// use dot_writer::{DotWriter, Color, Attributes};
    ///
    /// let dot_string = DotWriter::write_string(|writer| {
    ///   writer.set_pretty_print(false);
    ///   let mut digraph = writer.digraph();
    ///   let a = digraph.node_named("alpha").id();
    ///   let b = {
    ///     let mut node = digraph.node_named("beta");
    ///     node.set_color(Color::Red);
    ///     node.id()
    ///   };
    ///   digraph.edge(a, b);
    /// });
    /// assert_eq!(
    ///     dot_string,
    ///     "digraph{alpha;beta[color=red];alpha->beta;}"
    /// );
    /// ```
    pub fn node_named<S: Into<String>>(&mut self, id: S) -> Node<'_, 'w> {
        Node::new(Statement::new(self.writer), id.into())
    }

    /// Add a new edge joining `start_node_id` and `end_node_id` nodes.
    /// Note that nodes do not need to be already defined by [`Scope::node_auto`]
    /// or by [`Scope::node_named`] (unless you want to set node-specific attributes).
    /// Arguments can be just strings, or you can use the [`Node::id`] of an already
    /// defined node:
    ///
    /// ```
    /// use dot_writer::DotWriter;
    ///
    /// let mut output_bytes = Vec::new();
    /// {
    ///   let mut writer = DotWriter::from(&mut output_bytes);
    ///   writer.set_pretty_print(false);
    ///   let mut digraph = writer.digraph();
    ///   let a = digraph.node_auto().id();
    ///   digraph.edge(a, "b");
    /// }
    /// assert_eq!(
    ///     std::str::from_utf8(&output_bytes).unwrap(),
    ///     "digraph{node_0;node_0->b;}"
    /// );
    /// ```
    pub fn edge<F, T>(&mut self, from_node_id: F, to_node_id: T) -> EdgeList<'_, 'w>
    where
        F: AsRef<[u8]>,
        T: AsRef<[u8]>,
    {
        EdgeList::new(Statement::new(self.writer), from_node_id, to_node_id)
    }

    /// Add N-1 edges joining all node ids or subgraphs in the iterator,
    /// in the same manner as [`Scope::edge`].
    /// The return value will be None if less than 2 items are passed in.
    ///
    /// ```
    /// use dot_writer::DotWriter;
    ///
    /// let mut output_bytes = Vec::new();
    /// {
    ///   let mut writer = DotWriter::from(&mut output_bytes);
    ///   writer.set_pretty_print(false);
    ///   let mut digraph = writer.digraph();
    ///   digraph.edges(["a", "b", "c"]);
    /// }
    /// assert_eq!(
    ///     std::str::from_utf8(&output_bytes).unwrap(),
    ///     "digraph{a->b->c;}"
    /// );
    /// ```
    pub fn edges<I, E>(&mut self, items: I) -> Option<EdgeList<'_, 'w>>
    where
        I: IntoIterator<Item = E>,
        E: AsRef<[u8]>,
    {
        let mut iter = items.into_iter();
        let mut edge_list = self.edge(iter.next()?, iter.next()?);
        for item in iter {
            edge_list.edge(item);
        }
        Some(edge_list)
    }
}

impl<'d, 'w> Attributes for Scope<'d, 'w> {
    fn set(&mut self, name: &str, value: &str, quote: bool) -> &mut Self {
        {
            let mut statement = Statement::new(self.writer);
            statement.write(name.as_bytes());
            statement.write(b"=");
            if quote {
                statement.write_quoted(value.as_bytes());
            } else {
                statement.write(value.as_bytes());
            }
        }
        self
    }
}

impl<'d, 'w> Drop for Scope<'d, 'w> {
    fn drop(&mut self) {
        self.writer.unindent();
        Line::new(self.writer).write(b"}");
    }
}

/// An [`EdgeList`] is returned from [`Scope::edge`] and can be used to set the attributes
/// of an edge, or to chain additional nodes onto the edge statement.
pub struct EdgeList<'d, 'w> {
    statement: Statement<'d, 'w>,
}

impl<'d, 'w> EdgeList<'d, 'w> {
    fn new<S: AsRef<[u8]>, E: AsRef<[u8]>>(
        mut statement: Statement<'d, 'w>,
        start_node_id: S,
        end_node_id: E,
    ) -> EdgeList<'d, 'w> {
        statement.write(start_node_id.as_ref());
        statement.write_edge_operator();
        statement.write(end_node_id.as_ref());
        EdgeList { statement }
    }

    /// Adds another edge from the last node added to `node_id`.
    /// Note that nodes do not need to be already defined by [`Scope::node_auto`]
    /// or by [`Scope::node_named`] (unless you want to set node-specifc attributes).
    ///
    /// ```
    /// use dot_writer::DotWriter;
    ///
    /// let mut output_bytes = Vec::new();
    /// {
    ///   let mut writer = DotWriter::from(&mut output_bytes);
    ///   writer.set_pretty_print(false);
    ///   writer.digraph().edge("a", "b").edge("c").edge("d");
    /// }
    /// assert_eq!(
    ///     std::str::from_utf8(&output_bytes).unwrap(),
    ///     "digraph{a->b->c->d;}"
    /// );
    ///
    /// ```
    pub fn edge<N: AsRef<[u8]>>(&mut self, node_id: N) -> &mut Self {
        self.statement.write_edge_operator();
        self.statement.write(node_id.as_ref());
        self
    }

    /// Start writing the attributes section of an edge definition.
    /// See the returned structure for what attributes can be set.
    /// Note that no more nodes can be added after this function is called.
    pub fn attributes(self) -> AttributesList<'d, 'w> {
        AttributesList::new(self.statement)
    }
}

/// A [`Node`] is returned from [`Scope::node_named`] or [`Scope::node_auto`],
/// and allows for getting the id of the node for future reference via [`Node::id`],
///
/// Importantly it also implements [`Attributes`] for setting attributes
/// to override existing defaults for this specific node.
pub struct Node<'d, 'w> {
    attributes: AttributesList<'d, 'w>,
    id: String,
}

impl<'d, 'w> Node<'d, 'w> {
    fn new(mut statement: Statement<'d, 'w>, id: String) -> Self {
        statement.write(id.as_bytes());
        Node {
            attributes: AttributesList::new(statement),
            id,
        }
    }

    /// Returns a copy of the [`Node`] id, for later use with [`Scope::edge`].
    /// Note that as the node definition will not finish writing until after
    /// this [`Node`] goes out of scope, you'll need to save the id if you
    /// want to draw an edge to it later. This function is most usefull when
    /// the edge was automatically generated with [`Scope::node_auto`]:
    /// ```
    /// use dot_writer::DotWriter;
    ///
    /// let mut output_bytes = Vec::new();
    /// {
    ///     let mut writer = DotWriter::from(&mut output_bytes);
    ///     writer.set_pretty_print(false);
    ///     let mut digraph = writer.digraph();
    ///     let a_id = {
    ///         let mut sub_graph = digraph.subgraph();
    ///         let a_id = sub_graph.node_auto().id();
    ///         // sub_graph goes out of scope here to close bracket
    ///         // but id is kept for later call
    ///         a_id
    ///     };
    ///     digraph.edge(a_id, "b");
    /// }
    /// assert_eq!(
    ///     std::str::from_utf8(&output_bytes).unwrap(),
    ///     "digraph{subgraph{node_0;}node_0->b;}"
    /// );
    ///
    /// ```
    pub fn id(&self) -> NodeId {
        NodeId {
            id: self.id.clone(),
        }
    }
}

impl<'d, 'w> std::ops::DerefMut for Node<'d, 'w> {
    fn deref_mut(&mut self) -> &mut AttributesList<'d, 'w> {
        &mut self.attributes
    }
}

impl<'d, 'w> std::ops::Deref for Node<'d, 'w> {
    type Target = AttributesList<'d, 'w>;

    fn deref(&self) -> &AttributesList<'d, 'w> {
        &self.attributes
    }
}

/// A [`NodeId`] wraps a string storing the Id of a Node
/// It's designed for use with the [`Scope::edge`] function,
/// and for creating [`PortId`] using [`NodeId::port`].
#[derive(Clone, Debug)]
pub struct NodeId {
    id: String,
}

impl NodeId {
    /// Creates a [`PortId`] for refering to a port. These are specific sub parts of
    /// a [`Shape::Record`](`crate::attribute::Shape::Record`)
    /// or [`Shape::Mrecord`](`crate::attribute::Shape::Mrecord`)
    /// (for more information see "Record-based Nodes"
    /// on the [Graphviz documentation](https://graphviz.org/doc/info/shapes.html)).
    pub fn port(&self, port_id: &str) -> PortId {
        PortId {
            id: format!("{}:{}", self.id, port_id),
        }
    }
}

impl From<String> for NodeId {
    fn from(id: String) -> Self {
        Self { id }
    }
}

impl From<NodeId> for String {
    fn from(node_id: NodeId) -> Self {
        node_id.id
    }
}

impl AsRef<[u8]> for NodeId {
    fn as_ref(&self) -> &[u8] {
        self.id.as_bytes()
    }
}

/// A [`PortId`] wraps a string referning to the nnode id and port of a specifc record
/// or Mrecord node (for more information see "Record-based Nodes"
/// in the [Graphviz documentation](https://graphviz.org/doc/info/shapes.html)).
#[derive(Clone, Debug)]
pub struct PortId {
    id: String,
}

impl PortId {
    /// Creates a [`PortPosId`] for refering to a specific position on a record or Mrecord
    /// port for an edge to attach to.
    pub fn position(&self, position: PortPosition) -> PortPosId {
        PortPosId {
            id: format!("{}:{}", self.id, position.as_ref()),
        }
    }
}

impl From<String> for PortId {
    fn from(id: String) -> Self {
        Self { id }
    }
}

impl From<PortId> for String {
    fn from(port_id: PortId) -> Self {
        port_id.id
    }
}

impl AsRef<[u8]> for PortId {
    fn as_ref(&self) -> &[u8] {
        self.id.as_bytes()
    }
}

/// A [`PortPosId`] wraps a string referning to the [`NodeId`], [`PortId`] and [`PortPosition`]
/// of a specifc record or Mrecord node (for more information see "portPos"
/// in the [Graphviz documentation](https://graphviz.org/docs/attr-types/portPos/)).
#[derive(Clone, Debug)]
pub struct PortPosId {
    id: String,
}

impl From<String> for PortPosId {
    fn from(id: String) -> Self {
        Self { id }
    }
}

impl From<PortPosId> for String {
    fn from(port_pos_id: PortPosId) -> Self {
        port_pos_id.id
    }
}

impl AsRef<[u8]> for PortPosId {
    fn as_ref(&self) -> &[u8] {
        self.id.as_bytes()
    }
}

/// Refers the position to add an edge for of a specifc record or Mrecord node
/// (for more information see "portPos"
/// in the [Graphviz documentation](https://graphviz.org/docs/attr-types/portPos/)).
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum PortPosition {
    North,
    NorthEast,
    East,
    SouthEast,
    South,
    SouthWest,
    West,
    NorthWest,
    Centre,
    Auto,
}

impl AsRef<str> for PortPosition {
    fn as_ref(&self) -> &str {
        match self {
            Self::North => "n",
            Self::NorthEast => "ne",
            Self::East => "e",
            Self::SouthEast => "se",
            Self::South => "s",
            Self::SouthWest => "sw",
            Self::West => "w",
            Self::NorthWest => "nw",
            Self::Centre => "c",
            Self::Auto => "_",
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_manual_and_auto_nodes() {
        let output = DotWriter::write_string(|writer| {
            let mut digraph = writer.digraph();
            digraph.node_auto();
            let a = digraph.node_named("a").id();
            digraph.node_named(NodeId::from(String::from("b")));
            digraph.edge(a, "b").edge(String::from("c"));
        });
        assert_eq!(output, "digraph{node_0;a;b;a->b->c;}")
    }
}
