/**
  * @param send_buf
  * @param length
  * @author Conor Send the process through the stream
  */
 private void sendProcess(char[] send_buf, int length) {
   if (mSocket == null) {
     Log.e("streamdebug", "socket was null, could not send data.");
     return;
   }
   Log.d(
       "streamdebug",
       String.format("Sending :: Send_buf: %S - Length: %S", ((int) (send_buf[1])), length));
   // uncomment
   try {
     mSocket.getOut().write(send_buf, 0, length);
     mSocket.getOut().flush();
     //            Log.d("streamdebug", String.format("Successfully sent :: Send_buf: %S - Length:
     // %S", ((int) (send_buf[1])), length));
   } catch (Exception e) {
     Log.e("streamerror", "error sending");
     lostConnection();
     e.printStackTrace();
   }
 }
 /** closes connection when we are done streaming */
 private void closeSocket() {
   if (mSocket != null) {
     mSocket.close();
   }
 }
  @Override
  /**
   * Begins streaming process with mobile device and monitors for errors, e.g. connection time outs
   */
  public void run() {
    android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);

    Log.d("streamdebug", "trying to create socket buffer");
    Thread socketThread =
        new Thread() {
          public void run() {
            try {
              mSocket = new SocketBuffer(mAddress, mPort);
            } catch (Exception e) {
              Log.d("streamdebug", e.getMessage());
              mSocket = null;
            }
          }
        };

    socketThread.start();

    try {
      socketThread.join(5000); // socket timeout in construction
      sleep(30);
    } catch (Exception e) {
      e.printStackTrace();
    }

    if (mSocket == null) {
      lostConnection("Couldn't connect.");
      return;
    }

    mLastReceiveTime = System.currentTimeMillis();

    // Send VERSION, RESIZE, KEY
    char[] first_buf = new char[Constants.CL_FIRST_MSG_SIZE];
    first_buf[0] = Constants.MAGIC;
    first_buf[1] = Constants.VERSION;

    // Key
    if (mKey != null && mKey.length() == Constants.KEY_LENGTH) {
      first_buf[2] = mKey.charAt(0);
      first_buf[3] = mKey.charAt(1);
      first_buf[4] = mKey.charAt(2);
      first_buf[5] = mKey.charAt(3);
      first_buf[6] = mKey.charAt(4);
    } else {
      first_buf[2] = 0;
      first_buf[3] = 0;
      first_buf[4] = 0;
      first_buf[5] = 0;
      first_buf[6] = 0;
    }

    // Resolution
    first_buf[7] = (char) (Constants.REAL_IMG_WIDTH / 128);
    first_buf[8] = (char) (Constants.REAL_IMG_WIDTH % 128);
    first_buf[9] = (char) (Constants.REAL_IMG_HEIGHT / 128);
    first_buf[10] = (char) (Constants.REAL_IMG_HEIGHT % 128);
    if (mLowRes) {
      first_buf[11] = 1;
    } else {
      first_buf[11] = 0;
    }
    sendProcess(first_buf, Constants.CL_FIRST_MSG_SIZE);

    mLastReceiveTime = System.currentTimeMillis(); // last time a complete frame has been received
    long lastPingTime = System.currentTimeMillis();
    boolean isDiff = false; // keeps track of whether the bitmap has changed since the last draw
    while (mRun) {
      try {
        /* We're still listening; notify server */
        if (System.currentTimeMillis() - lastPingTime > 1000) {
          sendProcess(mRefrBuf, Constants.CL_MSG_SIZE);
          lastPingTime = System.currentTimeMillis();
        }
        while (mSocket.getIn().ready()) {
          int read =
              mSocket.getIn().read(mRecvBuf, mRecvBufOffset, mRecvBufMsgLen - mRecvBufOffset);
          mRecvBufOffset += read;

          if (mRecvBufOffset == Constants.SE_MSG_HDR) {
            char magic = mRecvBuf[0];
            if (magic != Constants.MAGIC) {
              Log.e("streamerror", "Bad magic number");
              setRunning(false);
              break;
            }
            int additional = mRecvBuf[2] * 128 + mRecvBuf[3];
            mRecvBufMsgLen += (additional - Constants.SE_MSG_HDR);
          }

          if (mRecvBufOffset == mRecvBufMsgLen) {
            int this_msg_len = mRecvBufMsgLen;
            mRecvBufOffset = 0;
            mRecvBufMsgLen = Constants.SE_MSG_HDR;
            char type = mRecvBuf[1];

            // Commented out to prevent crazy verbose logging
            // Log.d("stream", "GOT TYPE: " + (int)(type));

            if (type == Constants.SEEV_FLUSH) {
              mLastReceiveTime = System.currentTimeMillis();
              if (isDiff) {
                Canvas canvas = mSurfaceHolder.lockCanvas();
                synchronized (mSurfaceHolder) {
                  doDraw(canvas);
                }
                mSurfaceHolder.unlockCanvasAndPost(canvas);
                isDiff = false;
              }
            } else if (type == Constants.SEEV_TILE
                || type == Constants.SEEV_SOLID_TILE
                || type == Constants.SEEV_RLE24_TILE
                || type == Constants.SEEV_RLE16_TILE
                || type == Constants.SEEV_RLE8_TILE) {
              int xx = mRecvBuf[4] * 128 + mRecvBuf[5];
              int yy = mRecvBuf[6] * 128 + mRecvBuf[7];
              int sz = Constants.TILE_SIZE;

              if (0 <= xx
                  && xx + sz <= Constants.CUR_IMG_WIDTH
                  && 0 <= yy
                  && yy + sz <= Constants.CUR_IMG_HEIGHT) {
                if (type == Constants.SEEV_TILE) {
                  for (int ii = 0; ii < sz; ++ii) {
                    for (int jj = 0; jj < sz; ++jj) {
                      mColorArray[ii + jj * sz] =
                          Color.rgb(
                              mRecvBuf[8 + 3 * (sz - jj - 1 + ii * sz) + 0] * 2,
                              mRecvBuf[8 + 3 * (sz - jj - 1 + ii * sz) + 1] * 2,
                              mRecvBuf[8 + 3 * (sz - jj - 1 + ii * sz) + 2] * 2);
                    }
                  }
                } else if (type == Constants.SEEV_SOLID_TILE) {
                  Arrays.fill(
                      mColorArray, Color.rgb(mRecvBuf[8] * 2, mRecvBuf[9] * 2, mRecvBuf[10] * 2));
                } else if (type == Constants.SEEV_RLE24_TILE
                    || type == Constants.SEEV_RLE16_TILE
                    || type == Constants.SEEV_RLE8_TILE) {
                  int rxx = 0;
                  int ryy = Constants.TILE_SIZE - 1;

                  int stride = this_msg_len;
                  if (type == Constants.SEEV_RLE24_TILE) {
                    stride = 4;
                  } else if (type == Constants.SEEV_RLE16_TILE) {
                    stride = 3;
                  } else if (type == Constants.SEEV_RLE8_TILE) {
                    stride = 2;
                  }

                  for (int ii = 8; ii < this_msg_len; ii += stride) {
                    int run = mRecvBuf[ii + 0];
                    int rr;
                    int gg;
                    int bb;
                    if (type == Constants.SEEV_RLE24_TILE) {
                      rr = mRecvBuf[ii + 1] * 2;
                      gg = mRecvBuf[ii + 2] * 2;
                      bb = mRecvBuf[ii + 3] * 2;
                    } else if (type == Constants.SEEV_RLE16_TILE) {
                      int clr0 = mRecvBuf[ii + 1];
                      int clr1 = mRecvBuf[ii + 2];
                      rr = ((clr0 & 0x007C) >> 2) * 8;
                      gg = (((clr0 & 0x0003) << 3) | ((clr1 & 0x0070) >> 4)) * 8;
                      bb = ((clr1 & 0x000F) >> 0) * 16;
                    } else {
                      int clr = mRecvBuf[ii + 1];
                      rr = ((clr & 0x0060) >> 5) * 64;
                      gg = ((clr & 0x0018) >> 3) * 64;
                      bb = ((clr & 0x0007) >> 0) * 32;
                    }

                    for (int jj = 0; jj < run; ++jj) {
                      mColorArray[rxx + ryy * sz] = Color.rgb(rr, gg, bb);
                      ryy -= 1;
                      if (ryy < 0) {
                        ryy = Constants.TILE_SIZE - 1;
                        rxx += 1;
                      }
                    }
                  }
                }
                int inv_yy = Constants.CUR_IMG_HEIGHT - yy - sz;
                mImage.setPixels(mColorArray, 0, sz, xx, inv_yy, sz, sz);
                isDiff = true; // bitmap has been changed
              }
            } else if (type == Constants.SEEV_TERMINATE) {
              int terminateType = mRecvBuf[4];
              if (terminateType == 1) {
                lostConnection("Client and server versions don't match.");
              } else if (terminateType == 2) {
                lostConnection("Key is incorrect.");
              } else {
                lostConnection("Server closed connection.");
              }
              setRunning(false);
              closeSocket();
              return;
            } else {
              Log.e("streamerror", "Bad server event: " + (int) type);
            }
          }
        }
      } catch (Exception e) {
        Log.e("streamerror", "Exception: " + e.getMessage() + e.toString());
        lostConnection();
      }

      if (System.currentTimeMillis() - mLastReceiveTime > 10000) {
        lostConnection("Connection timed out.");
      }
    }
    if (mSocket != null) {
      Log.d("streamdebug", "CLOSING");
      mSendBuf[1] = Constants.CLEV_TERMINATE;
      sendProcess(mSendBuf, Constants.CL_MSG_SIZE);
      mSocket.close();
    }
  }