/*
 * Copyright (c) 2014 - 2016 by Stefan Ferstl <st.ferstl@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.ferstl.depgraph.dot;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;

import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

/**
 * A builder to create <a href="http://www.graphviz.org/doc/info/lang.html">DOT</a> strings by defining edges between
 * Nodes. The builder allows some customizations including custom {@link NodeRenderer}s and
 * {@link EdgeRenderer}s.
 *
 * @param <T> Type of the graph nodes.
 */
public final class DotBuilder<T> {

  private String graphName;
  private AttributeBuilder nodeAttributeBuilder;
  private AttributeBuilder edgeAttributeBuilder;
  private NodeRenderer<? super T> nodeIdRenderer;
  private NodeRenderer<? super T> nodeNameRenderer;
  private EdgeRenderer<? super T> edgeRenderer;
  private boolean omitSelfReferences;
  private final Map<String, Node<T>> nodeDefinitions;
  private final Set<Edge> edges;

  public DotBuilder() {
    this.graphName = "G";
    this.nodeAttributeBuilder = new AttributeBuilder().shape("box").fontName("Helvetica");
    this.edgeAttributeBuilder = new AttributeBuilder().fontName("Helvetica").fontSize(10);
    this.nodeIdRenderer = createDefaultNodeIdRenderer();
    this.nodeNameRenderer = createDefaultNodeNameRenderer();
    this.edgeRenderer = createDefaultEdgeRenderer();

    this.nodeDefinitions = new LinkedHashMap<>();
    this.edges = new LinkedHashSet<>();
  }

  public DotBuilder<T> graphName(String name) {
    this.graphName = name;
    return this;
  }

  public DotBuilder<T> nodeStyle(AttributeBuilder attributeBuilder) {
    this.nodeAttributeBuilder = attributeBuilder;
    return this;
  }

  public DotBuilder<T> edgeStyle(AttributeBuilder attributeBuilder) {
    this.edgeAttributeBuilder = attributeBuilder;
    return this;
  }

  public DotBuilder<T> useNodeIdRenderer(NodeRenderer<? super T> nodeIdRenderer) {
    this.nodeIdRenderer = nodeIdRenderer;
    return this;
  }

  public DotBuilder<T> useNodeNameRenderer(NodeRenderer<? super T> nodeNameRenderer) {
    this.nodeNameRenderer = nodeNameRenderer;
    return this;
  }

  public DotBuilder<T> useEdgeRenderer(EdgeRenderer<? super T> edgeRenderer) {
    this.edgeRenderer = edgeRenderer;
    return this;
  }

  public DotBuilder<T> omitSelfReferences() {
    this.omitSelfReferences = true;
    return this;
  }

  // no edge will be created in case one or both nodes are null.
  public DotBuilder<T> addEdge(T from, T to) {
    if (from != null && to != null) {
      addNode(from);
      addNode(to);

      safelyAddEdge(from, to);
    }

    return this;
  }

  public DotBuilder<T> addEdge(T from, T to, EdgeRenderer<? super T> edgeRenderer) {
    EdgeRenderer<? super T> originalEdgeRenderer = this.edgeRenderer;
    this.edgeRenderer = edgeRenderer;
    addEdge(from, to);
    this.edgeRenderer = originalEdgeRenderer;

    return this;
  }

  /**
   * Returns the node that was added <strong>first</strong> to this builder or the given node if new.
   *
   * @param node Node.
   * @return The firstly added node or the given node if not present.
   */
  public T getEffectiveNode(T node) {
    String key = this.nodeIdRenderer.render(node);
    if (this.nodeDefinitions.containsKey(key)) {
      return this.nodeDefinitions.get(key).nodeObject;
    }

    return node;
  }

  @Override
  public String toString() {
    // Work around some generics restrictions
    ImmutableList.Builder<Node<?>> nodeListBuilder = ImmutableList.builder();
    for (Node<?> node : this.nodeDefinitions.values()) {
      nodeListBuilder.add(node);
    }
    ImmutableList<Node<?>> nodeList = nodeListBuilder.build();
    ImmutableSet<Edge> edgeSet = ImmutableSet.copyOf(this.edges);

    return new DotGraphFormatter(this.nodeAttributeBuilder, this.edgeAttributeBuilder).format(this.graphName, nodeList, edgeSet);
  }

  private void addNode(T node) {
    String nodeId = this.nodeIdRenderer.render(node);
    String nodeName = this.nodeNameRenderer.render(node);
    this.nodeDefinitions.put(nodeId, new Node<>(nodeId, nodeName, node));
  }

  private void safelyAddEdge(T fromNode, T toNode) {
    String fromNodeId = this.nodeIdRenderer.render(fromNode);
    String toNodeId = this.nodeIdRenderer.render(toNode);

    if (!this.omitSelfReferences || !fromNodeId.equals(toNodeId)) {
      Edge edge = new Edge(fromNodeId, toNodeId, this.edgeRenderer.render(fromNode, toNode));
      this.edges.add(edge);
    }
  }

  static <T> EdgeRenderer<T> createDefaultEdgeRenderer() {
    return new EdgeRenderer<T>() {

      @Override
      public String render(T from, T to) {
        return "";
      }

    };
  }

  static <T> NodeRenderer<T> createDefaultNodeIdRenderer() {
    return new NodeRenderer<T>() {

      @Override
      public String render(T node) {
        return node.toString();
      }
    };
  }

  static <T> NodeRenderer<T> createDefaultNodeNameRenderer() {
    return new NodeRenderer<T>() {

      @Override
      public String render(T node) {
        return "";
      }
    };
  }

}

@@ -1312,32 +1312,73 @@
 tributeBuilder;%0A
+  private GraphFormatter graphFormatter;%0A
   private NodeRe
@@ -1403,32 +1403,32 @@
 nodeIdRenderer;%0A
-
   private NodeRe
@@ -1869,16 +1869,119 @@
 ze(10);%0A
+    this.graphFormatter = new DotGraphFormatter(this.nodeAttributeBuilder, this.edgeAttributeBuilder);%0A
     this
@@ -3165,16 +3165,141 @@
 s;%0A  %7D%0A%0A
+  public DotBuilder%3CT%3E graphFormatter(GraphFormatter formatter) %7B%0A    this.graphFormatter = formatter;%0A    return this;%0A  %7D%0A%0A
   // no 
@@ -4662,24 +4662,22 @@
  return 
-new DotG
+this.g
 raphForm
@@ -4685,62 +4685,8 @@
 tter
-(this.nodeAttributeBuilder, this.edgeAttributeBuilder)
 .for

Add GraphFormatter to builder