// Path: src/main/java/javapns/devices/Device.java
// public interface Device {
//   /**
//    * An id representing a particular device.
//    * <p>
//    * Note that this is a local reference to the device,
//    * which is not related to the actual device UUID or
//    * other device-specific identification. Most of the
//    * time, this deviceId should be the same as the token.
//    *
//    * @return the device id
//    */
//   String getDeviceId();
// 
//   /**
//    * An id representing a particular device.
//    * <p>
//    * Note that this is a local reference to the device,
//    * which is not related to the actual device UUID or
//    * other device-specific identification. Most of the
//    * time, this deviceId should be the same as the token.
//    *
//    * @param id the device id
//    */
//   void setDeviceId(String id);
// 
//   /**
//    * A device token.
//    *
//    * @return the device token
//    */
//   String getToken();
// 
//   /**
//    * Set the device token
//    *
//    * @param token
//    */
//   void setToken(String token);
// 
//   /**
//    * @return the last register
//    */
//   Timestamp getLastRegister();
// 
//   /**
//    * @param lastRegister the last register
//    */
//   void setLastRegister(Timestamp lastRegister);
// }
// 
// Path: src/main/java/javapns/devices/Devices.java
// public class Devices {
// 
//   private Devices() {}
// 
//   public static List<Device> asDevices(final Object rawList) {
//     final List<Device> list = new ArrayList<>();
//     if (rawList == null) {
//       return list;
//     }
// 
//     if (rawList instanceof List) {
//       final List devices = (List) rawList;
//       if (devices.isEmpty()) {
//         return list;
//       }
// 
//       final Object firstDevice = devices.get(0);
//       if (firstDevice instanceof Device) {
//         //noinspection unchecked
//         return devices;
//       } else if (firstDevice instanceof String) {
//         for (final Object token : devices) {
//           final BasicDevice device = new BasicDevice();
//           device.setToken((String) token);
//           list.add(device);
//         }
//       }
//     } else if (rawList instanceof String[]) {
//       final String[] tokens = (String[]) rawList;
//       for (final String token : tokens) {
//         final BasicDevice device = new BasicDevice();
//         device.setToken(token);
//         list.add(device);
//       }
//     } else if (rawList instanceof Device[]) {
//       final Device[] dvs = (Device[]) rawList;
//       return Arrays.asList(dvs);
//     } else if (rawList instanceof String) {
//       final BasicDevice device = new BasicDevice();
//       device.setToken((String) rawList);
//       list.add(device);
//     } else if (rawList instanceof Device) {
//       list.add((Device) rawList);
//     } else {
//       throw new IllegalArgumentException("Device list type not supported. Supported types are: String[], List<String>, Device[], List<Device>, String and Device");
//     }
//     return list;
//   }
// 
//   public static List<PayloadPerDevice> asPayloadsPerDevices(final Object rawList) {
//     final List<PayloadPerDevice> list = new ArrayList<>();
//     if (rawList == null) {
//       return list;
//     }
//     if (rawList instanceof List) {
//       final List devices = (List) rawList;
//       if (devices.isEmpty()) {
//         return list;
//       }
//       //noinspection unchecked
//       return devices;
//     } else if (rawList instanceof PayloadPerDevice[]) {
//       final PayloadPerDevice[] dvs = (PayloadPerDevice[]) rawList;
//       return Arrays.asList(dvs);
//     } else if (rawList instanceof PayloadPerDevice) {
//       list.add((PayloadPerDevice) rawList);
//     } else {
//       throw new IllegalArgumentException("PayloadPerDevice list type not supported. Supported types are: PayloadPerDevice[], List<PayloadPerDevice> and PayloadPerDevice");
//     }
//     return list;
//   }
// }
// 
// Path: src/main/java/javapns/devices/exceptions/InvalidDeviceTokenFormatException.java
// public class InvalidDeviceTokenFormatException extends Exception {
// 
//   private static final long serialVersionUID = -8571997399252125457L;
// 
//   public InvalidDeviceTokenFormatException(final String message) {
//     super(message);
//   }
// 
//   public InvalidDeviceTokenFormatException(final String token, final String problem) {
//     super(String.format("Device token cannot be parsed, most likely because it contains invalid hexadecimal characters: %s in %s", problem, token));
//   }
// 
// }


import javapns.devices.Device;
import javapns.devices.Devices;
import javapns.devices.exceptions.InvalidDeviceTokenFormatException;
import javapns.notification.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.stream.Collectors;
package javapns.notification.transmission;



/**
 * <h1>Pushes a payload to a large number of devices using multiple threads</h1>
 * <p>
 * <p>The list of devices is spread evenly into multiple {@link javapns.notification.transmission.NotificationThread}s.</p>
 * <p>
 * <p>Usage: once a NotificationThreads is created, invoke {@code start()} to start all {@link javapns.notification.transmission.NotificationThread} threads.</p>
 * <p>You can provide a {@link javapns.notification.transmission.NotificationProgressListener} to receive events about the work being done.</p>
 *
 * @author Sylvain Pedneault
 * @see NotificationThread.MODE
 * @see NotificationThread
 */
public class NotificationThreads extends ThreadGroup implements PushQueue {
  private static final long DEFAULT_DELAY_BETWEEN_THREADS = 500; // the number of milliseconds to wait between each thread startup
  private static final String JAVAPNS_NOTIFICATION_THREADS = "javapns notification threads (";
  private static final String THREADS = " threads)";

  private final Object finishPoint = new Object();

  private List<NotificationThread> threads = new ArrayList<>();
  private NotificationProgressListener listener;

  private boolean started = false;
  private int threadsRunning = 0;
  private int nextThread = 0;
  private long delayBetweenThreads = DEFAULT_DELAY_BETWEEN_THREADS;

  /**
   * Create the specified number of notification threads and spread the devices evenly between the threads.
   *
   * @param server          the server to push to
   * @param payload         the payload to push
   * @param devices         a very large list of devices
   * @param numberOfThreads the number of threads to create to share the work
   */
// Path: src/main/java/javapns/devices/Device.java
// public interface Device {
//   /**
//    * An id representing a particular device.
//    * <p>
//    * Note that this is a local reference to the device,
//    * which is not related to the actual device UUID or
//    * other device-specific identification. Most of the
//    * time, this deviceId should be the same as the token.
//    *
//    * @return the device id
//    */
//   String getDeviceId();
// 
//   /**
//    * An id representing a particular device.
//    * <p>
//    * Note that this is a local reference to the device,
//    * which is not related to the actual device UUID or
//    * other device-specific identification. Most of the
//    * time, this deviceId should be the same as the token.
//    *
//    * @param id the device id
//    */
//   void setDeviceId(String id);
// 
//   /**
//    * A device token.
//    *
//    * @return the device token
//    */
//   String getToken();
// 
//   /**
//    * Set the device token
//    *
//    * @param token
//    */
//   void setToken(String token);
// 
//   /**
//    * @return the last register
//    */
//   Timestamp getLastRegister();
// 
//   /**
//    * @param lastRegister the last register
//    */
//   void setLastRegister(Timestamp lastRegister);
// }
// 
// Path: src/main/java/javapns/devices/Devices.java
// public class Devices {
// 
//   private Devices() {}
// 
//   public static List<Device> asDevices(final Object rawList) {
//     final List<Device> list = new ArrayList<>();
//     if (rawList == null) {
//       return list;
//     }
// 
//     if (rawList instanceof List) {
//       final List devices = (List) rawList;
//       if (devices.isEmpty()) {
//         return list;
//       }
// 
//       final Object firstDevice = devices.get(0);
//       if (firstDevice instanceof Device) {
//         //noinspection unchecked
//         return devices;
//       } else if (firstDevice instanceof String) {
//         for (final Object token : devices) {
//           final BasicDevice device = new BasicDevice();
//           device.setToken((String) token);
//           list.add(device);
//         }
//       }
//     } else if (rawList instanceof String[]) {
//       final String[] tokens = (String[]) rawList;
//       for (final String token : tokens) {
//         final BasicDevice device = new BasicDevice();
//         device.setToken(token);
//         list.add(device);
//       }
//     } else if (rawList instanceof Device[]) {
//       final Device[] dvs = (Device[]) rawList;
//       return Arrays.asList(dvs);
//     } else if (rawList instanceof String) {
//       final BasicDevice device = new BasicDevice();
//       device.setToken((String) rawList);
//       list.add(device);
//     } else if (rawList instanceof Device) {
//       list.add((Device) rawList);
//     } else {
//       throw new IllegalArgumentException("Device list type not supported. Supported types are: String[], List<String>, Device[], List<Device>, String and Device");
//     }
//     return list;
//   }
// 
//   public static List<PayloadPerDevice> asPayloadsPerDevices(final Object rawList) {
//     final List<PayloadPerDevice> list = new ArrayList<>();
//     if (rawList == null) {
//       return list;
//     }
//     if (rawList instanceof List) {
//       final List devices = (List) rawList;
//       if (devices.isEmpty()) {
//         return list;
//       }
//       //noinspection unchecked
//       return devices;
//     } else if (rawList instanceof PayloadPerDevice[]) {
//       final PayloadPerDevice[] dvs = (PayloadPerDevice[]) rawList;
//       return Arrays.asList(dvs);
//     } else if (rawList instanceof PayloadPerDevice) {
//       list.add((PayloadPerDevice) rawList);
//     } else {
//       throw new IllegalArgumentException("PayloadPerDevice list type not supported. Supported types are: PayloadPerDevice[], List<PayloadPerDevice> and PayloadPerDevice");
//     }
//     return list;
//   }
// }
// 
// Path: src/main/java/javapns/devices/exceptions/InvalidDeviceTokenFormatException.java
// public class InvalidDeviceTokenFormatException extends Exception {
// 
//   private static final long serialVersionUID = -8571997399252125457L;
// 
//   public InvalidDeviceTokenFormatException(final String message) {
//     super(message);
//   }
// 
//   public InvalidDeviceTokenFormatException(final String token, final String problem) {
//     super(String.format("Device token cannot be parsed, most likely because it contains invalid hexadecimal characters: %s in %s", problem, token));
//   }
// 
// }


// Path: src/main/java/javapns/notification/transmission/NotificationThreads.java
import javapns.devices.Device;
import javapns.devices.Devices;
import javapns.devices.exceptions.InvalidDeviceTokenFormatException;
import javapns.notification.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.stream.Collectors;

package javapns.notification.transmission;



/**
 * <h1>Pushes a payload to a large number of devices using multiple threads</h1>
 * <p>
 * <p>The list of devices is spread evenly into multiple {@link javapns.notification.transmission.NotificationThread}s.</p>
 * <p>
 * <p>Usage: once a NotificationThreads is created, invoke {@code start()} to start all {@link javapns.notification.transmission.NotificationThread} threads.</p>
 * <p>You can provide a {@link javapns.notification.transmission.NotificationProgressListener} to receive events about the work being done.</p>
 *
 * @author Sylvain Pedneault
 * @see NotificationThread.MODE
 * @see NotificationThread
 */
public class NotificationThreads extends ThreadGroup implements PushQueue {
  private static final long DEFAULT_DELAY_BETWEEN_THREADS = 500; // the number of milliseconds to wait between each thread startup
  private static final String JAVAPNS_NOTIFICATION_THREADS = "javapns notification threads (";
  private static final String THREADS = " threads)";

  private final Object finishPoint = new Object();

  private List<NotificationThread> threads = new ArrayList<>();
  private NotificationProgressListener listener;

  private boolean started = false;
  private int threadsRunning = 0;
  private int nextThread = 0;
  private long delayBetweenThreads = DEFAULT_DELAY_BETWEEN_THREADS;

  /**
   * Create the specified number of notification threads and spread the devices evenly between the threads.
   *
   * @param server          the server to push to
   * @param payload         the payload to push
   * @param devices         a very large list of devices
   * @param numberOfThreads the number of threads to create to share the work
   */
  public NotificationThreads(final AppleNotificationServer server, final Payload payload, final List<Device> devices, final int numberOfThreads) {