package sudoku.time;

import common.time.AbstractTime;
import common.time.Time;
import java.util.Collections;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
import sudoku.Claim;
import sudoku.Rule;

/**
 * <p>A Time in which some Claims are {@link Claim#setFalse(FalsifiedTime) set false}.</p> <p>This
 * class is a base class for {@code Time}s that denote events in the process of solving a sudoku
 * puzzle, including direct {@link TechniqueEvent}s in which a Technique changes the puzzle and
 * indirect {@link Rule.AutoResolve AutoResolve} events in which changes made to the puzzle by some
 * solution event allow a part of the puzzle to
 * {@link Rule#validateState(FalsifiedTime) automatically detect} a solving action that it can take
 * locally.</p>
 * @author fiveham
 * @author fiveham
 *
 */
public abstract class FalsifiedTime extends AbstractTime {
	
	private final Set<Claim> falsified;
	
  /**
   * <p>Constructs a FalsifiedTime having the specified {@code parent} and representing the
   * falsification of Claims from {@code falsified} that are not included as
   * {@link #falsified() claims} of any nth-parents of this Time that are instances of
   * FalsifiedTime.</p>
   * @param parent the event which caused this event and of which this event is a part
   * @param falsified a superset of the Claims set false in this event
   * @throws NoUnaccountedClaims if all the Claims in {@code falsified} are accounted for as false
   * by this Time's {@code FalsifiedTime} nth-parents
   */
	public FalsifiedTime(Time parent, Set<Claim> falsified){
		super(parent);
		
		Set<Claim> f = new HashSet<>(falsified);
		upTrail().stream()
    		.skip(1)
    		.filter(FalsifiedTime.class::isInstance)
    		.map(FalsifiedTime.class::cast)
    		.map(FalsifiedTime::falsified)
    		.forEach(f::removeAll);
		if(f.isEmpty()){
		  throw new NoUnaccountedClaims("No unaccounted-for Claims specified.");
		}
		
		this.falsified = Collections.unmodifiableSet(f);
		this.falsified.stream().forEach(Claim::setFalse);
	}
	
  /**
   * <p>Returns the unmodifiable set of claims set false by the operation that this time node
   * represents.</p>
   * @return the set of claims set false by the operation that this time node represents
   */
	public Set<Claim> falsified(){
		return falsified;
	}
	
	@Override
	public String toString(){
		StringBuilder result = new StringBuilder(toStringStart())
				.append(" falsifying ")
				.append(falsified().size())
				.append(" Claims directly, and ")
				.append(deepFalse())
				.append(" Claims indirectly.")
				.append(System.lineSeparator())
				.append("Direct: ")
				.append(falsified())
				.append(System.lineSeparator());
		
		for(Time t : children()){
			String str = t.toString();
			for(Scanner s = new Scanner(str); s.hasNextLine();){
				result.append("  ").append(s.nextLine()).append(System.lineSeparator());
			}
		}
		
		return result.toString();
	}
	
	@Override
	public boolean equals(Object o){
		if(o instanceof FalsifiedTime){
			FalsifiedTime ft = (FalsifiedTime) o;
			return falsified().equals(ft.falsified());
		}
		return false;
	}
	
  /**
   * <p>A short description of this type of Time. The output of {@code toString()} begins with
   * this. A trailing space should not be included. The description should take the form of a noun
   * phrase.</p>
   * @return a short description of this type of Time
   */
	protected abstract String toStringStart();
	
  /**
   * <p>Returns the number of Claims {@link #falsified() falsified} by all the FalsifiedTime
   * nth-children of this Time.</p>
   * @return the number of Claims {@link #falsified() falsified} by all the FalsifiedTime
   * nth-children of this Time
   */
	private int deepFalse(){
		int count = 0;
		
		Set<Time> layer = new HashSet<>(children());
		while(!layer.isEmpty()){
			Set<Time> newLayer = new HashSet<>();
			for(Time t : layer){
				newLayer.addAll(t.children());
				if(t instanceof FalsifiedTime){
					count += ((FalsifiedTime) t).falsified().size();
				}
			}
			layer = newLayer;
		}
		
		return count;
	}
	
  /**
   * <p>An Exception thrown when a FalsifiedTime is constructed without any specified falsified
   * Claims not already {@link #falsified falsified} by the constructed FalsifiedTime's
   * FalsifiedTime nth-parents.</p>
   * @author fiveham
   * @author fiveham
	 *
	 */
	public static class NoUnaccountedClaims extends RuntimeException{
		
		private static final long serialVersionUID = 7063069284727178843L;
		
		private NoUnaccountedClaims(String s){
			super(s);
		}
	}
}

@@ -15,16 +15,36 @@
 .time;%0A%0A
+import common.Sets;%0A
 import c
@@ -3789,17 +3789,24 @@
 t%3CTime%3E 
-l
+currentL
 ayer = n
@@ -3840,17 +3840,24 @@
 %09while(!
-l
+currentL
 ayer.isE
@@ -3872,156 +3872,137 @@
 %0A%09%09%09
-Set%3CTime%3E newLayer = new HashSet%3C%3E
+count += currentLayer.stream
 ()
-;
 %0A%09%09%09
-for(Time t : layer)%7B%0A%09%09%09%09newLayer.addAll(t.children());%0A%09%09%09%09if(t instanceof FalsifiedTime)%7B%0A%09%09%09%09%09count += (
+    .filter(FalsifiedTime.class::isInstance)%0A%09%09%09    .map(FalsifiedTime.class::cast)%0A%09%09%09    .map
 (Fal
@@ -4015,13 +4015,10 @@
 Time
-) t).
+::
 fals
@@ -4026,49 +4026,269 @@
 fied
-().size();%0A%09%09%09%09%7D%0A%09%09%09%7D%0A%09%09%09layer = newLayer
+)%0A%09%09%09    .mapToInt(Set::size)%0A%09%09%09    .reduce(0, Integer::sum);%0A%09%09%09currentLayer = currentLayer.stream()%0A          .map(Time::children)%0A          .map(HashSet%3CTime%3E::new)%0A          .reduce(%0A              new HashSet%3CTime%3E(), %0A              Sets::mergeCollections)
 ;%0A%09%09

refactor deepFalse for clarity