"use strict";

var queryParser = require("querystring");
var URL = require("url");
var path = require("path");
var fse = require("fs-extra");
var util = require("./util");

var bundleWrapper = function bundleWrapper(code, sourceUrl) {
  var injectedGlobals = [
  // ES
  "Promise",
  // W3C
  "window", "weex", "service", "Rax", "services", "global", "screen", "document", "navigator", "location", "fetch", "Headers", "Response", "Request", "URL", "URLSearchParams", "setTimeout", "clearTimeout", "setInterval", "clearInterval", "requestAnimationFrame", "cancelAnimationFrame", "alert",
  // ModuleJS
  "define", "require",
  // Weex
  "bootstrap", "register", "render", "__d", "__r", "__DEV__", "__weex_define__", "__weex_require__", "__weex_viewmodel__", "__weex_document__", "__weex_bootstrap__", "__weex_options__", "__weex_data__", "__weex_downgrade__", "__weex_require_module__", "Vue"];
  var bundlewrapper = "function __weex_bundle_entry__(" + injectedGlobals.join(",") + "){";
  var rearRegexp = /\/\/#\s*sourceMappingURL(?!.*?\s+.)|$/;
  var match = /^\/\/\s?{\s?"framework"\s?:\s?"(\w+)"\s?}/.exec(code);
  var anno = "";
  if (match) {
    anno = '$$frameworkFlag["' + (sourceUrl || "@") + '"]="' + match[1] + '"\n';
  }
  return anno + bundlewrapper + code.replace(rearRegexp, "}\n$&");
};
var transformUrlToLocalUrl = function transformUrlToLocalUrl(sourceURl) {
  var rHttpHeader = /^(https?|taobao|qap):\/\/(?!.*your_current_ip)/i;
  var bundleUrl = void 0;
  if (rHttpHeader.test(sourceURl)) {
    var query = queryParser.parse(URL.parse(sourceURl).query);
    if (query["_wx_tpl"]) {
      bundleUrl = util.normalize(query["_wx_tpl"]).replace(rHttpHeader, "");
    } else {
      bundleUrl = util.normalize(sourceURl).replace(rHttpHeader, "");
    }
  } else {
    bundleUrl = sourceURl.replace(/^(https?|taobao|qap):\/\/(.*your_current_ip):(\d+)\//i, "file://");
  }
  if (bundleUrl.charAt(bundleUrl.length - 1) === "?") {
    bundleUrl = bundleUrl.substring(0, bundleUrl.length - 1);
  }
  if (bundleUrl.charAt(bundleUrl.length - 1) === "?") {
    bundleUrl = bundleUrl.substring(0, bundleUrl.length - 1);
  }
  return "/source/" + bundleUrl;
};

var eventConstructor = "// event constructor\nfunction __EventEmitter__() {\n  this._handlers = {};\n}\n  \n__EventEmitter__.prototype = {\n  constructor: __EventEmitter__,\n  off: function (method, handler) {\n      if (handler) {\n        for (var i = 0; i < this._handlers[method].length; i++) {\n          if (this._handlers[method][i] === handler) {\n            this._handlers[method].splice(i, 1);\n            i--;\n          }\n        }\n      }\n      else {\n        this._handlers[method] = [];\n      }\n  },\n  once: function (method, handler) {\n  var self = this;\n  var fired = false;\n  function g() {\n    self.off(method, g);\n    if (!fired) {\n      fired = true;\n       handler.apply(self, Array.prototype.slice.call(arguments));\n    }\n  }\n  this.on(method, g);\n  },\n  on: function (method, handler) {\n    if (this._handlers[method]) {\n      this._handlers[method].push(handler);\n    }\n    else {\n      this._handlers[method] = [handler];\n    }\n  },\n  _emit: function (method, args, context) {\n    var handlers = this._handlers[method];\n    if (handlers && handlers.length > 0) {\n      handlers.forEach(function (handler) {\n        handler.apply(context, args)\n      });\n      return true;\n    }\n    else {\n      return false;\n    }\n  },\n  emit: function (method) {\n    var context = {};\n    var args = Array.prototype.slice.call(arguments, 1);\n    if (!this._emit(method, args, context)) {\n      this._emit('*', args, context)\n    }\n    this._emit('$finally', args, context);\n    return context;\n  }\n};";

var mockBrowserApi = "// Redefine navigator \nObject.defineProperty(navigator, 'appCodeName', {\n  get: function() {\n    return 'WEEXDEBUGGER';\n  }\n});\n  \nObject.defineProperty(navigator, 'product', {\n  get: function() {\n    return 'WEEX';\n  }\n});\n\n// Redefine console\nvar __origConsole__ = this.console;\nvar __rewriteLog__ = function () {\n  var LEVELS = ['error', 'warn', 'info', 'log', 'debug'];\n  var backupConsole = {\n    error: __origConsole__.error,\n    warn: __origConsole__.warn,\n    info: __origConsole__.info,\n    log: __origConsole__.log,\n    debug: __origConsole__.debug\n  };\n  function resetConsole() {\n    self.console.error = backupConsole.error;\n    self.console.warn = backupConsole.warn;\n    self.console.info = backupConsole.info;\n    self.console.log = backupConsole.log;\n    self.console.debug = backupConsole.debug;\n    self.console.time = __origConsole__.time;\n    self.console.timeEnd = __origConsole__.timeEnd;\n  }\n  \n  function noop() {}\n  return function (logLevel) {\n    resetConsole();\n    LEVELS.slice(LEVELS.indexOf(logLevel) + 1).forEach(function (level) {\n      self.console[level] = noop;\n    })\n  }\n}();\n\n// Redefine timer\nvar __cachedSetTimeout__ = this.setTimeout;\nObject.defineProperty(this, 'setTimeout', {\n  get: function () {\n    return __cachedSetTimeout__;\n  },\n  set: function () {}\n});\nvar __cachedSetInterval__ = this.setInterval;\nObject.defineProperty(this, 'setInterval', {\n  get: function () {\n    return __cachedSetInterval__;\n  },\n  set: function () {}\n});\nvar __cachedClearTimeout__ = this.clearTimeout;\nObject.defineProperty(this, 'clearTimeout', {\n  get: function () {\n    return __cachedClearTimeout__;\n  },\n  set: function () {}\n});\nvar __cachedClearInterval__ = this.clearInterval;\nObject.defineProperty(this, 'clearInterval', {\n  get: function () {\n    return __cachedClearInterval__;\n  },\n  set: function () {}\n});";

var mockContextApi = "// Redefine the JSFramework API\nvar __syncRequest__ = function(data, channelId) {\n  var request = new XMLHttpRequest();\n  request.open(\"POST\", `/syncCallNative/${channelId}`, false); // \"false\" makes the request synchronous\n  request.setRequestHeader(\"Content-Type\", \"application/json;charset=UTF-8\");\n  request.send(JSON.stringify(data));\n  if (request.status === 200) {\n    return JSON.parse(request.responseText);\n  } else {\n    return {\n      error: request.responseText\n    };\n  }\n};\n\nself.callNativeModule = function () {\n  var message = {\n    method: 'WxDebug.syncCall',\n    params: {\n      method: 'callNativeModule',\n      args: __protectedAragument__(arguments)\n    }\n  }\n  var result = __syncRequest__(message, __channelId__);\n  if (result && result.error) {\n    self.console.error(result.error);\n    // throw new Error(result.error);\n  }\n  else {\n    return result && result.ret\n  };\n}\n\nself.callNativeComponent = function () {\n  var args = Array.prototype.slice.call(arguments);\n  for (var i = 0; i < args.length; i++) {\n    if (!args[i]) {\n      args[i] = ''\n    }\n  }\n  var message = {\n    method: 'WxDebug.syncCall',\n    params: {\n      method: 'callNativeComponent',\n      args: args\n    }\n  }\n  var result = __syncRequest__(message, __channelId__);\n  if (result.error) {\n    self.console.error(result.error);\n    // throw new Error(result.error);\n  }\n  else {\n    return result.ret;\n  };\n};\n\nself.callNative = function (instance, tasks, callback) {\n  for (var i = 0; i < tasks.length; i++) {\n    var task = tasks[i];\n    if (task.method == 'addElement') {\n      for (var key in task.args[1].style) {\n        if (Number.isNaN(task.args[1].style[key])) {\n          self.console.error('invalid value [NaN] for style [' + key + ']', task);\n        }\n      }\n    }\n  }\n  var payload = {\n    method: 'WxDebug.callNative',\n    params: {\n      instance: instance,\n      tasks: tasks,\n      callback: callback\n    }\n  };\n  __postData__(payload);\n};\n\nself.callAddElement = function (instance, ref, dom, index, callback) {\n  var payload = {\n    method: 'WxDebug.callAddElement',\n    params: {\n      instance: instance,\n      ref: ref,\n      dom: dom,\n      index: index,\n      callback: callback\n    }\n  };\n  __postData__(payload);\n};\n\nself.nativeLog = function (args) {\n  self.console.log(args)\n};";

var generateSandboxWorkerEntry = function generateSandboxWorkerEntry(env) {
  var worker = fse.readFileSync(path.join(__dirname, "../worker/sandbox_worker.js"));
  var androidMockApi = env.isLayoutAndSandbox ? "self.callCreateBody = function (instance, domStr) {\n  if (!domStr) return;\n  var payload = {\n    method: 'WxDebug.callCreateBody',\n    params: {\n      instance: instance,\n      domStr: domStr\n    }\n  };\n  __postData__(payload);\n};\n\nself.callUpdateFinish = function (instance, tasks, callback) {\n  var payload = {\n    method: 'WxDebug.callUpdateFinish',\n    params: {\n      instance: instance,\n      tasks: tasks,\n      callback: callback\n    }\n  };\n  __postData__(payload);\n};\n\nself.callCreateFinish = function (instance) {\n  var payload = {\n    method: 'WxDebug.callCreateFinish',\n    params: {\n      instance: instance\n    }\n  };\n  __postData__(payload);\n}\n\nself.callRefreshFinish = function (instance, tasks, callback) {\n  var payload = {\n    method: 'WxDebug.callRefreshFinish',\n    params: {\n      instance: instance,\n      tasks: tasks,\n      callback: callback\n    }\n  };\n  __postData__(payload);\n}\n\nself.callUpdateAttrs = function (instance, ref, data) {\n  var payload = {\n    method: 'WxDebug.callUpdateAttrs',\n    params: {\n      instance: instance,\n      ref: ref,\n      data: data\n    }\n  };\n  __postData__(payload);\n}\n\nself.callUpdateStyle = function (instance, ref, data) {\n  var payload = {\n    method: 'WxDebug.callUpdateStyle',\n    params: {\n      instance: instance,\n      ref: ref,\n      data: data\n    }\n  };\n  __postData__(payload);\n}\n\nself.callRemoveElement = function (instance, ref) {\n  var payload = {\n    method: 'WxDebug.callRemoveElement',\n    params: {\n      instance: instance,\n      ref: ref\n    }\n  };\n  __postData__(payload);\n}\n\nself.callMoveElement = function (instance, ref, parentRef, index_str) {\n  var payload = {\n    method: 'WxDebug.callMoveElement',\n    params: {\n      instance: instance,\n      ref: ref,\n      parentRef: parentRef,\n      index_str: index_str\n    }\n  };\n  __postData__(payload);;\n}\n\nself.callAddEvent = function (instance, ref, event) {\n  var payload = {\n    method: 'WxDebug.callAddEvent',\n    params: {\n      instance: instance,\n      ref: ref,\n      event: event\n    }\n  };\n  __postData__(payload);\n}\n\nself.callRemoveEvent = function (instance, ref, event) {\n  var payload = {\n    method: 'WxDebug.callRemoveEvent',\n    params: {\n      instance: instance,\n      ref: ref,\n      event: event\n    }\n  };\n  __postData__(payload);\n}" : "";
  var environment = eventConstructor + "\n\n" + mockBrowserApi + "\n\n" + mockContextApi + "\n\n" + androidMockApi + "\n";
  if (env.jsframework) {
    environment += "importScripts('" + env.jsframework + "');\n";
    // environment += `importScripts('/lib/runtime/js-framework.js');\n`
  }
  return environment + "\n" + worker + "\n  ";
};

var generateWorkerEntry = function generateWorkerEntry(env) {
  var worker = fse.readFileSync(path.join(__dirname, "../worker/worker.js"));
  var androidMockApi = env.isLayoutAndSandbox ? "self.callCreateBody = function (instance, domStr) {\n  if (!domStr) return;\n  var payload = {\n    method: 'WxDebug.callCreateBody',\n    params: {\n      instance: instance,\n      domStr: domStr\n    }\n  };\n  __postData__(payload);\n};\n\nself.callUpdateFinish = function (instance, tasks, callback) {\n  var payload = {\n    method: 'WxDebug.callUpdateFinish',\n    params: {\n      instance: instance,\n      tasks: tasks,\n      callback: callback\n    }\n  };\n  __postData__(payload);\n};\n\nself.callCreateFinish = function (instance) {\n  var payload = {\n    method: 'WxDebug.callCreateFinish',\n    params: {\n      instance: instance\n    }\n  };\n  __postData__(payload);\n}\n\nself.callRefreshFinish = function (instance, tasks, callback) {\n  var payload = {\n    method: 'WxDebug.callRefreshFinish',\n    params: {\n      instance: instance,\n      tasks: tasks,\n      callback: callback\n    }\n  };\n  __postData__(payload);\n}\n\nself.callUpdateAttrs = function (instance, ref, data) {\n  var payload = {\n    method: 'WxDebug.callUpdateAttrs',\n    params: {\n      instance: instance,\n      ref: ref,\n      data: data\n    }\n  };\n  __postData__(payload);\n}\n\nself.callUpdateStyle = function (instance, ref, data) {\n  var payload = {\n    method: 'WxDebug.callUpdateStyle',\n    params: {\n      instance: instance,\n      ref: ref,\n      data: data\n    }\n  };\n  __postData__(payload);\n}\n\nself.callRemoveElement = function (instance, ref) {\n  var payload = {\n    method: 'WxDebug.callRemoveElement',\n    params: {\n      instance: instance,\n      ref: ref\n    }\n  };\n  __postData__(payload);\n}\n\nself.callMoveElement = function (instance, ref, parentRef, index_str) {\n  var payload = {\n    method: 'WxDebug.callMoveElement',\n    params: {\n      instance: instance,\n      ref: ref,\n      parentRef: parentRef,\n      index_str: index_str\n    }\n  };\n  __postData__(payload);;\n}\n\nself.callAddEvent = function (instance, ref, event) {\n  var payload = {\n    method: 'WxDebug.callAddEvent',\n    params: {\n      instance: instance,\n      ref: ref,\n      event: event\n    }\n  };\n  __postData__(payload);\n}\n\nself.callRemoveEvent = function (instance, ref, event) {\n  var payload = {\n    method: 'WxDebug.callRemoveEvent',\n    params: {\n      instance: instance,\n      ref: ref,\n      event: event\n    }\n  };\n  __postData__(payload);\n}" : "";
  var environment = eventConstructor + "\n  \n" + mockBrowserApi + "\n\n" + mockContextApi + "\n\n" + androidMockApi + "\n\nself.$$frameworkFlag = {};\n";
  if (env.jsframework) {
    environment += "importScripts('" + env.jsframework + "');\n";
    // environment += `importScripts('/lib/runtime/js-framework.js');\n`
  }
  if (env.importScripts && env.importScripts.length > 0) {
    env.importScripts.forEach(function (script) {
      environment += "importScripts('" + script + "');\n";
    });
  }
  if (env.sourceUrl) {
    environment += "importScripts('" + env.sourceUrl + "');\n";
  }
  return "\n" + environment + "\n" + worker + "\n  ";
};

var pickDomain = function pickDomain(str) {
  if (/file:\/\/\//.test(str)) {
    return "local";
  }
  if (/http(s)?/.test(str)) {
    return URL.parse(str).hostname;
  }
};

module.exports = {
  bundleWrapper: bundleWrapper,
  transformUrlToLocalUrl: transformUrlToLocalUrl,
  generateSandboxWorkerEntry: generateSandboxWorkerEntry,
  generateWorkerEntry: generateWorkerEntry,
  pickDomain: pickDomain
};