function JSEditor() {
  this.isInit = false;
	this.curFunc = null;
	this.editor = null;
	this.funcLib = {};

	let that = this;

	this.init = function() {

    let htmlLib = "";

    /*for(var i=0;i<TB_SysFuncs.pageFuncs.length;i++) {
      var func = TB_SysFuncs.pageFuncs[i];
      func.type = "pageFunc";
      var funcId = "page_func_"+func.name;
      this.funcLib[funcId] = func;
      var comment = func.comment;
      if(!comment) comment = func.name;
      $("#jsEditor_lib_pageFuncs_items").append("<div func-id=\""
        +funcId+"\" class=\"JSLibFunc\" draggable=\"false\">"
        +"<img src=\"images/func.png\" /><label>"+comment+"</label></div>");
    }

    $(".JSLibFunc").mousedown(function() {
      var e = window.event;
      tfpDesigner.mouseInfo.downX = e.clientX;
      tfpDesigner.mouseInfo.downY = e.clientY;
      tfpDesigner.mouseInfo.dragType = "CreateFunc";
      tfpDesigner.mouseInfo.funcName = $(this).attr("func-name");
      tfpDesigner.mouseInfo.isDrag = true;
      $("#divCompoentDragging").find("img").attr("src", $(this).find("img").attr("src"));
      $("#divCompoentDragging").find("label").text($(this).find("label").text());
    });*/

    if(tfp.curPage.dataModel.jsFiles) {
      for(var i=0;i<tfp.curPage.dataModel.jsFiles.length;i++) {
        let filePath = tfp.curPage.dataModel.jsFiles[i];
        this.addJSFile(filePath);
      }
    }

    if(tfp.curPage.dataModel.jsGlobalVars) {
      for(var i=0;i<tfp.curPage.dataModel.jsGlobalVars.length;i++) {
        let globalVar = tfp.curPage.dataModel.jsGlobalVars[i];
        this.addGlobalVar(globalVar);
      }
    }

    if(tfp.curPage.dataModel.jsFuncs) {
      for(var i=0;i<tfp.curPage.dataModel.jsFuncs.length;i++) {
        let func = tfp.curPage.dataModel.jsFuncs[i];
        this.addFunc(func);
      }
    }

    this.isInit = true;
	};

  this.addJSFile = function(filePath) {
    $("#jsEditor_jsfiles").append("<div file-path=\""+filePath
      +"\"><div style=\"color:#0099FF;\">·&nbsp;"+tb.getFileName(filePath)+"</div>"
      +"<img src=\"images/del.png\" style=\"float:right;margin-left:0;"
      +"margin-top:4px;display:none;\" title=\"删除\" onclick=\""
      +"jsEditor.deleteJSFile($(this).parent())\"></div>");

    let newDiv = $("#jsEditor_jsfiles").find("[file-path]").last();

    newDiv.click(function() {
      openPage(tb.getFileName(filePath), "taskbuilder-code-editor/index.html?type=js&path=/web"+filePath);
    });

    newDiv.mouseover(function() {
      $(this).find("img").show();
    });

    newDiv.mouseout(function() {
      $(this).find("img").hide();
    });
  };

  this.addNewJSFile = function() {
    var e = window.event;
    e.stopPropagation();

    let args = {
      fileTypes: ["js"]
    };

    openDialog("添加外部脚本文件", "taskbuilder-tfp-designer/PathPicker.html", "480px", "480px", 
      JSON.stringify(args), function(filePath) {

      if(filePath.startsWith("/web/")) filePath = filePath.substr(4);

      if(tfp.curPage.dataModel.jsFiles) {
        for(var i=0;i<tfp.curPage.dataModel.jsFiles.length;i++) {
          let jsFile = tfp.curPage.dataModel.jsFiles[i];
          if(jsFile==filePath) {
            showMsg("该文件已添加！");
            return;
          }
        }
      }

      if(!tfp.curPage.dataModel.jsFiles) tfp.curPage.dataModel.jsFiles = [];
      tfp.curPage.dataModel.jsFiles.push(filePath);
      
      that.addJSFile(filePath);
    });
  };

  this.deleteJSFile = function(divJSFile) {
    let filePath = divJSFile.attr("file-path");
    divJSFile.remove();
    if(tfp.curPage.dataModel.jsFiles) {
      for(var i=0;i<tfp.curPage.dataModel.jsFiles.length;i++) {
        let jsFile = tfp.curPage.dataModel.jsFiles[i];
        if(jsFile==filePath) {
          tfp.curPage.dataModel.jsFiles.splice(i, 1);
          return;
        }
      }
    }
  };

  this.addNewGlobalVar = function() {
    var e = window.event;
    e.stopPropagation();

    openDialog("添加全局变量", "taskbuilder-tfp-designer/addGlobalVar.html", "360px", "220px", null, function(ret) {
      if(tfp.curPage.dataModel.jsGlobalVars) {
        for(var i=0;i<tfp.curPage.dataModel.jsGlobalVars.length;i++) {
          let v = tfp.curPage.dataModel.jsGlobalVars[i];
          if(v.name==ret.name) {
            showMsg("该变量已添加！");
            return;
          }
        }
      }

      if(!tfp.curPage.dataModel.jsGlobalVars) tfp.curPage.dataModel.jsGlobalVars = [];
      tfp.curPage.dataModel.jsGlobalVars.push(ret);

      that.addGlobalVar(ret);
    });
  };

  this.addGlobalVar = function(globalVar) {
    $("#jsEditor_global_vars").append("<div var-name=\""+globalVar.name
      +"\"><div style=\"color:#0099FF;\">·&nbsp;"+globalVar.name+"</div>"
      +"<img src=\"images/del.png\" style=\"float:right;margin-left:0;"
      +"margin-top:4px;display:none;\" title=\"删除\" onclick=\""
      +"jsEditor.deleteGlobalVar($(this).parent())\"></div>");

    let newDiv = $("#jsEditor_global_vars").find("[var-name]").last();

    newDiv.click(function() {
    	let varName = $(this).attr("var-name");

    	if(tfp.curPage.dataModel.jsGlobalVars) {
        for(var i=0;i<tfp.curPage.dataModel.jsGlobalVars.length;i++) {
          let v = tfp.curPage.dataModel.jsGlobalVars[i];
          if(v.name==varName) {
            that.updateGlobalVar(v);
            return;
          }
        }
      }
    });

    newDiv.mouseover(function() {
      $(this).find("img").show();
    });

    newDiv.mouseout(function() {
      $(this).find("img").hide();
    });
  };

  this.updateGlobalVar = function(varInfo) {
  	openDialog("修改全局变量", "taskbuilder-tfp-designer/addGlobalVar.html", "360px", "220px", 
  		JSON.stringify(varInfo), function(ret) {
  		let varOldName = varInfo.name;
    	if(tfp.curPage.dataModel.jsGlobalVars) {
        for(var i=0;i<tfp.curPage.dataModel.jsGlobalVars.length;i++) {
          let v = tfp.curPage.dataModel.jsGlobalVars[i];
          if(v.name==ret.name && ret.name!=varOldName) {
            showMsg("该变量已添加！");
            return;
          }
        }
        for(var i=0;i<tfp.curPage.dataModel.jsGlobalVars.length;i++) {
          let v = tfp.curPage.dataModel.jsGlobalVars[i];
          if(v.name==varOldName) {
            v.name = ret.name;
            v.type = ret.type;
            v.value = ret.value;
            break;
          }
        }
      }
      let divVar = $("#jsEditor_global_vars").find("[var-name='"+varOldName+"']");
      divVar.attr("var-name", ret.name);
      divVar.find("div").html("·&nbsp;"+ret.name);
    });
  };

  this.deleteGlobalVar = function(divVar) {
    let varName = divVar.attr("var-name");
    divVar.remove();
    if(tfp.curPage.dataModel.jsGlobalVars) {
      for(var i=0;i<tfp.curPage.dataModel.jsGlobalVars.length;i++) {
        let v = tfp.curPage.dataModel.jsGlobalVars[i];
        if(v.name==varName) {
          tfp.curPage.dataModel.jsGlobalVars.splice(i, 1);
          return;
        }
      }
    }
  };

  this.addNewFunc = function() {
    var e = window.event;
    e.stopPropagation();

  	openDialog("添加函数", "taskbuilder-tfp-designer/addFunc.html", "320px", "400px", null, function(func) {
	  	if(tfp.curPage.dataModel.jsFuncs) {
	      for(var i=0;i<tfp.curPage.dataModel.jsFuncs.length;i++) {
	        let f = tfp.curPage.dataModel.jsFuncs[i];
	        if(f.name==func.name) {
	          showMsg("该函数已存在！");
	          return;
	        }
	      }
	    }
	    if(!tfp.curPage.dataModel.jsFuncs) tfp.curPage.dataModel.jsFuncs = [];
	    tfp.curPage.dataModel.jsFuncs.push(func);

      that.addFunc(func);
	    that.selectFunc(func);
    });
  };

  this.addFunc = function(func) {
    $("#jsEditor_funcs").append("<div func-name=\""+func.name+"\"><div>·&nbsp;"+func.name+"</div>"
      +"<img src=\"images/del.png\" style=\"float:right;margin-left:0;"
      +"margin-top:4px;display:none;\" title=\"删除\" onclick=\""
      +"jsEditor.deleteFunc($(this).parent())\"><img src=\"images/edit.png\" "
      +"style=\"float:right;margin-left:0;margin-right:3px;margin-top:4px;display:none;\" "
      +"title=\"修改\" onclick=\"jsEditor.updateFunc($(this).parent())\"></div>");
    let newDiv = $("#jsEditor_funcs").find("[func-name]").last();

    newDiv.click(function() {
      that.selectFunc(func);
    });

    newDiv.mouseover(function() {
      $(this).find("img").show();
    });

    newDiv.mouseout(function() {
      $(this).find("img").hide();
    });
  };

  this.selectFunc = function(func) {
    if(this.curFunc && this.curFunc.name == func.name) return;

  	$("#divJS_top").show();
  	$("#divJS_code").show();

    if(this.curFunc) {
      $("#jsEditor_funcs").find("[func-name='"
        +this.curFunc.name+"']").css("background-color", "");
      $("#jsEditor_funcs").find("[func-name='"
        +this.curFunc.name+"']").find("div").css("background-color", "");
      if(tfp.curPage.dataModel.jsFuncs) {
        let oldFunc = null;
        for(var i=0;i<tfp.curPage.dataModel.jsFuncs.length;i++) {
          let f = tfp.curPage.dataModel.jsFuncs[i];
          if(f.name==this.curFunc.name) {
            f.code = this.editor.getValue();
            f.code = f.code.substring(f.code.indexOf("{")+2, f.code.lastIndexOf("}"));
            break;
          }
        }
      }
    }
    this.curFunc = func; 
    $("#jsEditor_funcs").find("[func-name='"
      +this.curFunc.name+"']").css("background-color", "#555555");
    $("#jsEditor_funcs").find("[func-name='"
      +this.curFunc.name+"']").find("div").css("background-color", "#555555");

    if(!this.editor) {
      let options = {
        mode: "javascript",
        lineNumbers: true,
        styleActiveLine: true,
        matchBrackets: true,
        selectionPointer: true,
        keyMap: "sublime",
        foldGutter: true,
        autoCloseBrackets: true,
        autofocus: true,
        gutters: ["CodeMirror-lint-markers", "CodeMirror-linenumbers", "CodeMirror-foldgutter"],
        hintOptions: {completeSingle: false}
      };
      this.editor = CodeMirror(document.getElementById("divJS_code"), options);
      $("#divJS_code").find(".CodeMirror").css("width", "100%");
      $("#divJS_code").find(".CodeMirror").css("height", $("#divJS_code").height()+"px");
      
      if(!tfpDesigner.ternServer) tfpDesigner.ternServer = new CodeMirror.TernServer({defs: [jsdefs]});
      CodeMirror.on(this.editor, 'change', function(instance, object) {
        var str = object.text[0];
        if(str.length==1 && /^[a-zA-Z.]+$/.test(str) && str!="u" && str!="U") {
          CodeMirror.showHint(that.editor, CodeMirror.hint.custom);
          if(str==".") tfpDesigner.ternServer.complete(that.editor);
        }
      });
      this.editor.setOption("theme", "monokai");
      this.editor.setOption("extraKeys", {
        "Ctrl-I": function(cm) { tfpDesigner.ternServer.showType(cm); },
        "Ctrl-O": function(cm) { tfpDesigner.ternServer.showDocs(cm); },
        "Alt-.": function(cm) { tfpDesigner.ternServer.jumpToDef(cm); },
        "Alt-,": function(cm) { tfpDesigner.ternServer.jumpBack(cm); },
        "Ctrl-Q": function(cm) { tfpDesigner.ternServer.rename(cm); },
        "Ctrl-.": function(cm) { tfpDesigner.ternServer.selectName(cm); },
        "Ctrl-S": function(cm) { tfpDesigner.save(); }
      });
      this.editor.on("cursorActivity", function(cm) { tfpDesigner.ternServer.updateArgHints(cm); });
    }
    this.editor.setValue("");
    let funcCode = "function "+func.name+"(";
    for(var i=0;i<func.args.length;i++) {
    	if(i>0) funcCode += " , ";
    	funcCode += func.args[i].name;
    }
    funcCode += ") {\r\n";
    if(func.code) {
    	funcCode += func.code;
    } else {
    	funcCode += "\r\n";
    }
    funcCode += "}";
    this.editor.setValue(funcCode);
  };

  this.updateFunc = function(divFunc) {
    var e = window.event;
    e.stopPropagation();

    let funcName = divFunc.attr("func-name");
    let func = null;
  	
  	if(tfp.curPage.dataModel.jsFuncs) {
      for(var i=0;i<tfp.curPage.dataModel.jsFuncs.length;i++) {
        let f = tfp.curPage.dataModel.jsFuncs[i];
        if(f.name==funcName) {
          func = f;
          break;
        }
      }
    }

    if(!func) return;

    let funcTmp = {
    	name: func.name,
    	editType: func.editType,
    	args: func.args
    };

    if(func.comment) funcTmp.comment = func.comment;

    openDialog("修改函数", "taskbuilder-tfp-designer/addFunc.html", "320px", "400px", 
    	JSON.stringify(funcTmp), function(funcNew) {
	  	if(tfp.curPage.dataModel.jsFuncs) {
	      for(var i=0;i<tfp.curPage.dataModel.jsFuncs.length;i++) {
	        let f = tfp.curPage.dataModel.jsFuncs[i];
	        if(f.name==funcNew.name && funcNew.name!=func.name) {
	          showMsg("该函数已存在！");
	          return;
	        }
	      }
	      for(var i=0;i<tfp.curPage.dataModel.jsFuncs.length;i++) {
	        let f = tfp.curPage.dataModel.jsFuncs[i];
	        if(f.name==func.name) {
	          f.name = funcNew.name;
	          f.editType = funcNew.editType;
	          f.comment = funcNew.comment;
	          f.args = funcNew.args;
	          break;
	        }
	      }
	    }
	    
	    if(funcTmp.name!=funcNew.name) {
	      let funcDiv = $("#jsEditor_funcs").find("[func-name='"+funcTmp.name+"']");
	      funcDiv.attr("func-name", funcNew.name);
	      funcDiv.find("div").html("·&nbsp;"+funcNew.name);
	    }
	    if(that.curFunc && that.curFunc.name==funcNew.name) {
	    	let code = that.editor.getValue();
				code = code.substring(code.indexOf("{")+2, code.lastIndexOf("}"));
		    let funcCode = "function "+funcNew.name+"(";
		    for(var i=0;i<funcNew.args.length;i++) {
		    	if(i>0) funcCode += " , ";
		    	funcCode += funcNew.args[i].name;
		    }
		    funcCode += ") {\r\n";
		    funcCode += code;
		    funcCode += "}";
	    	that.editor.setValue(funcCode);
	    }
    });
  };

  this.deleteFunc = function(divFunc) {
    let funcName = divFunc.attr("func-name");
    divFunc.remove();
    if(tfp.curPage.dataModel.jsFuncs) {
      for(var i=0;i<tfp.curPage.dataModel.jsFuncs.length;i++) {
        let func = tfp.curPage.dataModel.jsFuncs[i];
        if(func.name==funcName) {
          tfp.curPage.dataModel.jsFuncs.splice(i, 1);
          break;
        }
      }
    }
    if(this.curFunc && this.curFunc.name==funcName) {
    	this.curFunc = null;
    	this.editor.setValue("");
	  	$("#divJS_top").hide();
	  	$("#divJS_code").hide();
    }
  };

  this.comment = function() {
	  var slt = this.editor.doc.listSelections()[0];
	  if(slt.anchor.ch==0&&slt.anchor.line==0&&
	    slt.head.ch==0&&slt.head.line==0) return;
	  if(slt.anchor.line==slt.head.line) {
	    this.editor.lineComment(slt.head, slt.anchor);
	  } else {
	    slt.head.ch = 1;
	    slt.anchor.ch = this.editor.doc.getLine(slt.anchor.line).length;
	    this.editor.blockComment(slt.head, slt.anchor);
	  }
	};

	this.uncomment = function() {
	  var slt = this.editor.doc.listSelections()[0];
	  if(slt.anchor.ch==0&&slt.anchor.line==0&&
	    slt.head.ch==0&&slt.head.line==0) return;
	  this.editor.uncomment(slt.head, slt.anchor);
	};

	this.toggleWrap = function(div) {
	  var lineWrapping = this.editor.getOption("lineWrapping");
	  if(lineWrapping) {
	    this.editor.setOption("lineWrapping", false);
	    $(div).css("background-color", "");
	    $(div).css("margin", "3px");
	    $(div).css("border", "0");
	  } else {
	    this.editor.setOption("lineWrapping", true);
	    $(div).css("background-color", "#333");
	    $(div).css("margin", "2px");
	    $(div).css("border", "1px solid #999999");
	  }
	};

	this.toggleKeyMapRef = function(div) {
	  /*if($("#divJSKeyMap").is(":visible")) {
	    $("#divCenter").css("right", "0px");
	    $("#divJSKeyMapTitle").hide();
	    $("#divJSKeyMap").hide();
	    $(".CodeMirror").css("width", (document.documentElement.clientWidth)+"px");
	    $(div).css("background-color", "");
	    $(div).css("margin", "3px");
	    $(div).css("border", "0");
	  } else {
	    $("#divCenter").css("right", "211px");
	    $(".CodeMirror").css("width", (document.documentElement.clientWidth-211)+"px");
	    $("#divJSKeyMapTitle").show();
	    $("#divJSKeyMap").show();
	    $(div).css("background-color", "#333");
	    $(div).css("margin", "2px");
	    $(div).css("border", "1px solid #999999");
	  }*/
	};

	this.saveCode = function() {
		let code = this.editor.getValue();
		code = code.substring(code.indexOf("{")+2, code.lastIndexOf("}"));
		this.curFunc.code = code;
		//tfpDesigner.save();
	};

	this.addLibFunc = function(pre, funcName) {
		let func = this.funcLib[funcName];
		if(!func) return;
		let code = funcName+"(";
		if(func.args) {
			for(var i=0;i<func.args.length;i++) {
				if(i>0) code += " , ";
				code += func.args[i].name;
			}
		}
		code += ")";
		this.editor.replaceSelection(code);
	};

  this.insertStatement = function() {
    var args = {
      clientType: tfp.curPage.client,
      components: [],
      metadatas: {}
    };
    for (let cptId in tfp.components) {
      let cptTmp = tfp.components[cptId];
      if(cptTmp.type=="Page") continue;
      args.components.push({ id: cptId, type: cptTmp.type });
      if(!args.metadatas[cptTmp.type]) args.metadatas[cptTmp.type] = tfp.type(cptTmp.type);
    }
    if (tfp.curPage.dataModel.jsFuncs) {
      args.funcs = [];
      for (var i = 0; i < tfp.curPage.dataModel.jsFuncs.length; i++) {
        let func = tfp.curPage.dataModel.jsFuncs[i];
        args.funcs.push({
          name: func.name,
          comment: func.comment,
          args: func.args
        });
      }
    }

    openDialog("选择函数", "taskbuilder-tfp-designer/FuncPicker.html", "680px", "560px", 
      JSON.stringify(args), function (ret) {
      let eventVal = ret;
      if (eventVal) {
        var slt = that.editor.doc.listSelections()[0];
        if(slt.anchor.line==0 || slt.head.line==0) {
          that.editor.setSelection({line:1, ch: 2}, {line:1, ch: 2});
        }
        that.editor.replaceSelection(eventVal);
        that.editor.focus();
      }
    });
  };
}

var jsEditor = new JSEditor();