function debounce(func, wait, immediate) {
    var timeout, result;
    var debounced = function () {
        var context = this;
        var args = arguments;
        if (timeout) clearTimeout(timeout);
        if (immediate) {
            // 如果已经执行过，不再执行
            var callNow = !timeout;
            timeout = setTimeout(function () {
                timeout = null;
            }, wait);
            if (callNow) result = func.apply(context, args);
        }
        else {
            timeout = setTimeout(function () {
                func.apply(context, args);
            }, wait);
        }
        return result;
    };
    debounced.cancel = function () {
        clearTimeout(timeout);
        timeout = null;
    };
    return debounced;
}


// 防抖的原理就是：
// 你尽管触发事件，但是我一定在事件触发 n 秒后才执行，如果你在一个事件触发的 n 秒内又触发了这个事件，那我就以新的事件的时间为准，n 秒后才执行，总之，就是要等你触发完事件 n 秒内不再触发事件，我才执行!


// function getUserAction(e) {
//     ...
//  };
//  // 防抖
//  var setUseAction = debounce(getUserAction, 10000, true);
//  // 取消
//  setUseAction.cancel();

function throttle(func, wait, options) {
    var timeout, context, args;
    var previous = 0;
    if (!options) options = {};
    var later = function() {
        previous = options.leading === false ? 0 : new Date().getTime();
        timeout = null;
        func.apply(context, args);
        if (!timeout) context = args = null;
    };
    var throttled = function() {
        var now = new Date().getTime();
        if (!previous && options.leading === false) previous = now;
        var remaining = wait - (now - previous);
        context = this;
        args = arguments;
        if (remaining <= 0 || remaining > wait) {
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            previous = now;
            func.apply(context, args);
            if (!timeout) context = args = null;
        } else if (!timeout && options.trailing !== false) {
            timeout = setTimeout(later, remaining);
        }
    };
    throttled.cancel = function() {
        clearTimeout(timeout);
        previous = 0;
        timeout = null;
    };
    return throttled;
}


// 节流的原理：

// 如果你持续触发事件，每隔一段时间，只执行一次事件。

// 根据首次是否执行以及结束后是否执行，效果有所不同，实现的方式也有所不同。 我们用 leading 代表首次是否执行，trailing 代表结束后是否再执行一次。

// throttle(getUserAction, 1000);
// //leading：false 表示禁用第一次执行
// throttle(getUserAction, 1000, {
//     leading: false
// });
// //trailing: false 表示禁用停止触发的回调
// throttle(getUserAction, 1000, {
//     trailing: false
// });

function transformJson(a, idStr, pidStr, chindrenStr) {
    var r = [], hash = {}, id = idStr, pid = pidStr, children = chindrenStr, i = 0, j = 0, len = a.length;
    for (; i < len; i++) {
        hash[a[i][id]] = a[i];
    }
    for (; j < len; j++) {
        var aVal = a[j], hashVP = hash[aVal[pid]];
        if (hashVP) {
            !hashVP[children] && (hashVP[children] = []);
            hashVP[children].push(aVal);
        } else {
            r.push(aVal);
        }
    }
    return r;
}  


// // 二维字符串
// var jsonData = [{"belongsname":"","id":901,"isleaf":0,"name":"XJBHX-2标项目部","pid":"","type":""},{"belongsname":"","id":902,"isleaf":1,"name":"综合部(办公室)","pid":"901","type":""},{"belongsname":"","id":903,"isleaf":1,"name":"工程部(工技部/技术部)","pid":"901","type":""},{"belongsname":"","id":904,"isleaf":1,"name":"安质部","pid":"901","type":""},{"belongsname":"","id":905,"isleaf":1,"name":"计财部","pid":"901","type":""},{"belongsname":"","id":906,"isleaf":1,"name":"物设部(物机部)","pid":"901","type":""},{"belongsname":"","id":907,"isleaf":1,"name":"中心试验室","pid":"901","type":""}];
// //绑定的字段
// var jsonDataTree = transData(jsonData, 'id', 'pid', 'chindren');

// 加载带回调js
// loadScript('https://cdn.bootcss.com/Turf.js/5.1.6/turf.min.js',function(){
//   console.log('onload');
//   console.log(turf);
// });

function loadScriptCallback(src, callback) {
    var script = document.createElement('script'),
      head = document.getElementsByTagName('head')[0];
    script.type = 'text/javascript';
    script.charset = 'UTF-8';
    script.src = src;
    if (script.addEventListener) {
      script.addEventListener('load', function () {
        callback();
      }, false);
    } else if (script.attachEvent) {
      script.attachEvent('onreadystatechange', function () {
        var target = window.event.srcElement;
        if (target.readyState == 'loaded') {
          callback();
        }
      });
    }
    head.appendChild(script);
  }



// 动态加载js脚本文件
// loadScript("javascript/lib/cookie.js");
function loadScript(url) {
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.src = url;
    document.body.appendChild(script);
}





// 动态加载js脚本
// var text = "function test(){alert('test');}";
// loadScriptString(text);
// test();
function loadScriptString(code) {
    var script = document.createElement("script");
    script.type = "text/javascript";
    try{
        // firefox、safari、chrome和Opera
        script.appendChild(document.createTextNode(code));
    }catch(ex) {
        // IE早期的浏览器 ,需要使用script的text属性来指定javascript代码。
        script.text = code;
    }
    document.body.appendChild(script);
}





// 动态加载css文件
// loadStyles("css/secondindex.css");
function loadStyles(url) {
    var link = document.createElement("link");
    link.type = "text/css";
    link.rel = "stylesheet";
    link.href = url;
    document.getElementsByTagName("head")[0].appendChild(link);
}







// 动态加载css脚本

// var css = "body{color:blue;}";
// loadStyleString(css);
function loadStyleString(cssText) {
    var style = document.createElement("style");
    style.type = "text/css";
    try{
        // firefox、safari、chrome和Opera
        style.appendChild(document.createTextNode(cssText));
    }catch(ex) {
        // IE早期的浏览器 ,需要使用style元素的stylesheet属性的cssText属性
        style.styleSheet.cssText = cssText;
    }
    document.getElementsByTagName("head")[0].appendChild(style);
}

function getExt(filename) {
    if (typeof filename == 'string') {
        return filename
            .split('.')
            .pop()
            .toLowerCase()
    } else {
        throw new Error('filename must be a string type')
    }
}


// getExt("1.text") //->text

function copyToBoard(value) {
    const element = document.createElement('textarea');
    document.body.appendChild(element);
    element.value = value;
    element.select();
    if (document.execCommand('copy')) {
        document.execCommand('copy');
        document.body.removeChild(element);
        return true
    }
    document.body.removeChild(element);
    return false
}


// 原理：

// 创建一个textare元素并调用select()方法选中
// document.execCommand('copy')方法，拷贝当前选中内容到剪贴板。


//如果复制成功返回true
// copyToBoard('lalallala')

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}


//使用方式
// const fetchData = async () => {
//     await sleep(1000)
// }

function uuid(length, chars) {
    chars =
        chars ||
        '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    length = length || 8;
    var result = '';
    for (var i = length; i > 0; --i)
        result += chars[Math.floor(Math.random() * chars.length)];
    return result
}


// 使用方式

//第一个参数指定位数，第二个字符串指定字符，都是可选参数，如果都不传，默认生成8位
// uuid()

// 保留小数点以后几位，默认2位
function cutNumber(number, no = 2) {
    if (typeof number != 'number') {
        number = Number(number);
    }
    return Number(number.toFixed(no))
}

// 使用场景：JS的浮点数超长，有时候页面显示时需要保留2位小数

function getFormData(object) {
    const formData = new FormData();
    Object.keys(object).forEach(key => {
        const value = object[key];
        if (Array.isArray(value)) {
            value.forEach((subValue, i) =>
                formData.append(key + `[${i}]`, subValue)
            );
        } else {
            formData.append(key, object[key]);
        }
    });
    return formData
}


// 使用场景：上传文件时我们要新建一个FormData对象，然后有多少个参数就append多少次，使用该函数可以简化逻辑
// 使用方式：
// let req={
// file:xxx,
// userId:1,
// phone:'15198763636',
// //...
// }
// fetch(getFormData(req))

function getQueryValue(queryName) {
    let reg = new RegExp("(^|&)" + queryName + "=([^&]*)(&|$)", "i");
    let r = window.location.search.substr(1).match(reg);
    if (r != null) {
      return decodeURI(r[2]);
    } else {
      return null;
    }
  }


//   示例URL:
// http://htmlJsTest/getrequest.html?uid=admin&rid=1&fid=2&name=小明
// @param {[string]} queryName [参数名]
// @return {[string]} [参数值]

function objExtension (inObj, path, parms) {
    // 一个长得像对象的字符串
    var Things = path.split(".");
    // 即将返回的对象
    var obj = inObj;
    // 不断迭代的key值
    var key = "";
    // 赋值
    var strparms = JSON.stringify(parms);
    for (var i = 0; i < Things.length; i++) {
        // 累加key
        key += Things[i];
        // 如果对象不存在
        if (!eval("obj." + key)) {
            // 则创建它
            eval("obj." + key + " = {}");
        }
        // 如果是最后一个的话
        if (i === Things.length - 1) {
            eval("obj." + key + " = " + strparms);
        }
        // 再叠加一次.
        key += ".";
    }
    // 循环结束，返回对象
    return obj
}


// var path = "D01.D0104.D010442.D01044207.D0104420707.D010442070701"
// objExtension({a: 123}, path, {id: 123, label: 321})

function getRequest(urlStr) {
    let url = decodeURI(location.search);
    if (typeof urlStr !== "undefined") {
      url = "?" + urlStr.split("?")[1];
    }
    let theRequest = new Object();
    if (url.indexOf("?") != -1) {
      let str = url.substr(1);
      let strs = str.split("&");
      for (var i = 0; i < strs.length; i++) {
        theRequest[strs[i].split("=")[0]] = decodeURI(strs[i].split("=")[1]);
      }
    }
    return theRequest;
  }


//   示例URL:
// http://htmlJsTest/getrequest.html?uid=admin&rid=1&fid=2&name=小明
// @param {[string]} urlStr [当该参数不为空的时候，则解析该url中的参数集合]
// @return {[string]} [参数集合]

/* eslint-disable promise/prefer-await-to-then */
const methodMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror'], // New WebKit
['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror'], // Old WebKit
['webkitRequestFullScreen', 'webkitCancelFullScreen', 'webkitCurrentFullScreenElement', 'webkitCancelFullScreen', 'webkitfullscreenchange', 'webkitfullscreenerror'], ['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror'], ['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError']];

const nativeAPI = (() => {
  const unprefixedMethods = methodMap[0];
  const returnValue = {};

  for (const methodList of methodMap) {
    const exitFullscreenMethod = methodList[1];

    if (exitFullscreenMethod in document) {
      for (const [index, method] of methodList.entries()) {
        returnValue[unprefixedMethods[index]] = method;
      }

      return returnValue;
    }
  }

  return false;
})();

const eventNameMap = {
  change: nativeAPI.fullscreenchange,
  error: nativeAPI.fullscreenerror
}; // eslint-disable-next-line import/no-mutable-exports

let screenfull = {
  // eslint-disable-next-line default-param-last
  request(element = document.documentElement, options) {
    return new Promise((resolve, reject) => {
      const onFullScreenEntered = () => {
        screenfull.off('change', onFullScreenEntered);
        resolve();
      };

      screenfull.on('change', onFullScreenEntered);
      const returnPromise = element[nativeAPI.requestFullscreen](options);

      if (returnPromise instanceof Promise) {
        returnPromise.then(onFullScreenEntered).catch(reject);
      }
    });
  },

  exit() {
    return new Promise((resolve, reject) => {
      if (!screenfull.isFullscreen) {
        resolve();
        return;
      }

      const onFullScreenExit = () => {
        screenfull.off('change', onFullScreenExit);
        resolve();
      };

      screenfull.on('change', onFullScreenExit);
      const returnPromise = document[nativeAPI.exitFullscreen]();

      if (returnPromise instanceof Promise) {
        returnPromise.then(onFullScreenExit).catch(reject);
      }
    });
  },

  toggle(element, options) {
    return screenfull.isFullscreen ? screenfull.exit() : screenfull.request(element, options);
  },

  onchange(callback) {
    screenfull.on('change', callback);
  },

  onerror(callback) {
    screenfull.on('error', callback);
  },

  on(event, callback) {
    const eventName = eventNameMap[event];

    if (eventName) {
      document.addEventListener(eventName, callback, false);
    }
  },

  off(event, callback) {
    const eventName = eventNameMap[event];

    if (eventName) {
      document.removeEventListener(eventName, callback, false);
    }
  },

  raw: nativeAPI
};
Object.defineProperties(screenfull, {
  isFullscreen: {
    get: () => Boolean(document[nativeAPI.fullscreenElement])
  },
  element: {
    enumerable: true,
    get: () => document[nativeAPI.fullscreenElement] || undefined
  },
  isEnabled: {
    enumerable: true,
    // Coerce to boolean in case of old WebKit.
    get: () => Boolean(document[nativeAPI.fullscreenEnabled])
  }
});

if (!nativeAPI) {
  screenfull = {
    isEnabled: false
  };
}

function isFullscreenEnabled() {
  return !!(
      document.webkitIsFullScreen ||
      document.mozFullScreen ||
      document.msFullscreenElement ||
      document.fullscreenElement
  );
}

//定义一些常量
var x_PI = 3.14159265358979324 * 3000.0 / 180.0;
var PI = 3.1415926535897932384626;
var a = 6378245.0;
var ee = 0.00669342162296594323;
/**
 * 百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02)的转换
 * 即 百度 转 谷歌、高德
 * @param bd_lon
 * @param bd_lat
 * @returns {*[]}
 */
function bd09togcj02(bd_lon, bd_lat) {
    var bd_lon = +bd_lon;
    var bd_lat = +bd_lat;
    var x = bd_lon - 0.0065;
    var y = bd_lat - 0.006;
    var z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_PI);
    var theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_PI);
    var gg_lng = z * Math.cos(theta);
    var gg_lat = z * Math.sin(theta);
    return [gg_lng, gg_lat]
}/**
 * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换
 * 即谷歌、高德 转 百度
 * @param lng
 * @param lat
 * @returns {*[]}
 */
function gcj02tobd09(lng, lat) {
    var lat = +lat;
    var lng = +lng;
    var z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * x_PI);
    var theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * x_PI);
    var bd_lng = z * Math.cos(theta) + 0.0065;
    var bd_lat = z * Math.sin(theta) + 0.006;
    return [bd_lng, bd_lat]
}/**
 * WGS84转GCj02
 * @param lng
 * @param lat
 * @returns {*[]}
 */
function wgs84togcj02(lng, lat) {
    var lat = +lat;
    var lng = +lng;
    if (out_of_china(lng, lat)) {
        return [lng, lat]
    } else {
        var dlat = transformlat(lng - 105.0, lat - 35.0);
        var dlng = transformlng(lng - 105.0, lat - 35.0);
        var radlat = lat / 180.0 * PI;
        var magic = Math.sin(radlat);
        magic = 1 - ee * magic * magic;
        var sqrtmagic = Math.sqrt(magic);
        dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);
        dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);
        var mglat = lat + dlat;
        var mglng = lng + dlng;
        return [mglng, mglat]
    }
}/**
 * GCJ02 转换为 WGS84
 * @param lng
 * @param lat
 * @returns {*[]}
 */
function gcj02towgs84(lng, lat) {
    var lat = +lat;
    var lng = +lng;
    if (out_of_china(lng, lat)) {
        return [lng, lat]
    } else {
        var dlat = transformlat(lng - 105.0, lat - 35.0);
        var dlng = transformlng(lng - 105.0, lat - 35.0);
        var radlat = lat / 180.0 * PI;
        var magic = Math.sin(radlat);
        magic = 1 - ee * magic * magic;
        var sqrtmagic = Math.sqrt(magic);
        dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);
        dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);
        var mglat = lat + dlat;
        var mglng = lng + dlng;
        return [lng * 2 - mglng, lat * 2 - mglat]
    }
}








var transformlat = function transformlat(lng, lat) {
    var lat = +lat;
    var lng = +lng;
    var ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));
    ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;
    ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0;
    ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0;
    return ret
};
var transformlng = function transformlng(lng, lat) {
    var lat = +lat;
    var lng = +lng;
    var ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));
    ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;
    ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0;
    ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0;
    return ret
};
/**
 * 判断是否在国内，不在国内则不做偏移
 * @param lng
 * @param lat
 * @returns {boolean}
 */
var out_of_china = function out_of_china(lng, lat) {
    var lat = +lat;
    var lng = +lng;
    // 纬度3.86~53.55,经度73.66~135.05
    return !(lng > 73.66 && lng < 135.05 && lat > 3.86 && lat < 53.55);
};




// 其它方法

function calcAngle(p1, p2) {
    // angle in radians
    Math.atan2(p2[1] - p1[1], p2[0] - p1[0]);
    // angle in degrees
    var angleDeg = Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180 / Math.PI;
    return angleDeg;
}

/**
 * 事件总线
 *
 * 使用方法:
 *  1. 引入组件类
 *         import EventBus from './EventBus'
 *  2. 事件输出与订阅
 *         输出：EventBus.emit('ww.myevent','nihao1')
 *
 *         订阅：EventBus.addEventListener('ww.myevent', (data) => {
 *              name = data;
 *         })
 *
 */
 class EventDispatcer {
    constructor () {
        // if (window && window.EventBus) {
        //     throw new Error('这是一个单例！')
        // }
        this.eventMap = new Map();
    }
    emit (evt,data) {
        const event = new Event(evt);
        let evtSet = this.eventMap.get(event.type);
        if (evtSet) {
            for (let item of evtSet.values()) {
                item(data);
            }
        }
    }
    addEventListener (eventType, func) {
        let evtSet = this.eventMap.get(eventType);
        if (!evtSet) {
            evtSet = new Set();
            this.eventMap.set(eventType, evtSet);
        }
        evtSet.add(func);
    }
    removeEventListener (eventType, func) {
        let evtSet = this.eventMap.get(eventType);
        if (evtSet) {
            evtSet.delete(func);
        }
    }
}
const bus = new EventDispatcer();

var main = {
    debounce,
    throttle,
    transformJson,
    getExt,
    copyToBoard,
    sleep,
    uuid,
    cutNumber,
    getFormData,
    getQueryValue,
    isFullscreenEnabled,
    objExtension,
    getRequest,
    screenfull,
    loadScript,
    loadScriptString,
    loadScriptCallback,
    loadStyles,
    loadStyleString,
    bd09togcj02,
    gcj02tobd09,
    gcj02towgs84,
    wgs84togcj02,
    calcAngle,
    bus
};

export { bd09togcj02, bus, calcAngle, copyToBoard, cutNumber, debounce, main as default, gcj02tobd09, gcj02towgs84, getExt, getFormData, getQueryValue, getRequest, isFullscreenEnabled, loadScript, loadScriptCallback, loadScriptString, loadStyleString, loadStyles, objExtension, screenfull, sleep, throttle, transformJson, uuid, wgs84togcj02 };
