content: package io.activej.ot;
  
  import io.activej.ot.system.OTSystem;
  import io.activej.ot.utils.OTGraphBuilder;
  import io.activej.ot.utils.StubOTRepository;
  import io.activej.ot.utils.TestOp;
  import io.activej.ot.utils.Utils;
  import io.activej.test.rules.EventloopRule;
  import org.junit.ClassRule;
  import org.junit.Test;
  
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.function.Consumer;
  
  import static io.activej.common.Checks.checkArgument;
  import static io.activej.common.Utils.nonNullElse;
  import static io.activej.ot.OTAlgorithms.loadForMerge;
  import static io.activej.ot.utils.Utils.add;
  import static io.activej.ot.utils.Utils.createTestOp;
  import static io.activej.promise.TestUtils.await;
  import static org.junit.Assert.assertEquals;
  
  @SuppressWarnings("CodeBlock2Expr")
  public class OTMergeAlgorithmTest {
  	private static final OTSystem<TestOp> system = createTestOp();
  
  	@ClassRule
  	public static final EventloopRule eventloopRule = new EventloopRule();
  
  	static OTLoadedGraph<String, TestOp> buildGraph(Consumer<OTGraphBuilder<String, TestOp>> consumer) {
  		OTLoadedGraph<String, TestOp> graph = new OTLoadedGraph<>(OTMergeAlgorithmTest.system);
  		consumer.accept((parent, child, diffs) -> {
  			checkArgument(graph.getParents(child) == null || graph.getParents(child).get(parent) == null, "Invalid graph");
  			graph.addEdge(parent, child, diffs);
  		});
  		Map<String, Long> levels = new HashMap<>();
  		for (String commitId : graph.getTips()) {
  			Utils.calcLevels(commitId, levels,
  				parentId -> nonNullElse(graph.getParents(parentId), Map.<String, List<TestOp>>of()).keySet());
  		}
  		levels.forEach(graph::setLevel);
  
  		return graph;
  	}
  
  	@FunctionalInterface
  	private interface TestAcceptor {
  		void accept(OTLoadedGraph<String, TestOp> graph, Map<String, List<TestOp>> merge);
  	}
  
  	private void doTest(Set<String> heads, Consumer<OTGraphBuilder<String, TestOp>> graphBuilder, TestAcceptor testAcceptor) throws Exception {
  		doTestMerge(heads, graphBuilder, testAcceptor);
  		doTestLoadAndMerge(heads, graphBuilder, testAcceptor);
  	}
  
  	private void doTestMerge(Set<String> heads, Consumer<OTGraphBuilder<String, TestOp>> graphBuilder, TestAcceptor testAcceptor) throws Exception {
  		OTLoadedGraph<String, TestOp> graph = buildGraph(graphBuilder);
  		Map<String, List<TestOp>> merge;
  		try {
  			merge = graph.merge(heads);
  		} finally {
  			System.out.println(graph.toGraphViz());
  		}
  		testAcceptor.accept(graph, merge);
  	}
  
  	private void doTestLoadAndMerge(Set<String> heads, Consumer<OTGraphBuilder<String, TestOp>> graphBuilder, TestAcceptor testAcceptor) throws Exception {
  		StubOTRepository<String, TestOp> repository = StubOTRepository.create();
  		repository.setGraph(graphBuilder);
  
  		OTLoadedGraph<String, TestOp> graph = await(loadForMerge(repository, system, heads));
  		Map<String, List<TestOp>> merge;
  		try {
  			merge = graph.merge(heads);
  		} finally {
  			System.out.println(graph.toGraphViz());
  		}
  		testAcceptor.accept(graph, merge);
  	}
  
  	@Test
  	// Merge one node should return empty merge
  	public void test1() throws Exception {
  		doTest(Set.of("A", "B"), g -> {
  			g.add("A", "B", add(1));
  		}, (graph, merge) -> {
  			assertEquals(List.of(add(1)), merge.get("A"));
  			assertEquals(List.of(), merge.get("B"));
  		});
  	}
  
  	@Test
  	// Merge already merged line
  	public void test2() throws Exception {
  		doTest(Set.of("A", "B"), g -> {
  			g.add("A", "T", add(10));
  			g.add("T", "B", add(1));
  		}, (graph, merge) -> {
  			assertEquals(List.of(add(11)), merge.get("A"));
  			assertEquals(List.of(), merge.get("B"));
  		});
  	}
  
  	@Test
  	// Merge V form tree
  	public void test3() throws Exception {
  		doTest(Set.of("D", "E"), g -> {
  			g.add("A", "B", add(1));
  			g.add("A", "C", add(100));
  			g.add("B", "D", add(10));
  			g.add("C", "E", add(1000));
  		}, (graph, merge) -> {
  			assertEquals(List.of(add(1100)), merge.get("D"));
  			assertEquals(List.of(add(11)), merge.get("E"));
  		});
  	}
  
  	@Test
  	// Merge A, B nodes and D, E subnodes
  	public void test4() throws Exception {
  		doTest(Set.of("A", "B", "D", "E"), g -> {
  			g.add("A", "C", add(1));
  			g.add("B", "C", add(3));
  			g.add("B", "D", add(-5));
  			g.add("C", "E", add(10));
  		}, (graph, merge) -> {
  			assertEquals(List.of(add(-5)), merge.get("E"));
  			assertEquals(List.of(add(13)), merge.get("D"));
  			assertEquals(List.of(add(6)), merge.get("A"));
  			assertEquals(List.of(add(8)), merge.get("B"));
  		});
  	}
  
  	@Test
  	// Merge triple form tree
  	public void test5() throws Exception {
  		doTest(Set.of("A", "B", "C"), g -> {
  			g.add("*", "A", add(1));
  			g.add("*", "B", add(10));
  			g.add("*", "C", add(100));
  		}, (graph, merge) -> {
  			assertEquals(List.of(add(110)), merge.get("A"));
  			assertEquals(List.of(add(101)), merge.get("B"));
  			assertEquals(List.of(add(11)), merge.get("C"));
  		});
  	}
  
  	@Test
  	// Merge W form graph
  	public void test6() throws Exception {
  		doTest(Set.of("C", "D", "E"), g -> {
  			g.add("A", "C", add(3));
  			g.add("A", "D", add(10));
  			g.add("B", "D", add(1));
  			g.add("B", "E", add(30));
  		}, (graph, merge) -> {
  			assertEquals(List.of(add(40)), merge.get("C"));
  			assertEquals(List.of(add(33)), merge.get("D"));
  			assertEquals(List.of(add(4)), merge.get("E"));
  		});
  	}
  
  	@Test
  	// Merge equal merges of two nodes
  	public void test7() throws Exception {
  		doTest(Set.of("C", "D"), g -> {
  			g.add("A", "C", add(2));
  			g.add("A", "D", add(2));
  			g.add("B", "C", add(1));
  			g.add("B", "D", add(1));
  		}, (graph, merge) -> {
  			assertEquals(List.of(), merge.get("C"));
  			assertEquals(List.of(), merge.get("D"));
  		});
  	}
  
  	@Test
  	// Merge three equal merges on three nodes
  	public void test7a() throws Exception {
  		doTest(Set.of("D", "E", "F"), g -> {
  			g.add("A", "D", add(5));
  			g.add("A", "E", add(5));
  			g.add("A", "F", add(5));
  			g.add("B", "D", add(4));
  			g.add("B", "E", add(4));
  			g.add("B", "F", add(4));
  			g.add("C", "D", add(3));
  			g.add("C", "E", add(3));
  			g.add("C", "F", add(3));
  		}, (graph, merge) -> {
  			assertEquals(List.of(), merge.get("D"));
  			assertEquals(List.of(), merge.get("E"));
  			assertEquals(List.of(), merge.get("F"));
  		});
  	}
  
  	@Test
  	// Merge full merge and submerge
  	public void test8() throws Exception {
  		doTest(Set.of("E", "F"), g -> {
  			g.add("A", "C", add(10));
  			g.add("A", "D", add(100));
  			g.add("B", "E", add(10));
  			g.add("B", "F", add(110));
  			g.add("C", "E", add(1));
  			g.add("C", "F", add(101));
  			g.add("D", "F", add(11));
  		}, (graph, merge) -> {
  			assertEquals(List.of(add(100)), merge.get("E"));
  			assertEquals(List.of(), merge.get("F"));
  		});
  	}
  
  	@Test
  	// Merge two submerges
  	public void test9() throws Exception {
  		doTest(Set.of("G", "J"), g -> {
  			g.add("A", "C", add(1));
  			g.add("A", "D", add(10));
  			g.add("B", "E", add(100));
  			g.add("B", "F", add(1000));
  			g.add("C", "G", add(112));
  			g.add("D", "G", add(103));
  			g.add("D", "J", add(1102));
  			g.add("E", "G", add(14));
  			g.add("E", "J", add(1013));
  			g.add("F", "J", add(113));
  		}, (graph, merge) -> {
  			assertEquals(List.of(add(1000)), merge.get("G"));
  			assertEquals(List.of(add(1)), merge.get("J"));
  		});
  	}
  
  	@Test
  	// Merge having equal merges parents
  	public void test10() throws Exception {
  		doTest(Set.of("E", "F", "G"), g -> {
  			g.add("A", "C", add(3));
  			g.add("A", "D", add(3));
  			g.add("B", "C", add(2));
  			g.add("B", "D", add(2));
  			g.add("C", "E", add(1));
  			g.add("C", "F", add(10));
  			g.add("D", "G", add(100));
  		}, (graph, merge) -> {
  			assertEquals(List.of(add(110)), merge.get("E"));
  			assertEquals(List.of(add(101)), merge.get("F"));
  			assertEquals(List.of(add(11)), merge.get("G"));
  		});
  	}
  
  	@Test
  	// Merge having equal merges parents
  	public void test11() throws Exception {
  		doTest(Set.of("I", "J"), g -> {
  			g.add("A", "C", add(1));
  			g.add("A", "D", add(10));
  			g.add("B", "E", add(100));
  			g.add("B", "F", add(1000));
  			g.add("C", "G", add(112));
  			g.add("D", "G", add(103));
  			g.add("D", "H", add(1102));
  			g.add("E", "G", add(14));
  			g.add("E", "H", add(1013));
  			g.add("F", "H", add(113));
  			g.add("G", "I", add(-10));
  			g.add("H", "J", add(-100));
  		}, (graph, merge) -> {
  			assertEquals(List.of(add(900)), merge.get("I"));
  			assertEquals(List.of(add(-9)), merge.get("J"));
  		});
  	}
  
  	@Test
  	// Merge of merges should check operations
  	public void test12() throws Exception {
  		doTest(Set.of("F", "G"), g -> {
  			g.add("A", "D", add(1));
  			g.add("A", "B", add(100));
  			g.add("B", "C", add(2));
  			g.add("B", "E", add(3));
  			g.add("C", "F", add(1));
  			g.add("D", "F", add(102));
  			g.add("D", "G", add(103));
  			g.add("E", "G", add(1));
  		}, (graph, merge) -> {
  			assertEquals(List.of(add(3)), merge.get("F"));
  			assertEquals(List.of(add(2)), merge.get("G"));
  		});
  	}
  
  	@Test
  	// Should merge in different order
  	public void test13() throws Exception {
  		doTest(Set.of("F", "C", "E"), g -> {
  			g.add("A", "C", add(3));
  			g.add("A", "D", add(10));
  			g.add("B", "D", add(1));
  			g.add("B", "E", add(30));
  			g.add("D", "F", add(5));
  		}, (graph, merge) -> {
  			assertEquals(List.of(add(45)), merge.get("C"));
  			assertEquals(List.of(add(33)), merge.get("F"));
  			assertEquals(List.of(add(9)), merge.get("E"));
  		});
  	}
  
  	@Test
  	public void test14() throws Exception {
  		doTest(Set.of("X", "Y", "Z"), g -> {
  			g.add("A", "X", add(1));
  			g.add("A", "Z", add(2));
  			g.add("B", "X", add(1));
  			g.add("B", "Y", add(1));
  			g.add("C", "Y", add(1));
  			g.add("C", "Z", add(2));
  			g.add("B", "Z", add(2));
  		}, (graph, merge) -> {
  			System.out.println(merge);
  		});
  	}
  
  	@Test
  	public void test15() throws Exception {
  		doTest(Set.of("X", "Z"), g -> {
  			g.add("A", "U", add(10));
  			g.add("A", "Z", add(2));
  			g.add("B", "X", add(1));
  			g.add("B", "Z", add(2));
  			g.add("C", "V", add(100));
  			g.add("U", "X", add(-9));
  			g.add("V", "Z", add(-98));
  		}, (graph, merge) -> {
  			System.out.println(merge);
  		});
  	}
  
  	@Test
  	public void test16() throws Exception {
  		doTest(Set.of("X", "Y"), g -> {
  			g.add("A", "U", add(10));
  			g.add("A", "Y", add(2));
  			g.add("B", "X", add(1));
  			g.add("B", "V", add(100));
  			g.add("U", "X", add(-9));
  			g.add("V", "Y", add(-98));
  		}, (graph, merge) -> {
  			System.out.println(merge);
  		});
  	}
  
  }
  