class JUnit4ReportWriter implements ReportWriter {

	@Override
	public void writeReport(ReportTreeAccessor reportTreeAccessor, String executionId, File outputFile) throws IOException {
		ReportTreeVisitor visitor = new ReportTreeVisitor(reportTreeAccessor);
		
		try(BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile))) {
			// Using AtomicInteger and StringBuilder because of the "final limitation" in lambdas...
			AtomicInteger numberOfTests = new AtomicInteger(0);
			AtomicInteger numberOfFailures = new AtomicInteger(0);
			AtomicInteger numberOfErrors = new AtomicInteger(0);
			AtomicInteger numberOfSkipped = new AtomicInteger(0);
			AtomicLong duration = new AtomicLong();
			StringBuilder name = new StringBuilder();
			
			// First visit the report tree to get the root node informations and the different counts
			visitor.visit(executionId, e->{
				if(e.getStack().size()==0) {
					name.append(e.getNode().getName());
					duration.set(e.getNode().getDuration());
				}
				if(e.getStack().size()==1) {
					numberOfTests.incrementAndGet();
					ReportNode node = e.getNode();
					if(node.getStatus() == ReportNodeStatus.FAILED) {
						numberOfFailures.incrementAndGet();
					} else if(node.getStatus() == ReportNodeStatus.TECHNICAL_ERROR) {
						numberOfErrors.incrementAndGet();
					} else if(node.getStatus() == ReportNodeStatus.SKIPPED) {
						numberOfSkipped.incrementAndGet();
					}
				}
			});
			
			
			writer.write("<testsuite name=\""+name.toString()+"\" time=\""+formatTime(duration.get())+"\" tests=\""+numberOfTests.get()+"\" skipped=\""+numberOfSkipped.get()+"\" failures=\""+numberOfFailures.get()+"\" errors=\""+numberOfErrors.get()+"\">");
			writer.newLine();
			
			AtomicBoolean errorWritten = new AtomicBoolean(false);
			// visit the tree again and write the <testcase> blocks
			visitor.visit(executionId, new ReportNodeVisitorEventHandler() {
				@Override
				public void startReportNode(ReportNodeEvent event) {
					ReportNode node = event.getNode();
					try {
						// as a convention report the children of the first level as testcases
						if(event.getStack().size()==1) {
							if(!skipReportNode(node)) {
								writer.write("<testcase classname=\""+node.getClass().getName()+"\" name=\""+node.getName()+"\" time=\""+formatTime(node.getDuration())+"\">");
								errorWritten.set(false);
							}
						} else if (event.getStack().size()>1) {
							// report all the errors of the sub nodes (level > 1)
							if(node.getError() != null) {
								writeErrorOrFailure(writer, node, errorWritten);
							}							
						}
					} catch (IOException e1) {
						throw new RuntimeException(e1);
					}
				}
				
				@Override
				public void endReportNode(ReportNodeEvent event) {
					if(event.getStack().size()==1) {
						ReportNode node = event.getNode();
						if(!skipReportNode(node)) {
							try {
								// if no error has been found in the sub-nodes, report the error for this node
								if(node.getStatus()!=ReportNodeStatus.PASSED && !errorWritten.get()) {
									writeErrorOrFailure(writer, node, errorWritten);
								}
								// close the <testcase> block
								writer.write("</testcase>");
								writer.newLine();
							} catch (IOException e1) {
								throw new RuntimeException(e1);
							}
						}
					}
				}

				protected boolean skipReportNode(ReportNode node) {
					return node.getStatus()==ReportNodeStatus.SKIPPED || node.getStatus()==ReportNodeStatus.NORUN;
				}
			});
			
			writer.write("</testsuite>");
			writer.newLine();
			
			writer.flush();
		}
	}

}