package de.retest.recheck.ui.diff;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import de.retest.recheck.RecheckProperties;
import de.retest.recheck.ui.DefaultValueFinder;
import de.retest.recheck.ui.descriptors.ElementUtil;
import de.retest.recheck.ui.descriptors.RootElement;

public class RootElementDifferenceFinder {

	private static final org.slf4j.Logger logger =
			org.slf4j.LoggerFactory.getLogger( RootElementDifferenceFinder.class );

	private static final double ROOT_ELEMENT_MATCH_THRESHOLD =
			RecheckProperties.getInstance().rootElementMatchThreshold();
	private static final double ROOT_ELEMENT_CONTAINED_CHILDREN_MATCH_THRESHOLD =
			RecheckProperties.getInstance().rootElementContainedChildrenMatchThreshold();

	private final ElementDifferenceFinder elementDifferenceFinder;

	public RootElementDifferenceFinder( final DefaultValueFinder defaultValueFinder ) {
		elementDifferenceFinder = new ElementDifferenceFinder( defaultValueFinder );
	}

	public List<RootElementDifference> findDifferences( final List<RootElement> expecteds,
			final List<RootElement> actuals ) {
		final List<RootElementDifference> differences = new ArrayList<>();
		final List<RootElement> copyOfActuals = new ArrayList<>( actuals );
		for ( final RootElement expected : expecteds ) {
			final RootElementDifference difference =
					findDifference( expected, findAndRemove( expected, copyOfActuals ) );
			if ( difference != null ) {
				differences.add( difference );
			}
		}
		for ( final RootElement actual : copyOfActuals ) {
			differences.add( findDifference( null, actual ) );
		}
		return differences;
	}

	private RootElement findAndRemove( final RootElement expected, final List<RootElement> actuals ) {
		double bestMatch = 0.0;
		RootElement bestWindow = null;
		for ( final RootElement actual : actuals ) {
			if ( actual.getIdentifyingAttributes().equals( expected.getIdentifyingAttributes() ) ) {
				bestMatch = 1.0d;
				bestWindow = actual;
				break;
			}
			final double currentMatch = actual.getIdentifyingAttributes().match( expected.getIdentifyingAttributes() );
			if ( currentMatch > bestMatch ) {
				bestMatch = currentMatch;
				bestWindow = actual;
			}
		}
		if ( bestWindow == null ) {
			logger.info( "No window at all found!" );
			return null;
		}
		if ( bestMatch >= ROOT_ELEMENT_MATCH_THRESHOLD ) {
			actuals.remove( bestWindow );
			return bestWindow;
		}
		logger.info( "Best match of window {} did not exceed MATCH_THRESHOLD with {}: {}",
				expected.getIdentifyingAttributes(), bestMatch, bestWindow );
		final double containedComponentsMatch = compareContainedComponents( expected, bestWindow );
		if ( containedComponentsMatch >= ROOT_ELEMENT_CONTAINED_CHILDREN_MATCH_THRESHOLD ) {
			logger.info( "Best match of window has a match of contained components of {}.", containedComponentsMatch );
			return bestWindow;
		}
		return null;
	}

	private double compareContainedComponents( final RootElement expected, final RootElement bestWindow ) {
		return getNumberOfChildDifferences( expected, bestWindow )
				/ (getNumberOfChildComponents( expected, bestWindow ) + 1.0);
	}

	private int getNumberOfChildDifferences( final RootElement expected, final RootElement bestWindow ) {
		final Collection<ElementDifference> differences =
				elementDifferenceFinder.findChildDifferences( expected, bestWindow );
		int count = 0;
		for ( final ElementDifference elementDifference : differences ) {
			count += elementDifference.getImmediateDifferences().size();
		}
		logger.debug( "Found {} child differences for window {} and best match {}.", count, expected, bestWindow );
		return count;
	}

	private int getNumberOfChildComponents( final RootElement expected, final RootElement bestWindow ) {
		final int expectedChildCount = ElementUtil.flattenChildElements( expected ).size();
		final int bestWindowChildCount = ElementUtil.flattenChildElements( bestWindow ).size();
		logger.debug( "Window {} contained {} components, and and best match {} contained {} components.",
				new Object[] { expected, expectedChildCount, bestWindow, bestWindowChildCount } );
		return Math.max( expectedChildCount, bestWindowChildCount );
	}

	public RootElementDifference findDifference( final RootElement expected, final RootElement actual ) {
		final long startTime = System.currentTimeMillis();
		final ElementDifference elementDifference = elementDifferenceFinder.differenceFor( expected, actual );
		logger.debug( "Finding differences for window took {}ms.", System.currentTimeMillis() - startTime );
		return elementDifference != null ? new RootElementDifference( elementDifference, expected, actual ) : null;
	}

}

@@ -4041,27 +4041,8 @@
 s.%22,
-%0A%09%09%09%09new Object%5B%5D %7B
  exp
@@ -4047,17 +4047,21 @@
 xpected,
- 
+%0A%09%09%09%09
 expected
@@ -4108,10 +4108,8 @@
 ount
- %7D
  );%0A

Remove array creation and simply pass the elements