/// <reference path="./definition.d.ts" />

import WebApi from '../ajax/webapi';
import { userAgent } from '../util/device';
import { urlStartIndexForString, getCurrentRoute } from '../util/router';

declare const process: {
  env: {
    NODE_ENV: string;
  }
};

class Exception {
  /**
   * api名称
   */
  public static apiname: string = '/api/product/exception/create';
  /**
   * 产品编码
   */
  public static productCode: ExceptionProductCode;
  /**
   * 经度
   */
  public static lat: string;
  /**
   * 纬度
   */
  public static lng: string;
  /**
   * 账号
   */
  public static account: string;
  /**
   * 微信用户标示
   */
  public static openId: string;
  /**
   * 版本
   */
  public static version: string;
  /**
   * 网络环境
   */
  public static network: string;
  /**
   * 接口请求类
   */
  public static webapi: WebApi;

  /**
   * 发送错误
   */
  public static send(data: ExceptionSendParams): void {
    if (process.env.NODE_ENV !== 'production' || !Exception.productCode) {
      if (Exception.productCode) {
        console.log('异常上报的内容：');
        console.log(data);
      }
      return;
    }
    const params: ExceptionData = {
      col: data.col,
      errorType: data.errorType || 1,
      line: data.line,
      content: data.content,
      details: data.details,
      productCode: data.productCode || Exception.productCode,
      url: data.url,
      userAgent,
      lat: Exception.lat,
      lng: Exception.lng,
      account: Exception.account,
      openId: Exception.openId,
      version: Exception.version,
      currentPage: getCurrentRoute(),
      network: Exception.network
    };
    this.webapi.post({
      api: Exception.apiname,
      type: 'post',
      contentType: 'application/json',
      timeoutStr: 'none',
      params,
      success: () => null,
      isCache: false
    });
  }

  /**
   * 解析catch的错误
   */
  public static catchParse(e: Error): ExceptionData {
    const details = e.stack.trim();
    let startIndex = urlStartIndexForString(details);
    let suffixIndex = details.indexOf('.js:') + 4;
    let lineEndIndex = details.substr(suffixIndex).indexOf(':') + suffixIndex;
    let endIndex = lineEndIndex + 6;
    let path = details.substr(startIndex, endIndex - startIndex);
    if (urlStartIndexForString(path, true) !== startIndex) {
      startIndex = urlStartIndexForString(path, true);
      path = path.substr(startIndex, endIndex - startIndex);
      startIndex = 0;
      suffixIndex = path.indexOf('.js:') + 4;
      lineEndIndex = path.substr(suffixIndex).indexOf(':') + suffixIndex;
      endIndex = lineEndIndex + 6;
    }
    const url = path.substr(startIndex, suffixIndex - startIndex - 1);
    const line = path.substr(suffixIndex, lineEndIndex - suffixIndex);
    const col = path.substr(lineEndIndex, endIndex - suffixIndex).replace(/[^0-9]/ig, '');

    return {
      col: parseInt(col),
      line: parseInt(line),
      url,
      details: e.stack,
      content: `name: ${e.name};
message: ${e.message};`,
      errorType: 1,
      userAgent,
      lat: Exception.lat,
      lng: Exception.lng,
      account: Exception.account,
      openId: Exception.openId,
      version: Exception.version,
      currentPage: getCurrentRoute(),
      network: Exception.network
    };
  }
  /**
   * catch时发送错误
   */
  public static catchSend(e: Error): void {
    const data: ExceptionSendParams = Exception.catchParse(e);
    if (process.env.NODE_ENV !== 'production') {
      console.log('异常解析结果：');
      console.log(data);
      throw e;
    } else {
      Exception.send(data);
    }
  }

  /**
   * 监听异常
   */
  public static listen(): void {
    window.onerror = (msg: string, url: string, line: number, col: number, error: Error): boolean => {
      //没有URL不上报！上报也不知道错误
      if (msg !== 'Script error.' && !url) {
        return true;
      }
      //采用异步的方式
      //我遇到过在window.onunload进行ajax的堵塞上报
      //由于客户端强制关闭webview导致这次堵塞上报有Network Error
      //我猜测这里window.onerror的执行流在关闭前是必然执行的
      //而离开文章之后的上报对于业务来说是可丢失的
      //所以我把这里的执行流放到异步事件去执行
      //脚本的异常数降低了10倍
      setTimeout(function(): void {
        const data: ExceptionData = {
          productCode: Exception.productCode,
          errorType: 1,
          col: 0,
          line: 0,
          url: '',
          content: '',
          details: '',
          userAgent,
          lat: Exception.lat,
          lng: Exception.lng,
          account: Exception.account,
          openId: Exception.openId,
          version: Exception.version,
          currentPage: getCurrentRoute(),
          network: Exception.network
        };
        //不一定所有浏览器都支持col参数
        col = col || (window.event && window.event['errorCharacter']) || 0;

        data.url = url;
        data.line = line;
        data.col = col;
        if (!!error && !!error.stack) {
          //如果浏览器有堆栈信息
          //直接使用
          data.content = `name: ${error.name};
message: ${error.message};`;
          data.details = error.stack;
        } else if (!!arguments.callee) {
          //尝试通过callee拿堆栈信息
          let ext: any = [];
          let f = arguments.callee.caller;
          let c = 3;
          //这里只拿三层堆栈信息
          while (f && (--c > 0)) {
            ext.push(f.toString());
            if (f  === f.caller) {
              //如果有环
              break;
            }
            f = f.caller;
          }
          ext = ext.join(',');
          data.content = ext;
        }
        //把data上报到后台！
        if (process.env.NODE_ENV !== 'production') {
          console.log('onerror扑捉到的错误：');
          console.log(data);
        } else {
          Exception.send(data);
        }
      }, 0);
      if (process.env.NODE_ENV !== 'production') {
        throw error;
      } else {
        return true;
      }
    };
  }
}

export default Exception;