package org.graphwalker.core.model;

/*
 * #%L
 * GraphWalker Core
 * %%
 * Copyright (C) 2005 - 2014 GraphWalker
 * %%
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * #L%
 */

import org.junit.Assert;
import org.junit.Test;

import java.util.HashSet;
import java.util.Set;

import static org.graphwalker.core.model.Edge.RuntimeEdge;
import static org.graphwalker.core.model.Model.RuntimeModel;
import static org.graphwalker.core.model.Vertex.RuntimeVertex;
import static org.hamcrest.core.Is.is;

/**
 * @author Nils Olsson
 */
public class VisitorTest {

  RuntimeModel model = new Model().addEdge(
    new Edge()
      .setName("edge1")
      .setSourceVertex(new Vertex()
        .setName("vertex1"))
      .setTargetVertex(new Vertex()
        .setName("vertex2")))
    .build();

  @Test
  public void visitVertex() {
    ElementVisitor visitor = new MyElementVisitor();
    new Vertex().setName("vertex").build().accept(visitor);
  }

  @Test
  public void visitVertices() {
    MyNamedVertexCounter visitor = new MyNamedVertexCounter();
    model.accept(visitor);
    Assert.assertThat(visitor.count, is(2));
  }

  @Test
  public void visitEdge() {
    ElementVisitor visitor = new MyEdgeVisitor();
    new Edge().setName("edge1").setSourceVertex(new Vertex()).setTargetVertex(new Vertex()).build().accept(visitor);
  }

  @Test
  public void visitEdges() {
    Vertex startVertex = new Vertex().setName("start");
    Vertex endVertex = new Vertex().setName("end");
    RuntimeModel pseudograph = new Model()
      .addEdge(new Edge().setSourceVertex(startVertex).setTargetVertex(endVertex))
      .addEdge(new Edge().setSourceVertex(endVertex).setTargetVertex(endVertex))
      .build();
    MyLoopEdgeFinder visitor = new MyLoopEdgeFinder();
    pseudograph.accept(visitor);
    Assert.assertThat(visitor.count, is(1));
  }

  @Test
  public void detectBridge() {
    Vertex start = new Vertex();
    Vertex v1 = new Vertex().setName("V1");
    Vertex v2 = new Vertex().setName("V2");
    Edge e1 = new Edge().setSourceVertex(start).setTargetVertex(v2);
    Model model = new Model().addEdge(e1)
      .addEdge(new Edge().setSourceVertex(start).setTargetVertex(v1));

    MyVertexCounter count1 = new MyVertexCounter(model.build());
    MyVertexCounter count2 = new MyVertexCounter(model.build(), e1.build());

    start.build().accept(count1);
    start.build().accept(count2);

    // if e1 is a bridge then the vertex count will differ between count1 and count2
    Assert.assertNotEquals(count1.count, count2.count);

    // if we add a edge between v1 and v2 then e1 is no longer a bridge and count1 and count3 should be the same
    model.addEdge(new Edge().setSourceVertex(v1).setTargetVertex(v2));
    MyVertexCounter count3 = new MyVertexCounter(model.build(), e1.build());
    start.build().accept(count3);
    Assert.assertEquals(count1.count, count3.count);

  }

  private class MyElementVisitor implements ElementVisitor {

    @Override
    public void visit(Element element) {
    }
  }

  private class MyNamedVertexCounter implements ElementVisitor {

    int count = 0;

    @Override
    public void visit(Element element) {
      if (element instanceof RuntimeModel) {
        RuntimeModel model = (RuntimeModel) element;
        // We don't need to visit() all edges to count the vertices with names, it's just PoC (we could just loop over them)
        for (Element childElement : model.getElements()) {
          childElement.accept(this);
        }
      } else if (element instanceof RuntimeVertex && element.hasName()) {
        count++;
      }
    }
  }

  private class MyLoopEdgeFinder implements ElementVisitor {

    int count = 0;

    @Override
    public void visit(Element element) {
      if (element instanceof RuntimeModel) {
        RuntimeModel model = (RuntimeModel) element;
        for (RuntimeEdge edge : model.getEdges()) {
          if (edge.getSourceVertex().equals(edge.getTargetVertex())) {
            count++;
          }
        }
      }
    }
  }

  private class MyEdgeVisitor implements ElementVisitor {

    @Override
    public void visit(Element element) {
    }
  }

  private class MyVertexCounter implements ElementVisitor {

    private RuntimeModel model;
    private int count = 0;
    private Set<Element> visited = new HashSet<>();

    MyVertexCounter(RuntimeModel model) {
      this(model, null);
    }

    MyVertexCounter(RuntimeModel model, RuntimeEdge skipEdge) {
      this.model = model;
      if (null != skipEdge) {
        visited.add(skipEdge);
      }
    }

    @Override
    public void visit(Element element) {
      visited.add(element);
      if (element instanceof RuntimeVertex) {
        visit((RuntimeVertex) element);
      } else if (element instanceof RuntimeEdge) {
        visit((RuntimeEdge) element);
      }
    }

    private void visit(RuntimeVertex vertex) {
      count++;
      for (RuntimeEdge edge : model.getOutEdges(vertex)) {
        if (!isVisited(edge)) {
          edge.accept(this);
        }
      }
    }

    private void visit(RuntimeEdge edge) {
      if (!isVisited(edge.getTargetVertex())) {
        edge.getTargetVertex().accept(this);
      }
    }

    private boolean isVisited(Element element) {
      return visited.contains(element);
    }
  }
}

@@ -5973,35 +5973,37 @@
 ) %7B%0A        if (
-!
 is
+Not
 Visited(edge)) %7B
@@ -6114,11 +6114,13 @@
 if (
-!
 is
+Not
 Visi
@@ -6232,16 +6232,19 @@
 olean is
+Not
 Visited(
@@ -6275,16 +6275,17 @@
  return 
+!
 visited.

Fix analysis complaint