content: package im.actor.runtime.promise;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.Comparator;
  import java.util.List;
  
  import im.actor.runtime.Crypto;
  import im.actor.runtime.function.Function;
  import im.actor.runtime.function.ListFunction;
  import im.actor.runtime.function.Predicate;
  import im.actor.runtime.function.Predicates;
  
  /**
   * Array of Promises. Allows you to invoke map, flatMap and other useful methods
   * for manipulating data.
   *
   * @param <T> type of array
   */
  public class PromisesArray<T> {
  
      /**
       * Create PromisesArray from collection
       *
       * @param collection Source collection
       * @param <T>        type of array
       * @return array
       */
      @SuppressWarnings("unchecked")
      public static <T> PromisesArray<T> of(Collection<T> collection) {
          final ArrayList<Promise<T>> res = new ArrayList<>();
          for (T t : collection) {
              res.add(Promise.success(t));
          }
          final Promise[] promises = res.toArray(new Promise[res.size()]);
          return new PromisesArray<>((PromiseFunc<Promise<T>[]>) executor -> {
              executor.result(promises);
          });
      }
  
      /**
       * Create PromisesArray from values
       *
       * @param items elements
       * @param <T>   type of array
       * @return array
       */
      @SafeVarargs
      public static <T> PromisesArray<T> of(T... items) {
          ArrayList<Promise<T>> res = new ArrayList<>();
          for (T t : items) {
              res.add(Promise.success(t));
          }
          final Promise[] promises = res.toArray(new Promise[res.size()]);
          return new PromisesArray<>((PromiseFunc<Promise<T>[]>) executor -> {
              executor.result(promises);
          });
      }
  
      /**
       * Create PromisesArray from multiple Promise
       *
       * @param items promises
       * @param <T>   type of array
       * @return array
       */
      @SafeVarargs
      public static <T> PromisesArray<T> ofPromises(Promise<T>... items) {
          ArrayList<Promise<T>> res = new ArrayList<>();
          Collections.addAll(res, items);
          final Promise[] promises = res.toArray(new Promise[res.size()]);
          return new PromisesArray<>((PromiseFunc<Promise<T>[]>) executor -> {
              executor.result(promises);
          });
      }
  
      public static <T> PromisesArray<T> ofPromises(Collection<Promise<T>> items) {
          ArrayList<Promise<T>> res = new ArrayList<>(items);
          // Collections.addAll(res, items);
          final Promise[] promises = res.toArray(new Promise[res.size()]);
          return new PromisesArray<>((PromiseFunc<Promise<T>[]>) executor -> {
              executor.result(promises);
          });
      }
  
      //
      // Constructors and methods
      //
  
      private Promise<Promise<T>[]> promises;
  
      private PromisesArray(Promise<Promise<T>[]> promises) {
          this.promises = promises;
      }
  
      private PromisesArray(PromiseFunc<Promise<T>[]> executor) {
          this(new Promise<>(executor));
      }
  
      /**
       * Map promises results to new promises
       *
       * @param fun mapping function
       * @param <R> type of result promises
       * @return PromisesArray
       */
      public <R> PromisesArray<R> map(final Function<T, Promise<R>> fun) {
          return mapSourcePromises(srcPromise -> new Promise<R>(resolver -> {
              srcPromise.then(t -> {
                  Promise<R> mapped = fun.apply(t);
                  mapped.then(t2 -> resolver.result(t2));
                  mapped.failure(e -> resolver.error(e));
              });
              srcPromise.failure(e -> resolver.error(e));
          }));
      }
  
      public <R> PromisesArray<R> mapOptional(final Function<T, Promise<R>> fun) {
          return map(fun)
                  .ignoreFailed()
                  .filterNull();
      }
  
      public PromisesArray<T> ignoreFailed() {
          return mapSourcePromises(tPromise -> new Promise<T>(resolver -> {
              tPromise.then(t -> resolver.result(t));
              tPromise.failure(e -> resolver.result(null));
          }));
      }
  
      public PromisesArray<T> filterNull() {
          return filter(Predicates.NOT_NULL);
      }
  
      private <R> PromisesArray<R> mapSourcePromises(final Function<Promise<T>, Promise<R>> fun) {
  
          return new PromisesArray<R>(executor -> {
  
              //
              // Handling source results
              //
  
              promises.then(sourcePromises -> {
  
                  //
                  // Building mapped promises
                  //
  
                  final Promise<R>[] mappedPromises = new Promise[sourcePromises.length];
  
                  for (int i = 0; i < mappedPromises.length; i++) {
  
                      mappedPromises[i] = fun.apply(sourcePromises[i]);
                  }
  
                  //
                  // Returning mapped promises
                  //
  
                  executor.result(mappedPromises);
              });
  
              //
              // Handling failure
              //
  
              promises.failure(e -> executor.error(e));
          });
      }
  
      public PromisesArray<T> filter(final Predicate<T> predicate) {
          return flatMap(t -> {
              if (predicate.apply(t)) {
                  return (T[]) new Object[]{t};
              }
              return (T[]) new Object[0];
          });
      }
  
      public PromisesArray<T> sort(final Comparator<T> comparator) {
          return flatMapAll(ts -> {
              T[] res = (T[]) new Object[ts.length];
              System.arraycopy(ts, 0, res, 0, ts.length);
              Arrays.sort(res, comparator);
              return res;
          });
      }
  
      public PromisesArray<T> first(final int count) {
          return flatMapAll(ts -> {
              int len = Math.min(count, ts.length);
              T[] res = (T[]) new Object[len];
              System.arraycopy(ts, 0, res, 0, len);
              return res;
          });
      }
  
      public Promise<T> first() {
          return first(1)
                  .zip()
                  .map(src -> {
                      if (src.size() == 0) {
                          throw new RuntimeException("Array is empty (first)");
                      }
                      return src.get(0);
                  });
      }
  
      public Promise<T> random() {
          return flatMapAll(ts -> {
              if (ts.length == 0) {
                  throw new RuntimeException("Array is empty");
              }
              return (T[]) new Object[]{ts[Crypto.randomInt(ts.length)]};
          }).first();
      }
  
      public <R> PromisesArray<R> flatMapAll(final Function<T[], R[]> fuc) {
          return new PromisesArray<R>(new Promise<>((PromiseFunc<Promise<R>[]>) resolver -> {
  
              //
              // Handling source results
              //
  
              promises.then(sourcePromises -> {
                  final Object[] res = new Object[sourcePromises.length];
                  final Boolean[] ended = new Boolean[sourcePromises.length];
                  for (int i = 0; i < sourcePromises.length; i++) {
                      final int finalI = i;
                      sourcePromises[i].then(t -> {
                          res[finalI] = t;
                          ended[finalI] = true;
  
                          for (int i1 = 0; i1 < sourcePromises.length; i1++) {
                              if (ended[i1] == null || !ended[i1]) {
                                  return;
                              }
                          }
  
                          R[] resMap = fuc.apply((T[]) res);
  
  
                          ArrayList<Promise<R>> resultList = new ArrayList<>();
                          for (R r : resMap) {
                              resultList.add(Promise.success(r));
                          }
  
                          resolver.result(resultList.toArray(new Promise[0]));
                      });
                      sourcePromises[i].failure(e -> resolver.error(e));
                  }
                  if (sourcePromises.length == 0) {
                      resolver.result(new Promise[0]);
                  }
              });
  
              //
              // Handling failure
              //
  
              promises.failure(e -> resolver.error(e));
          }));
      }
  
      public <R> PromisesArray<R> flatMap(final Function<T, R[]> map) {
          return new PromisesArray<R>(new Promise<>((PromiseFunc<Promise<R>[]>) resolver -> {
  
              //
              // Handling source results
              //
  
              promises.then(sourcePromises -> {
                  final Object[][] res = new Object[sourcePromises.length][];
                  final Boolean[] ended = new Boolean[sourcePromises.length];
  
                  for (int i = 0; i < sourcePromises.length; i++) {
                      final int finalI = i;
                      sourcePromises[i].then(t -> {
  
                          res[finalI] = map.apply(t);
                          ended[finalI] = true;
  
                          for (int i1 = 0; i1 < sourcePromises.length; i1++) {
                              if (ended[i1] == null || !ended[i1]) {
                                  return;
                              }
                          }
  
                          ArrayList<Promise<R>> resultList = new ArrayList<>();
                          for (int i2 = 0; i2 < sourcePromises.length; i2++) {
                              for (int j = 0; j < res[i2].length; j++) {
                                  resultList.add(Promise.success((R) res[i2][j]));
                              }
                          }
  
                          resolver.result(resultList.toArray(new Promise[0]));
                      });
                      sourcePromises[i].failure(e -> resolver.error(e));
                  }
  
                  if (sourcePromises.length == 0) {
                      resolver.result(new Promise[0]);
                  }
              });
  
              //
              // Handling failure
              //
  
              promises.failure(e -> resolver.error(e));
          }));
      }
  
      /**
       * Zip array to single promise
       *
       * @param fuc zipping function
       * @param <R> type of result
       * @return promise
       */
      public <R> Promise<R> zipPromise(final ListFunction<T, Promise<R>> fuc) {
          return new Promise<>(resolver -> {
              promises.then(promises1 -> {
                  final ArrayList<T> res = new ArrayList<T>();
                  for (int i = 0; i < promises1.length; i++) {
                      res.add(null);
                  }
                  final Boolean[] ended = new Boolean[promises1.length];
  
                  for (int i = 0; i < promises1.length; i++) {
                      final int finalI = i;
                      promises1[i].then(t -> {
                          res.set(finalI, t);
                          ended[finalI] = true;
  
                          for (int i1 = 0; i1 < promises1.length; i1++) {
                              if (ended[i1] == null || !ended[i1]) {
                                  return;
                              }
                          }
  
                          fuc.apply(res)
                                  .pipeTo(resolver);
                      });
                      promises1[i].failure(e -> resolver.error(e));
                  }
  
                  if (promises1.length == 0) {
                      fuc.apply(res)
                              .pipeTo(resolver);
                  }
              });
              promises.failure(e -> resolver.error(e));
          });
      }
  
      /**
       * Zipping array of promises to single promise of array
       *
       * @return promise
       */
      public Promise<List<T>> zip() {
          return zipPromise(t -> Promise.success(t));
      }
  }