package com.netease.LDNetDiagnoService;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import android.content.Context;
import android.telephony.TelephonyManager;
import android.text.TextUtils;

import com.netease.LDNetDiagnoService.LDNetPing.LDNetPingListener;
import com.netease.LDNetDiagnoService.LDNetSocket.LDNetSocketListener;
import com.netease.LDNetDiagnoService.LDNetTraceRoute.LDNetTraceRouteListener;
import com.netease.LDNetDiagnoUtils.LDNetUtil;

/**
 * 网络诊断服务 通过对制定域名进行ping诊断和traceroute诊断收集诊断日志
 *
 * @author panghui
 *
 */
public class LDNetDiagnoService extends
    LDNetAsyncTaskEx<String, String, String> implements LDNetPingListener,
    LDNetTraceRouteListener, LDNetSocketListener {
  private String _appCode; // 客户端标记
  private String _appName;
  private String _appVersion;
  private String _UID; // 用户ID
  private String _deviceID; // 客户端机器ID,如果不传入会默认取API提供的机器ID
  private String _dormain; // 接口域名
  private String _carrierName;
  private String _ISOCountryCode;
  private String _MobileCountryCode;
  private String _MobileNetCode;

  private boolean _isNetConnected;// 当前是否联网
  private boolean _isDomainParseOk;// 域名解析是否成功
  private boolean _isSocketConnected;// conected是否成功
  private Context _context;
  private String _netType;
  private String _localIp;
  private String _gateWay;
  private String _dns1;
  private String _dns2;
  private InetAddress[] _remoteInet;
  private List<String> _remoteIpList;
  private final StringBuilder _logInfo = new StringBuilder(256);
  private LDNetSocket _netSocker;// 监控socket的连接时间
  private LDNetPing _netPinger; // 监控ping命令的执行时间
  private LDNetTraceRoute _traceRouter; // 监控ping模拟traceroute的执行过程
  private boolean _isRunning;

  private LDNetDiagnoListener _netDiagnolistener; // 将监控日志上报到前段页面
  private boolean _isUseJNICConn = false;
  private boolean _isUseJNICTrace = true;
  private TelephonyManager _telManager = null; // 用于获取网络基本信息

  public LDNetDiagnoService() {
    super();
  }

  /**
   * 初始化网络诊断服务
   *
   * @param theAppCode
   * @param theDeviceID
   * @param theUID
   * @param theDormain
   */
  public LDNetDiagnoService(Context context, String theAppCode,
      String theAppName, String theAppVersion, String theUID,
      String theDeviceID, String theDormain, String theCarrierName,
      String theISOCountryCode, String theMobileCountryCode,
      String theMobileNetCode, LDNetDiagnoListener theListener) {
    super();
    this._context = context;
    this._appCode = theAppCode;
    this._appName = theAppName;
    this._appVersion = theAppVersion;
    this._UID = theUID;
    this._deviceID = theDeviceID;
    this._dormain = theDormain;
    this._carrierName = theCarrierName;
    this._ISOCountryCode = theISOCountryCode;
    this._MobileCountryCode = theMobileCountryCode;
    this._MobileNetCode = theMobileNetCode;
    this._netDiagnolistener = theListener;
    //
    this._isRunning = false;
    _remoteIpList = new ArrayList<String>();
    _telManager = (TelephonyManager) context
        .getSystemService(Context.TELEPHONY_SERVICE);
    sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE,
        KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);

  }

  @Override
  protected String doInBackground(String... params) {
    if (this.isCancelled())
      return null;
    // TODO Auto-generated method stub
    return this.startNetDiagnosis();
  }

  @Override
  protected void onPostExecute(String result) {
    if (this.isCancelled())
      return;
    super.onPostExecute(result);
    // 线程执行结束
    recordStepInfo("\n网络诊断结束\n");
    this.stopNetDialogsis();
    if (_netDiagnolistener != null) {
      _netDiagnolistener.OnNetDiagnoFinished(_logInfo.toString());
    }
  }

  @Override
  protected void onProgressUpdate(String... values) {
    if (this.isCancelled())
      return;
    // TODO Auto-generated method stub
    super.onProgressUpdate(values);
    if (_netDiagnolistener != null) {
      _netDiagnolistener.OnNetDiagnoUpdated(values[0]);
    }
  }

  @Override
  protected void onCancelled() {
    this.stopNetDialogsis();
  }

  /**
   * 开始诊断网络
   */
  public String startNetDiagnosis() {
    if (TextUtils.isEmpty(this._dormain))
      return "";
    this._isRunning = true;
    this._logInfo.setLength(0);
    recordStepInfo("开始诊断...\n");
    recordCurrentAppVersion();
    recordLocalNetEnvironmentInfo();

    if (_isNetConnected) {
      // 获取运营商信息
      //recordStepInfo("\n开始获取运营商信息...");
      //String operatorInfo = requestOperatorInfo();
      //if (operatorInfo != null) {
        //recordStepInfo(operatorInfo);
      //}

      // TCP三次握手时间测试
      recordStepInfo("\n开始TCP连接测试...");
      _netSocker = LDNetSocket.getInstance();
      _netSocker._remoteInet = _remoteInet;
      _netSocker._remoteIpList = _remoteIpList;
      _netSocker.initListener(this);
      _netSocker.isCConn = this._isUseJNICConn;// 设置是否启用C进行connected
      _isSocketConnected = _netSocker.exec(_dormain);

      // 诊断ping信息, 同步过程

      if (!(_isNetConnected && _isDomainParseOk && _isSocketConnected)) {// 联网&&DNS解析成功&&connect测试成功
        recordStepInfo("\n开始ping...");
        _netPinger = new LDNetPing(this, 4);
        recordStepInfo("ping...127.0.0.1");
        _netPinger.exec("127.0.0.1", false);
        recordStepInfo("ping本机IP..." + _localIp);
        _netPinger.exec(_localIp, false);
        if (LDNetUtil.NETWORKTYPE_WIFI.equals(_netType)) {// 在wifi下ping网关
          recordStepInfo("ping本地网关..." + _gateWay);
          _netPinger.exec(_gateWay, false);
        }
        recordStepInfo("ping本地DNS1..." + _dns1);
        _netPinger.exec(_dns1, false);
        recordStepInfo("ping本地DNS2..." + _dns2);
        _netPinger.exec(_dns2, false);
      }
      
      if (_netPinger == null) {
        _netPinger = new LDNetPing(this, 4);
      }
      if (_netPinger != null) {
        //recordStepInfo("ping..." + LDNetUtil.OPEN_IP);
        //_netPinger.exec(LDNetUtil.OPEN_IP, true);
      }

      // 开始诊断traceRoute
      recordStepInfo("\n开始traceroute...");
      _traceRouter = LDNetTraceRoute.getInstance();
      _traceRouter.initListenter(this);
      _traceRouter.isCTrace = this._isUseJNICTrace;
      _traceRouter.startTraceRoute(_dormain);
      return _logInfo.toString();
    } else {
      recordStepInfo("\n\n当前主机未联网,请检查网络!");
      return _logInfo.toString();
    }
  }

  /**
   * 停止诊断网络
   */
  public void stopNetDialogsis() {
    if (_isRunning) {
      if (_netSocker != null) {
        _netSocker.resetInstance();
        _netSocker = null;
      }

      if (_netPinger != null) {
        _netPinger = null;
      }
      if (_traceRouter != null) {
        _traceRouter.resetInstance();
        _traceRouter = null;
      }
      cancel(true);// 尝试去取消线程的执行
      if (sExecutor != null && !sExecutor.isShutdown()) {
        sExecutor.shutdown();
        sExecutor = null;
      }

      _isRunning = false;
    }
  }

  /**
   * 设置是否需要JNICTraceRoute
   *
   * @param use
   */
  public void setIfUseJNICConn(boolean use) {
    this._isUseJNICConn = use;
  }

  /**
   * 设置是否需要JNICTraceRoute
   *
   * @param use
   */
  public void setIfUseJNICTrace(boolean use) {
    this._isUseJNICTrace = use;
  }

  /**
   * 打印整体loginInfo;
   */
  public void printLogInfo() {
    System.out.print(_logInfo);
  }

  /**
   * 如果调用者实现了stepInfo接口,输出信息
   *
   * @param stepInfo
   */
  private void recordStepInfo(String stepInfo) {
    _logInfo.append(stepInfo + "\n");
    publishProgress(stepInfo + "\n");
  }

  /**
   * traceroute 消息跟踪
   */
  @Override
  public void OnNetTraceFinished() {
  }

  @Override
  public void OnNetTraceUpdated(String log) {
    if (log == null) {
      return;
    }
    if (this._traceRouter != null && this._traceRouter.isCTrace) {
      if (log.contains("ms") || log.contains("***")) {
        log += "\n";
      }
      _logInfo.append(log);
      publishProgress(log);
    } else {
      this.recordStepInfo(log);
    }
  }

  /**
   * socket完成跟踪
   */
  @Override
  public void OnNetSocketFinished(String log) {
    _logInfo.append(log);
    publishProgress(log);
  }

  /**
   * socket更新跟踪
   */
  @Override
  public void OnNetSocketUpdated(String log) {
    _logInfo.append(log);
    publishProgress(log);
  }

  /**
   * 输出关于应用、机器、网络诊断的基本信息
   */
  private void recordCurrentAppVersion() {
    // 输出应用版本信息和用户ID
    recordStepInfo("应用code:\t" + _appCode);
    recordStepInfo("应用名称:\t" + this._appName);
    recordStepInfo("应用版本:\t" + this._appVersion);
//    recordStepInfo("用户id:\t" + _UID);

    // 输出机器信息
    recordStepInfo("机器类型:\t" + android.os.Build.MANUFACTURER + ":"
        + android.os.Build.BRAND + ":" + android.os.Build.MODEL);
    recordStepInfo("系统版本:\t" + android.os.Build.VERSION.RELEASE);
    if (_telManager != null && TextUtils.isEmpty(_deviceID)) {
      _deviceID = _telManager.getDeviceId();
    }
    recordStepInfo("机器ID:\t" + _deviceID);

    // 运营商信息
    if (TextUtils.isEmpty(_carrierName)) {
      _carrierName = LDNetUtil.getMobileOperator(_context);
    }
    recordStepInfo("运营商:\t" + _carrierName);

    if (_telManager != null && TextUtils.isEmpty(_ISOCountryCode)) {
      _ISOCountryCode = _telManager.getNetworkCountryIso();
    }
    recordStepInfo("ISOCountryCode:\t" + _ISOCountryCode);

    if (_telManager != null && TextUtils.isEmpty(_MobileCountryCode)) {
      String tmp = _telManager.getNetworkOperator();
      if (tmp.length() >= 3) {
        _MobileCountryCode = tmp.substring(0, 3);
      }
      if (tmp.length() >= 5) {
        _MobileNetCode = tmp.substring(3, 5);
      }
    }
    recordStepInfo("MobileCountryCode:\t" + _MobileCountryCode);
    recordStepInfo("MobileNetworkCode:\t" + _MobileNetCode+"\n");
  }

  /**
   * 输出本地网络环境信息
   */
  private void recordLocalNetEnvironmentInfo() {
    recordStepInfo("诊断域名 " + _dormain + "...");

    // 网络状态
    if (LDNetUtil.isNetworkConnected(_context)) {
      _isNetConnected = true;
      recordStepInfo("当前是否联网:\t" + "已联网");
    } else {
      _isNetConnected = false;
      recordStepInfo("当前是否联网:\t" + "未联网");
    }

    // 获取当前网络类型
    _netType = LDNetUtil.getNetWorkType(_context);
    recordStepInfo("当前联网类型:\t" + _netType);
    if (_isNetConnected) {
      if (LDNetUtil.NETWORKTYPE_WIFI.equals(_netType)) { // wifi:获取本地ip和网关,其他类型:只获取ip
        _localIp = LDNetUtil.getLocalIpByWifi(_context);
        _gateWay = LDNetUtil.pingGateWayInWifi(_context);
      } else {
        _localIp = LDNetUtil.getLocalIpBy3G();
      }
      recordStepInfo("本地IP:\t" + _localIp);
    } else {
      recordStepInfo("本地IP:\t" + "127.0.0.1");
    }
    if (_gateWay != null) {
      recordStepInfo("本地网关:\t" + this._gateWay);
    }

    // 获取本地DNS地址
    if (_isNetConnected) {
      _dns1 = LDNetUtil.getLocalDns("dns1");
      _dns2 = LDNetUtil.getLocalDns("dns2");
      recordStepInfo("本地DNS:\t" + this._dns1 + "," + this._dns2);
    } else {
      recordStepInfo("本地DNS:\t" + "0.0.0.0" + "," + "0.0.0.0");
    }

    // 获取远端域名的DNS解析地址
    if (_isNetConnected) {
      recordStepInfo("远端域名:\t" + this._dormain);
      _isDomainParseOk = parseDomain(this._dormain);// 域名解析
    }
  }

  /**
   * 域名解析
   */
  private boolean parseDomain(String _dormain) {
    boolean flag = false;
    int len = 0;
    String ipString = "";
    Map<String, Object> map = LDNetUtil.getDomainIp(_dormain);
    String useTime = (String) map.get("useTime");
    _remoteInet = (InetAddress[]) map.get("remoteInet");
    String timeShow = null;
    if (Integer.parseInt(useTime) > 5000) {// 如果大于1000ms,则换用s来显示
      timeShow = " (" + Integer.parseInt(useTime) / 1000 + "s)";
    } else {
      timeShow = " (" + useTime + "ms)";
    }
    if (_remoteInet != null) {// 解析正确
      len = _remoteInet.length;
      for (int i = 0; i < len; i++) {
        _remoteIpList.add(_remoteInet[i].getHostAddress());
        ipString += _remoteInet[i].getHostAddress() + ",";
      }
      ipString = ipString.substring(0, ipString.length() - 1);
      recordStepInfo("DNS解析结果:\t" + ipString + timeShow);
      flag = true;
    } else {// 解析不到,判断第一次解析耗时,如果大于10s进行第二次解析
      if (Integer.parseInt(useTime) > 10000) {
        map = LDNetUtil.getDomainIp(_dormain);
        useTime = (String) map.get("useTime");
        _remoteInet = (InetAddress[]) map.get("remoteInet");
        if (Integer.parseInt(useTime) > 5000) {// 如果大于1000ms,则换用s来显示
          timeShow = " (" + Integer.parseInt(useTime) / 1000 + "s)";
        } else {
          timeShow = " (" + useTime + "ms)";
        }
        if (_remoteInet != null) {
          len = _remoteInet.length;
          for (int i = 0; i < len; i++) {
            _remoteIpList.add(_remoteInet[i].getHostAddress());
            ipString += _remoteInet[i].getHostAddress() + ",";
          }
          ipString = ipString.substring(0, ipString.length() - 1);
          recordStepInfo("DNS解析结果:\t" + ipString + timeShow);
          flag = true;
        } else {
          recordStepInfo("DNS解析结果:\t" + "解析失败" + timeShow);
        }
      } else {
        recordStepInfo("DNS解析结果:\t" + "解析失败" + timeShow);
      }
    }
    return flag;
  }

  /**
   * 获取运营商信息
   */
  private String requestOperatorInfo() {
    String res = null;
    String url = LDNetUtil.OPERATOR_URL;
    HttpURLConnection conn = null;
    URL Operator_url;
    try {
      Operator_url = new URL(url);
      conn = (HttpURLConnection) Operator_url.openConnection();
      conn.setRequestMethod("GET");
      conn.setConnectTimeout(1000 * 10);
      conn.connect();
      int responseCode = conn.getResponseCode();
      if (responseCode == 200) {
        res = LDNetUtil.getStringFromStream(conn.getInputStream());
        if (conn != null) {
          conn.disconnect();
        }
      }
      return res;
    } catch (MalformedURLException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } finally {
      if (conn != null) {
        conn.disconnect();
      }
    }
    return res;
  }

  /**
   * ping 消息跟踪
   */
  @Override
  public void OnNetPingFinished(String log) {
    this.recordStepInfo(log);
  }

  private static final int CORE_POOL_SIZE = 1;// 4
  private static final int MAXIMUM_POOL_SIZE = 1;// 10
  private static final int KEEP_ALIVE = 10;// 10

  private static final BlockingQueue<Runnable> sWorkQueue = new LinkedBlockingQueue<Runnable>(
      2);// 2
  private static final ThreadFactory sThreadFactory = new ThreadFactory() {
    private final AtomicInteger mCount = new AtomicInteger(1);

    @Override
    public Thread newThread(Runnable r) {
      Thread t = new Thread(r, "Trace #" + mCount.getAndIncrement());
      t.setPriority(Thread.MIN_PRIORITY);
      return t;
    }
  };

  private static ThreadPoolExecutor sExecutor = null;

  @Override
  protected ThreadPoolExecutor getThreadPoolExecutor() {
    // TODO Auto-generated method stub
    return sExecutor;
  }

}