// Path: src/main/java/mirror/UpdateTreeDiff.java
// public static class DiffResults {
//   public final List<Update> sendToRemote = new ArrayList<>();
//   public final List<Update> saveLocally = new ArrayList<>();
// 
//   @Override
//   public String toString() {
//     return "[sendToRemote=" + sendToRemote.size() + ",saveLocally=" + saveLocally.size() + "]";
//   }
// }
// 
// Path: src/main/java/mirror/tasks/TaskLogic.java
// public interface TaskLogic {
// 
//   /** Run one interaction, and return how long the task wants to sleep. */
//   Duration runOneLoop() throws InterruptedException;
// 
//   /** Called on the task thread, before we start calling {@link #runOneLoop()} in a loop. */
//   default void onStart() throws InterruptedException {
//   }
// 
//   default void onFailure() throws InterruptedException {
//   }
// 
//   /** Called on the task thread, after we've interrupted/stopped calling {@link #runOneLoop()}. */
//   default void onStop() throws InterruptedException {
//   }
// 
//   /**
//    * Called off the task thread, when we're trying to interrupt the task.
//    *
//    * Most threads shouldn't need this because they'll shut down
//    * when we call thread.interrupt().
//    */
//   default void onInterrupt() {
//   }
// 
//   default String getName() {
//     String name = getClass().getSimpleName();
//     // lambdas don't have simple names
//     if (name.equals("")) {
//       name = StringUtils.substringAfterLast(getClass().getName(), ".");
//     }
//     return name;
//   }
// }


import static org.apache.commons.lang3.StringUtils.defaultIfEmpty;
import static org.apache.commons.lang3.StringUtils.substringAfterLast;
import static org.jooq.lambda.Seq.seq;
import static org.jooq.lambda.tuple.Tuple.tuple;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.jooq.lambda.tuple.Tuple2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.annotations.VisibleForTesting;
import mirror.UpdateTreeDiff.DiffResults;
import mirror.tasks.TaskLogic;
  private List<Update> getNextBatchOrBlock() throws InterruptedException {
    List<Update> updates = new ArrayList<>();
    // block for at least one
    updates.add(queues.incomingQueue.take());
    // now go ahead and drain the rest while we're here
    queues.incomingQueue.drainTo(updates);
    return updates;
  }

  @VisibleForTesting
  void poll() throws IOException, InterruptedException {
    Update u;
    while ((u = queues.incomingQueue.poll()) != null) {
      handleUpdate(u);
    }
    diff();
  }

  private void handleUpdate(Update u) throws InterruptedException {
    if (u.getLocal()) {
      if (isStaleLocalUpdate(u)) {
        return;
      }
      tree.addLocal(ensureSettledAndReadModTime(u));
    } else {
      tree.addRemote(u);
    }
  }

  private void diff() throws InterruptedException {
// Path: src/main/java/mirror/UpdateTreeDiff.java
// public static class DiffResults {
//   public final List<Update> sendToRemote = new ArrayList<>();
//   public final List<Update> saveLocally = new ArrayList<>();
// 
//   @Override
//   public String toString() {
//     return "[sendToRemote=" + sendToRemote.size() + ",saveLocally=" + saveLocally.size() + "]";
//   }
// }
// 
// Path: src/main/java/mirror/tasks/TaskLogic.java
// public interface TaskLogic {
// 
//   /** Run one interaction, and return how long the task wants to sleep. */
//   Duration runOneLoop() throws InterruptedException;
// 
//   /** Called on the task thread, before we start calling {@link #runOneLoop()} in a loop. */
//   default void onStart() throws InterruptedException {
//   }
// 
//   default void onFailure() throws InterruptedException {
//   }
// 
//   /** Called on the task thread, after we've interrupted/stopped calling {@link #runOneLoop()}. */
//   default void onStop() throws InterruptedException {
//   }
// 
//   /**
//    * Called off the task thread, when we're trying to interrupt the task.
//    *
//    * Most threads shouldn't need this because they'll shut down
//    * when we call thread.interrupt().
//    */
//   default void onInterrupt() {
//   }
// 
//   default String getName() {
//     String name = getClass().getSimpleName();
//     // lambdas don't have simple names
//     if (name.equals("")) {
//       name = StringUtils.substringAfterLast(getClass().getName(), ".");
//     }
//     return name;
//   }
// }


// Path: src/main/java/mirror/SyncLogic.java
import static org.apache.commons.lang3.StringUtils.defaultIfEmpty;
import static org.apache.commons.lang3.StringUtils.substringAfterLast;
import static org.jooq.lambda.Seq.seq;
import static org.jooq.lambda.tuple.Tuple.tuple;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.jooq.lambda.tuple.Tuple2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.annotations.VisibleForTesting;
import mirror.UpdateTreeDiff.DiffResults;
import mirror.tasks.TaskLogic;

  private List<Update> getNextBatchOrBlock() throws InterruptedException {
    List<Update> updates = new ArrayList<>();
    // block for at least one
    updates.add(queues.incomingQueue.take());
    // now go ahead and drain the rest while we're here
    queues.incomingQueue.drainTo(updates);
    return updates;
  }

  @VisibleForTesting
  void poll() throws IOException, InterruptedException {
    Update u;
    while ((u = queues.incomingQueue.poll()) != null) {
      handleUpdate(u);
    }
    diff();
  }

  private void handleUpdate(Update u) throws InterruptedException {
    if (u.getLocal()) {
      if (isStaleLocalUpdate(u)) {
        return;
      }
      tree.addLocal(ensureSettledAndReadModTime(u));
    } else {
      tree.addRemote(u);
    }
  }

  private void diff() throws InterruptedException {
    DiffResults r = new UpdateTreeDiff(tree).diff();