content: package com.espressif.iot.esptouch.task;
  
  import android.content.Context;
  import android.os.Looper;
  import android.util.Log;
  
  import com.espressif.iot.esptouch.EsptouchResult;
  import com.espressif.iot.esptouch.IEsptouchListener;
  import com.espressif.iot.esptouch.IEsptouchResult;
  import com.espressif.iot.esptouch.IEsptouchTask;
  import com.espressif.iot.esptouch.protocol.EsptouchGenerator;
  import com.espressif.iot.esptouch.protocol.TouchData;
  import com.espressif.iot.esptouch.udp.UDPSocketClient;
  import com.espressif.iot.esptouch.udp.UDPSocketServer;
  import com.espressif.iot.esptouch.util.ByteUtil;
  import com.espressif.iot.esptouch.util.EspAES;
  import com.espressif.iot.esptouch.util.EspNetUtil;
  
  import java.net.InetAddress;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.concurrent.atomic.AtomicBoolean;
  
  public class __EsptouchTask implements __IEsptouchTask {
      /**
       * one indivisible data contain 3 9bits info
       */
      private static final int ONE_DATA_LEN = 3;
  
      private static final String TAG = "__EsptouchTask";
  
      private final UDPSocketClient mSocketClient;
      private final UDPSocketServer mSocketServer;
      private final byte[] mApSsid;
      private final byte[] mApPassword;
      private final byte[] mApBssid;
      private final boolean mIsSsidHidden;
      private final Context mContext;
      private volatile List<IEsptouchResult> mEsptouchResultList;
      private volatile boolean mIsSuc = false;
      private volatile boolean mIsInterrupt = false;
      private volatile boolean mIsExecuted = false;
      private AtomicBoolean mIsCancelled;
      private IEsptouchTaskParameter mParameter;
      private volatile Map<String, Integer> mBssidTaskSucCountMap;
      private IEsptouchListener mEsptouchListener;
      private Thread mTask;
  
      public __EsptouchTask(Context context, TouchData apSsid, TouchData apBssid, TouchData apPassword, EspAES espAES,
                            IEsptouchTaskParameter parameter, boolean isSsidHidden) {
          Log.i(TAG, "Welcome Esptouch " + IEsptouchTask.ESPTOUCH_VERSION);
          mContext = context;
          if (espAES == null) {
              mApSsid = apSsid.getData();
              mApPassword = apPassword.getData();
          } else {
              mApSsid = espAES.encrypt(apSsid.getData());
              mApPassword = espAES.encrypt(apPassword.getData());
          }
          mApBssid = apBssid.getData();
          mIsCancelled = new AtomicBoolean(false);
          mSocketClient = new UDPSocketClient();
          mParameter = parameter;
          mSocketServer = new UDPSocketServer(mParameter.getPortListening(),
                  mParameter.getWaitUdpTotalMillisecond(), context);
          mIsSsidHidden = isSsidHidden;
          mEsptouchResultList = new ArrayList<>();
          mBssidTaskSucCountMap = new HashMap<>();
      }
  
      private void __putEsptouchResult(boolean isSuc, String bssid, InetAddress inetAddress) {
          synchronized (mEsptouchResultList) {
              // check whether the result receive enough UDP response
              boolean isTaskSucCountEnough = false;
              Integer count = mBssidTaskSucCountMap.get(bssid);
              if (count == null) {
                  count = 0;
              }
              ++count;
              if (__IEsptouchTask.DEBUG) {
                  Log.d(TAG, "__putEsptouchResult(): count = " + count);
              }
              mBssidTaskSucCountMap.put(bssid, count);
              isTaskSucCountEnough = count >= mParameter
                      .getThresholdSucBroadcastCount();
              if (!isTaskSucCountEnough) {
                  if (__IEsptouchTask.DEBUG) {
                      Log.d(TAG, "__putEsptouchResult(): count = " + count
                              + ", isn't enough");
                  }
                  return;
              }
              // check whether the result is in the mEsptouchResultList already
              boolean isExist = false;
              for (IEsptouchResult esptouchResultInList : mEsptouchResultList) {
                  if (esptouchResultInList.getBssid().equals(bssid)) {
                      isExist = true;
                      break;
                  }
              }
              // only add the result who isn't in the mEsptouchResultList
              if (!isExist) {
                  if (__IEsptouchTask.DEBUG) {
                      Log.d(TAG, "__putEsptouchResult(): put one more result");
                  }
                  final IEsptouchResult esptouchResult = new EsptouchResult(isSuc,
                          bssid, inetAddress);
                  mEsptouchResultList.add(esptouchResult);
                  if (mEsptouchListener != null) {
                      mEsptouchListener.onEsptouchResultAdded(esptouchResult);
                  }
              }
          }
      }
  
      private List<IEsptouchResult> __getEsptouchResultList() {
          synchronized (mEsptouchResultList) {
              if (mEsptouchResultList.isEmpty()) {
                  EsptouchResult esptouchResultFail = new EsptouchResult(false,
                          null, null);
                  esptouchResultFail.setIsCancelled(mIsCancelled.get());
                  mEsptouchResultList.add(esptouchResultFail);
              }
  
              return mEsptouchResultList;
          }
      }
  
      private synchronized void __interrupt() {
          if (!mIsInterrupt) {
              mIsInterrupt = true;
              mSocketClient.interrupt();
              mSocketServer.interrupt();
              // interrupt the current Thread which is used to wait for udp response
              if (mTask != null) {
                  mTask.interrupt();
                  mTask = null;
              }
          }
      }
  
      @Override
      public void interrupt() {
          if (__IEsptouchTask.DEBUG) {
              Log.d(TAG, "interrupt()");
          }
          mIsCancelled.set(true);
          __interrupt();
      }
  
      private void __listenAsyn(final int expectDataLen) {
          mTask = new Thread() {
              public void run() {
                  if (__IEsptouchTask.DEBUG) {
                      Log.d(TAG, "__listenAsyn() start");
                  }
                  long startTimestamp = System.currentTimeMillis();
  //                byte[] apSsidAndPassword = ByteUtil.getBytesByString(mApSsid
  //                        + mApPassword);
                  byte expectOneByte = (byte) (mApSsid.length + mApPassword.length + 9);
                  if (__IEsptouchTask.DEBUG) {
                      Log.i(TAG, "expectOneByte: " + (0 + expectOneByte));
                  }
                  byte receiveOneByte = -1;
                  byte[] receiveBytes = null;
                  while (mEsptouchResultList.size() < mParameter
                          .getExpectTaskResultCount() && !mIsInterrupt) {
                      receiveBytes = mSocketServer
                              .receiveSpecLenBytes(expectDataLen);
                      if (receiveBytes != null) {
                          receiveOneByte = receiveBytes[0];
                      } else {
                          receiveOneByte = -1;
                      }
                      if (receiveOneByte == expectOneByte) {
                          if (__IEsptouchTask.DEBUG) {
                              Log.i(TAG, "receive correct broadcast");
                          }
                          // change the socket's timeout
                          long consume = System.currentTimeMillis()
                                  - startTimestamp;
                          int timeout = (int) (mParameter
                                  .getWaitUdpTotalMillisecond() - consume);
                          if (timeout < 0) {
                              if (__IEsptouchTask.DEBUG) {
                                  Log.i(TAG, "esptouch timeout");
                              }
                              break;
                          } else {
                              if (__IEsptouchTask.DEBUG) {
                                  Log.i(TAG, "mSocketServer's new timeout is "
                                          + timeout + " milliseconds");
                              }
                              mSocketServer.setSoTimeout(timeout);
                              if (__IEsptouchTask.DEBUG) {
                                  Log.i(TAG, "receive correct broadcast");
                              }
                              if (receiveBytes != null) {
                                  String bssid = ByteUtil.parseBssid(
                                          receiveBytes,
                                          mParameter.getEsptouchResultOneLen(),
                                          mParameter.getEsptouchResultMacLen());
                                  InetAddress inetAddress = EspNetUtil
                                          .parseInetAddr(
                                                  receiveBytes,
                                                  mParameter
                                                          .getEsptouchResultOneLen()
                                                          + mParameter
                                                          .getEsptouchResultMacLen(),
                                                  mParameter
                                                          .getEsptouchResultIpLen());
                                  __putEsptouchResult(true, bssid, inetAddress);
                              }
                          }
                      } else {
                          if (__IEsptouchTask.DEBUG) {
                              Log.i(TAG, "receive rubbish message, just ignore");
                          }
                      }
                  }
                  mIsSuc = mEsptouchResultList.size() >= mParameter
                          .getExpectTaskResultCount();
                  __EsptouchTask.this.__interrupt();
                  if (__IEsptouchTask.DEBUG) {
                      Log.d(TAG, "__listenAsyn() finish");
                  }
              }
          };
          mTask.start();
      }
  
      private boolean __execute(IEsptouchGenerator generator) {
  
          long startTime = System.currentTimeMillis();
          long currentTime = startTime;
          long lastTime = currentTime - mParameter.getTimeoutTotalCodeMillisecond();
  
          byte[][] gcBytes2 = generator.getGCBytes2();
          byte[][] dcBytes2 = generator.getDCBytes2();
  
          int index = 0;
          while (!mIsInterrupt) {
              if (currentTime - lastTime >= mParameter.getTimeoutTotalCodeMillisecond()) {
                  if (__IEsptouchTask.DEBUG) {
                      Log.d(TAG, "send gc code ");
                  }
                  // send guide code
                  while (!mIsInterrupt
                          && System.currentTimeMillis() - currentTime < mParameter
                          .getTimeoutGuideCodeMillisecond()) {
                      mSocketClient.sendData(gcBytes2,
                              mParameter.getTargetHostname(),
                              mParameter.getTargetPort(),
                              mParameter.getIntervalGuideCodeMillisecond());
                      // check whether the udp is send enough time
                      if (System.currentTimeMillis() - startTime > mParameter.getWaitUdpSendingMillisecond()) {
                          break;
                      }
                  }
                  lastTime = currentTime;
              } else {
                  mSocketClient.sendData(dcBytes2, index, ONE_DATA_LEN,
                          mParameter.getTargetHostname(),
                          mParameter.getTargetPort(),
                          mParameter.getIntervalDataCodeMillisecond());
                  index = (index + ONE_DATA_LEN) % dcBytes2.length;
              }
              currentTime = System.currentTimeMillis();
              // check whether the udp is send enough time
              if (currentTime - startTime > mParameter.getWaitUdpSendingMillisecond()) {
                  break;
              }
          }
  
          return mIsSuc;
      }
  
      private void __checkTaskValid() {
          // !!!NOTE: the esptouch task could be executed only once
          if (this.mIsExecuted) {
              throw new IllegalStateException(
                      "the Esptouch task could be executed only once");
          }
          this.mIsExecuted = true;
      }
  
      @Override
      public IEsptouchResult executeForResult() throws RuntimeException {
          return executeForResults(1).get(0);
      }
  
      @Override
      public boolean isCancelled() {
          return this.mIsCancelled.get();
      }
  
      @Override
      public List<IEsptouchResult> executeForResults(int expectTaskResultCount)
              throws RuntimeException {
          __checkTaskValid();
  
          mParameter.setExpectTaskResultCount(expectTaskResultCount);
  
          if (__IEsptouchTask.DEBUG) {
              Log.d(TAG, "execute()");
          }
          if (Looper.myLooper() == Looper.getMainLooper()) {
              throw new RuntimeException(
                      "Don't call the esptouch Task at Main(UI) thread directly.");
          }
          InetAddress localInetAddress = EspNetUtil.getLocalInetAddress(mContext);
          if (__IEsptouchTask.DEBUG) {
              Log.i(TAG, "localInetAddress: " + localInetAddress);
          }
          // generator the esptouch byte[][] to be transformed, which will cost
          // some time(maybe a bit much)
          IEsptouchGenerator generator = new EsptouchGenerator(mApSsid, mApBssid,
                  mApPassword, localInetAddress, mIsSsidHidden);
          // listen the esptouch result asyn
          __listenAsyn(mParameter.getEsptouchResultTotalLen());
          boolean isSuc = false;
          for (int i = 0; i < mParameter.getTotalRepeatTime(); i++) {
              isSuc = __execute(generator);
              if (isSuc) {
                  return __getEsptouchResultList();
              }
          }
  
          if (!mIsInterrupt) {
              // wait the udp response without sending udp broadcast
              try {
                  Thread.sleep(mParameter.getWaitUdpReceivingMillisecond());
              } catch (InterruptedException e) {
                  // receive the udp broadcast or the user interrupt the task
                  if (this.mIsSuc) {
                      return __getEsptouchResultList();
                  } else {
                      this.__interrupt();
                      return __getEsptouchResultList();
                  }
              }
              this.__interrupt();
          }
  
          return __getEsptouchResultList();
      }
  
      @Override
      public void setEsptouchListener(IEsptouchListener esptouchListener) {
          mEsptouchListener = esptouchListener;
      }
  
  }
  