package example;

import io.reactivex.Observable;
import io.reactivex.subjects.PublishSubject;
import io.reactivex.subjects.Subject;
import org.tio.utils.hutool.FileUtil;
import org.tio.websocket.client.WebSocket;
import org.tio.websocket.client.WsClient;
import org.tio.websocket.client.config.WsClientConfig;
import org.tio.websocket.common.WsPacket;

import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.LongStream;

class EchoClient {

  static class SentItem {
    boolean recv;
    long sendAt;
    long recvAt;

    public SentItem(boolean recv, long sendAt, long recvAt) {
      this.recv = recv;
      this.sendAt = sendAt;
      this.recvAt = recvAt;
    }
  }

  // 基准测试？
  public static void main5(String[] args) throws Exception {
    int totalWsCount = 10000;
    int msgCount = 100;

    List<WsClient> list = new ArrayList<>();
    for (int i = 0; i < totalWsCount; i++) {
      list.add(WsClient.create("ws://127.0.0.1:7777"));
    }

    ConcurrentHashMap<Integer, SentItem> map = new ConcurrentHashMap<>();

    Subject<Integer> recv = PublishSubject.<Integer>create().toSerialized();

    recv.buffer(totalWsCount * msgCount)
        .take(1)
        .subscribe(
            x -> {
              System.out.println("all ok");
              Callable<LongStream> builder =
                  () -> map.values().stream().mapToLong(it -> it.recvAt - it.sendAt);
              System.out.printf("min %dms\n", builder.call().min().getAsLong());
              System.out.printf("max %dms\n", builder.call().max().getAsLong());
              System.out.printf("avg %.2fms\n", builder.call().average().getAsDouble());
              long[] array = builder.call().sorted().toArray();
              System.out.printf("mid0.25 %dms\n", array[(int) (0.25*totalWsCount * msgCount)]);
              System.out.printf("mid0.5 %dms\n", array[(int) (0.5*totalWsCount * msgCount)]);
              System.out.printf("mid0.75 %dms\n", array[(int) (0.75*totalWsCount * msgCount)]);
            });

    AtomicInteger index = new AtomicInteger(0);
    list.forEach(
        c -> {
          try {
            int currIndex = index.getAndIncrement();
            WebSocket ws = c.connect();
            ws.addOnMessage(
                e -> {
                  String text = e.data.getWsBodyText();
                  int i = Integer.parseInt(text);
                  System.out.println(currIndex + " recv " + text);
                  map.compute(
                      i,
                      (k, v) -> {
                        v.recv = true;
                        v.recvAt = System.currentTimeMillis();
                        return v;
                      });
                  recv.onNext(i);
                });
            ws.getMessageStream()
                .buffer(msgCount)
                .take(1)
                .subscribe(
                    x -> {
                      ws.close();
                      System.out.println("close " + currIndex);
                    });
          } catch (Exception e) {
            e.printStackTrace();
          }
        });

    AtomicInteger id = new AtomicInteger(1);

    IntStream.range(0, msgCount)
        .parallel()
        .forEach(
            i -> {
              list.parallelStream()
                  .map(WsClient::getWs)
                  .forEach(
                      ws -> {
                        int curr = id.getAndIncrement();
                        map.put(curr, new SentItem(false, System.currentTimeMillis(), 0));
                        ws.send(curr + "");
                        System.out.println(i + " send " + curr);
                        try {
                          Thread.sleep(10);
                        } catch (InterruptedException e) {
                        }
                      });
              try {
                Thread.sleep(10);
              } catch (InterruptedException e) {
              }
            });
  }

  public static void main4(String[] args) throws Exception {
    WsClient client =
        WsClient.create(
            "ws://127.0.0.1:7777?id=77",
            new WsClientConfig(
                e -> {
                  System.out.println("on open");
                },
                e -> {
                  System.out.println(String.format("on message: %s", e.data));
                },
                e -> {
                  System.out.println(
                      String.format("on close: %d %s %s", e.code, e.reason, e.wasClean));
                },
                e -> {
                  System.out.println(String.format("on error: %s", e.msg));
                },
                e -> {
                  e.printStackTrace();
                }));
    WebSocket ws = client.connect();
    ws.addOnOpen(
        e -> {
          Observable.timer(5, TimeUnit.SECONDS)
              .subscribe(
                  (i) -> {
                    System.out.println("close");
                    ws.close(1001, "user has leave");
                  });
        });
    ws.addOnClose(
        e -> {
          String s = e.code + e.reason;
        });
  }

  // echo 大 byte array
  public static void main(String[] args) throws Exception {
    int len = 1024 * 899 * 15;
    ByteBuffer buf = ByteBuffer.allocate(len);
    for (int i = 0; i < len; i++) {
      buf.put((byte) (Byte.MAX_VALUE * Math.random()));
    }
    WsClient client =
        WsClient.create(
            "ws://127.0.0.1:7777?id=77",
            new WsClientConfig(
                null,
                e -> {
                  byte[] ret = e.data.getBody();
                  boolean equals = Arrays.equals(buf.array(), ret);
                  System.out.println(equals ? "ok" : "fail");
                },
                null,
                null,
                e -> {
                  e.printStackTrace();
                }));
    WebSocket ws = client.connect();
    ws.send(buf);
  }

  // echo，10次，917KB大文本，次次加一份，大文本测试
  public static void main2(String[] args) {
    try {

      String text = FileUtil.readString(FileUtil.file("E:\\镇魂.txt"));
      int size = 10;
      Map<Integer, String> sent = new ConcurrentHashMap<>();
      Map<Integer, String> recv = new ConcurrentHashMap<>();
      WsClient echo =
          WsClient.create(
              "ws://127.0.0.1:7777/echo",
              new WsClientConfig(
                  e -> {
                    System.out.println("emit open");
                  },
                  e -> {
                    WsPacket data = e.data;
                    int i = Integer.parseInt(data.getWsBodyText().split("/")[0]);
                    System.out.println("recv No." + i);
                    recv.put(i, data.getWsBodyText());
                    if (i == size - 1) {
                      boolean all = true;
                      for (int j = 0; j < size; j++) {
                        all = all && sent.get(j).equals(recv.get(j));
                      }
                      if (all) {
                        System.out.println("All sent success.");
                      } else {
                        System.out.println("All sent fail.");
                      }
                    }
                  },
                  e -> {
                    System.out.println(
                        String.format("emit close: %d, %s, %s", e.code, e.reason, e.wasClean));
                  },
                  e -> {
                    System.out.println(String.format("emit error: %s", e.msg));
                  },
                  Throwable::printStackTrace));
      WebSocket ws = echo.connect();

      for (int i = 0; i < size; i++) {
        StringBuilder sb = new StringBuilder(i + "/" + text);
        for (int j = 0; j < i; j++) {
          sb.append(text);
        }
        ws.send(sb.toString());
        sent.put(i, sb.toString());
        System.out.println("send No." + i);
      }

    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  // echo, 10_0000条短文本测试
  public static void main1(String[] args) throws Exception {
    Map<Integer, Boolean> sent = new ConcurrentHashMap<>();
    int total = 1000;

    Subject<Object> complete = PublishSubject.create().toSerialized();
    complete
        .buffer(total)
        .subscribe(
            x -> {
              Boolean all = sent.values().stream().reduce(true, (p, c) -> p && c);
              if (all) {
                System.out.println("All sent success! ");
              }
            });

    WsClient echo =
        WsClient.create(
            "wss://echo.websocket.org/?encoding=text",
            new WsClientConfig(
                e -> System.out.println("opened"),
                e -> {
                  WsPacket data = e.data;
                  int i = Integer.parseInt(data.getWsBodyText());
                  sent.put(i, true);
                  System.out.println("recv: " + i);
                  complete.onNext(i);
                },
                e -> System.out.printf("emit close: %d, %s, %s\n", e.code, e.reason, e.wasClean),
                e -> System.out.println(String.format("emit error: %s", e.msg)),
                Throwable::printStackTrace));

    WebSocket ws = echo.connect();

    for (int i = 0; i < total; i++) {
      ws.send("" + i);
      sent.put(i, false);
      System.out.println("sent: " + i);
    }
  }
}
