String.prototype.replaceAll = function ( search, replacement ){
  var target = this;
  return target.split(search).join(replacement);
};
module.exports = function (){
  var newOntologyCounter = 1;
  var app = {},
    graph = webvowl.graph(),
    options = graph.graphOptions(),
    languageTools = webvowl.util.languageTools(),
    GRAPH_SELECTOR = "#graph",
    // Modules for the webvowl app
    exportMenu = require("./menu/exportMenu")(graph),
    filterMenu = require("./menu/filterMenu")(graph),
    gravityMenu = require("./menu/gravityMenu")(graph),
    modeMenu = require("./menu/modeMenu")(graph),
    debugMenu = require("./menu/debugMenu")(graph),
    ontologyMenu = require("./menu/ontologyMenu")(graph),
    pauseMenu = require("./menu/pauseMenu")(graph),
    resetMenu = require("./menu/resetMenu")(graph),
    searchMenu = require("./menu/searchMenu")(graph),
    navigationMenu = require("./menu/navigationMenu")(graph),
    zoomSlider = require("./menu/zoomSlider")(graph),
    sidebar = require("./sidebar")(graph),
    leftSidebar = require("./leftSidebar")(graph),
    editSidebar = require("./editSidebar")(graph),
    configMenu = require("./menu/configMenu")(graph),
    loadingModule = require("./loadingModule")(graph),
    warningModule = require("./warningModule")(graph),
    directInputMod = require("./directInputModule")(graph),
    
    
    // Graph modules
    colorExternalsSwitch = webvowl.modules.colorExternalsSwitch(graph),
    compactNotationSwitch = webvowl.modules.compactNotationSwitch(graph),
    datatypeFilter = webvowl.modules.datatypeFilter(),
    disjointFilter = webvowl.modules.disjointFilter(),
    emptyLiteralFilter = webvowl.modules.emptyLiteralFilter(),
    // OUR CUSTOM FILTER AND HANDLER
    filterAndHandle = webvowl.modules.filterAndHandle(graph),
    focuser = webvowl.modules.focuser(graph),
    nodeDegreeFilter = webvowl.modules.nodeDegreeFilter(filterMenu),
    nodeScalingSwitch = webvowl.modules.nodeScalingSwitch(graph),
    objectPropertyFilter = webvowl.modules.objectPropertyFilter(),
    pickAndPin = webvowl.modules.pickAndPin(),
    selectionDetailDisplayer = webvowl.modules.selectionDetailsDisplayer(sidebar.updateSelectionInformation),
    statistics = webvowl.modules.statistics(),
    subclassFilter = webvowl.modules.subclassFilter(),
    setOperatorFilter = webvowl.modules.setOperatorFilter();
  
  
  app.getOptions = function (){
    return webvowl.opts;
  };
  app.getGraph = function (){
    return webvowl.gr;
  };
  // app.afterInitializationCallback=undefined;
  
  
  var executeFileDrop = false;
  var wasMessageToShow = false;
  var firstTime = false;
  
  function addFileDropEvents( selector ){
    var node = d3.select(selector);
    
    node.node().ondragover = function ( e ){
      e.preventDefault();

      d3.select("#dragDropContainer").classed("hidden", false);
      // get svg size
      var w = graph.options().width();
      var h = graph.options().height();
      
      // get event position; (using clientX and clientY);
      var cx = e.clientX;
      var cy = e.clientY;
      
      if ( firstTime === false ) {
        var state = d3.select("#loading-info").classed("hidden");
        wasMessageToShow = !state;
        firstTime = true;
        d3.select("#loading-info").classed("hidden", true); // hide it so it does not conflict with drop event
        var bb=d3.select("#drag_msg").node().getBoundingClientRect();
        var hs = bb.height;
        var ws = bb.width;
        
        var icon_scale=Math.min(hs,ws);
        icon_scale/=100;
        
        d3.select("#drag_icon_group").attr("transform", "translate ( " + 0.25 * ws + " " + 0.25 * hs + ")");
        d3.select("#drag_icon").attr("transform","matrix ("+icon_scale+",0,0,"+icon_scale+",0,0)");
        d3.select("#drag_icon_drop").attr("transform","matrix ("+icon_scale+",0,0,"+icon_scale+",0,0)");
      }
      
      
      if ( (cx > 0.25 * w && cx < 0.75 * w) && (cy > 0.25 * h && cy < 0.75 * h) ) {
        
        d3.select("#drag_msg_text").node().innerHTML = "Drop it here.";
        d3.select("#drag_msg").style("background-color", "#67bc0f");
        d3.select("#drag_msg").style("color", "#000000");
        executeFileDrop = true;
        // d3.select("#drag_svg").transition()
        //   .duration(100)
        //   // .attr("-webkit-transform", "rotate(90)")
        //   // .attr("-moz-transform",    "rotate(90)")
        //   // .attr("-o-transform",      "rotate(90)")
        //   .attr("transform",         "rotate(90)");
  
        d3.select("#drag_icon").classed("hidden",true);
        d3.select("#drag_icon_drop").classed("hidden",false);
  
  
      } else {
        d3.select("#drag_msg_text").node().innerHTML = "Drag ontology file here.";
        d3.select("#drag_msg").style("background-color", "#fefefe");
        d3.select("#drag_msg").style("color", "#000000");
        executeFileDrop = false;
  
        d3.select("#drag_icon").classed("hidden",false);
        d3.select("#drag_icon_drop").classed("hidden",true);
        
        
        // d3.select("#drag_svg").transition()
        //   .duration(100)
        //   // .attr("-webkit-transform", "rotate(0)")
        //   // .attr("-moz-transform",    "rotate(0)")
        //   // .attr("-o-transform",      "rotate(0)")
        //   .attr("transform",         "rotate(0)");
        //
      }
      
    };
    node.node().ondrop = function ( ev ){
      ev.preventDefault();
      firstTime = false;
      if ( executeFileDrop ) {
        if ( ev.dataTransfer.items ) {
          if ( ev.dataTransfer.items.length === 1 ) {
            if ( ev.dataTransfer.items[0].kind === 'file' ) {
              var file = ev.dataTransfer.items[0].getAsFile();
              graph.options().loadingModule().fromFileDrop(file.name, file);
            }
          }
          else {
            //  >> WARNING not multiple file uploaded;
            graph.options().warningModule().showMultiFileUploadWarning();
          }
        }
      }
      d3.select("#dragDropContainer").classed("hidden", true);
    };
    
    node.node().ondragleave = function ( e ){
      var w = graph.options().width();
      var h = graph.options().height();
      
      // get event position; (using clientX and clientY);
      var cx = e.clientX;
      var cy = e.clientY;
      
      var hidden = false;
      firstTime = false;
      
      if ( cx < 0.1 * w || cx > 0.9 * w ) hidden = true;
      if ( cy < 0.1 * h || cy > 0.9 * h ) hidden = true;
      d3.select("#dragDropContainer").classed("hidden", hidden);
      
      d3.select("#loading-info").classed("hidden", !wasMessageToShow); // show it again
      // check if it should be visible
      var should_show=graph.options().loadingModule().getMessageVisibilityStatus();
      if (should_show===false){
        d3.select("#loading-info").classed("hidden", true); // hide it
      }
    };
    
  }
  
  
  app.initialize = function (){
    addFileDropEvents(GRAPH_SELECTOR);
    
    window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || function ( f ){
        return setTimeout(f, 1000 / 60);
      }; // simulate calling code 60
    window.cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame || function ( requestID ){
        clearTimeout(requestID);
      }; //fall back
    
    options.graphContainerSelector(GRAPH_SELECTOR);
    options.selectionModules().push(focuser);
    options.selectionModules().push(selectionDetailDisplayer);
    options.selectionModules().push(pickAndPin);
    options.selectionModules().push(filterAndHandle);

    options.filterModules().push(emptyLiteralFilter);
    options.filterModules().push(statistics);
    
    options.filterModules().push(nodeDegreeFilter);
    options.filterModules().push(datatypeFilter);
    // OUR CUSTOM FILTER AND HANDLER
    options.filterModules().push(filterAndHandle);
    options.filterModules().push(objectPropertyFilter);
    options.filterModules().push(subclassFilter);
    options.filterModules().push(disjointFilter);
    options.filterModules().push(setOperatorFilter);
    options.filterModules().push(nodeScalingSwitch);
    options.filterModules().push(compactNotationSwitch);
    options.filterModules().push(colorExternalsSwitch);
    
    d3.select(window).on("resize", adjustSize);
    
    exportMenu.setup();
    gravityMenu.setup();
    filterMenu.setup(datatypeFilter, filterAndHandle, objectPropertyFilter, subclassFilter, disjointFilter, setOperatorFilter, nodeDegreeFilter);
    modeMenu.setup(pickAndPin, nodeScalingSwitch, compactNotationSwitch, colorExternalsSwitch);
    pauseMenu.setup();
    sidebar.setup();
    loadingModule.setup();
    leftSidebar.setup();
    editSidebar.setup();
    debugMenu.setup();
    var agentVersion = getInternetExplorerVersion();
    if ( agentVersion > 0 && agentVersion <= 11 ) {
      console.log("Agent version " + agentVersion);
      console.log("This agent is not supported");
      d3.select("#browserCheck").classed("hidden", false);
      d3.select("#killWarning").classed("hidden", true);
      d3.select("#optionsArea").classed("hidden", true);
      d3.select("#logo").classed("hidden", true);
    } else {
      d3.select("#logo").classed("hidden", false);
      if ( agentVersion === 12 ) {
        // allow Mircosoft Edge Browser but with warning
        d3.select("#browserCheck").classed("hidden", false);
        d3.select("#killWarning").classed("hidden", false);
      } else {
        d3.select("#browserCheck").classed("hidden", true);
      }
      
      resetMenu.setup([gravityMenu, filterMenu, modeMenu, focuser, selectionDetailDisplayer, pauseMenu]);
      searchMenu.setup();
      navigationMenu.setup();
      zoomSlider.setup();
      
      // give the options the pointer to the some menus for import and export
      options.literalFilter(emptyLiteralFilter);
      options.nodeDegreeFilter(nodeDegreeFilter);
      options.loadingModule(loadingModule);
      options.filterMenu(filterMenu);
      options.modeMenu(modeMenu);
      options.gravityMenu(gravityMenu);
      options.pausedMenu(pauseMenu);
      options.pickAndPinModule(pickAndPin);
      options.resetMenu(resetMenu);
      options.searchMenu(searchMenu);
      options.ontologyMenu(ontologyMenu);
      options.navigationMenu(navigationMenu);
      options.sidebar(sidebar);
      options.leftSidebar(leftSidebar);
      options.editSidebar(editSidebar);
      options.exportMenu(exportMenu);
      options.graphObject(graph);
      options.zoomSlider(zoomSlider);
      options.warningModule(warningModule);
      options.directInputModule(directInputMod);
      options.datatypeFilter(datatypeFilter);
      options.filterAndHandle(filterAndHandle);

      options.objectPropertyFilter(objectPropertyFilter);
      options.subclassFilter(subclassFilter);
      options.setOperatorFilter(setOperatorFilter);
      options.disjointPropertyFilter(disjointFilter);
      options.focuserModule(focuser);
      options.colorExternalsModule(colorExternalsSwitch);
      options.compactNotationModule(compactNotationSwitch);
      
      ontologyMenu.setup(loadOntologyFromText);
      configMenu.setup();
      
      leftSidebar.showSidebar(0);
      leftSidebar.hideCollapseButton(true);
      
      
      graph.start();
      
      var modeOp = d3.select("#modeOfOperationString");
      modeOp.style("font-size", "0.6em");
      modeOp.style("font-style", "italic");
      
      adjustSize();
      var defZoom;
      var w = graph.options().width();
      var h = graph.options().height();
      defZoom = Math.min(w, h) / 1000;
      
      var hideDebugOptions = true;
      if ( hideDebugOptions === false ) {
        graph.setForceTickFunctionWithFPS();
      }
      
      graph.setDefaultZoom(defZoom);
      d3.selectAll(".debugOption").classed("hidden", hideDebugOptions);
      
      // prevent backspace reloading event
      var htmlBody = d3.select("body");
      d3.select(document).on("keydown", function ( e ){
        if ( d3.event.keyCode === 8 && d3.event.target === htmlBody.node() ) {
          // we could add here an alert
          d3.event.preventDefault();
        }
        // using ctrl+Shift+d as debug option
        if ( d3.event.ctrlKey && d3.event.shiftKey && d3.event.keyCode === 68 ) {
          graph.options().executeHiddenDebugFeatuers();
          d3.event.preventDefault();
        }
      });
      if ( d3.select("#maxLabelWidthSliderOption") ) {
        var setValue = !graph.options().dynamicLabelWidth();
        d3.select("#maxLabelWidthSlider").node().disabled = setValue;
        d3.select("#maxLabelWidthvalueLabel").classed("disabledLabelForSlider", setValue);
        d3.select("#maxLabelWidthDescriptionLabel").classed("disabledLabelForSlider", setValue);
      }
      
      d3.select("#blockGraphInteractions").style("position", "absolute")
        .style("top", "0")
        .style("background-color", "#bdbdbd")
        .style("opacity", "0.5")
        .style("pointer-events", "auto")
        .style("width", graph.options().width() + "px")
        .style("height", graph.options().height() + "px")
        .on("click", function (){
          d3.event.preventDefault();
          d3.event.stopPropagation();
        })
        .on("dblclick", function (){
          d3.event.preventDefault();
          d3.event.stopPropagation();
        });
      
      d3.select("#direct-text-input").on("click", function (){
        directInputMod.setDirectInputMode();
      });
      d3.select("#blockGraphInteractions").node().draggable = false;
      options.prefixModule(webvowl.util.prefixTools(graph));
      adjustSize();
      sidebar.updateOntologyInformation(undefined, statistics);
      loadingModule.parseUrlAndLoadOntology(); // loads automatically the ontology provided by the parameters
      options.debugMenu(debugMenu);
      debugMenu.updateSettings();
      
      // connect the reloadCachedVersionButton
      d3.select("#reloadSvgIcon").on("click", function (){
        if ( d3.select("#reloadSvgIcon").node().disabled === true ) {
          graph.options().ontologyMenu().clearCachedVersion();
          return;
        }
        d3.select("#reloadCachedOntology").classed("hidden", true);
        graph.options().ontologyMenu().reloadCachedOntology();
        
      });
      // add the initialized objects
      webvowl.opts = options;
      webvowl.gr = graph;
      
    }
  };
  
  
  function loadOntologyFromText( jsonText, filename, alternativeFilename ){
    d3.select("#reloadCachedOntology").classed("hidden", true);
    pauseMenu.reset();
    graph.options().navigationMenu().hideAllMenus();
    
    if ( (jsonText === undefined && filename === undefined) || (jsonText.length === 0) ) {
      loadingModule.notValidJsonFile();
      return;
    }
    graph.editorMode(); // updates the checkbox
    var data;
    if ( jsonText ) {
      // validate JSON FILE
      var validJSON;
      try {
        data = JSON.parse(jsonText);
        validJSON = true;
      } catch ( e ) {
        validJSON = false;
      }
      if ( validJSON === false ) {
        // the server output is not a valid json file
        loadingModule.notValidJsonFile();
        return;
      }
      
      if ( !filename ) {
        // First look if an ontology title exists, otherwise take the alternative filename
        var ontologyNames = data.header ? data.header.title : undefined;
        var ontologyName = languageTools.textInLanguage(ontologyNames);
        
        if ( ontologyName ) {
          filename = ontologyName;
        } else {
          filename = alternativeFilename;
        }
      }
    }
    
    
    // check if we have graph data
    var classCount = 0;
    if ( data.class !== undefined ) {
      classCount = data.class.length;
    }
    
    var loadEmptyOntologyForEditing = false;
    if ( location.hash.indexOf("#new_ontology") !== -1 ) {
      loadEmptyOntologyForEditing = true;
      newOntologyCounter++;
      d3.select("#empty").node().href = "#opts=editorMode=true;#new_ontology" + newOntologyCounter;
    }
    if ( classCount === 0 && graph.editorMode() === false && loadEmptyOntologyForEditing === false ) {
      // generate message for the user;
      loadingModule.emptyGraphContentError();
    } else {
      loadingModule.validJsonFile();
      ontologyMenu.setCachedOntology(filename, jsonText);
      exportMenu.setJsonText(jsonText);
      options.data(data);
      graph.options().loadingModule().setPercentMode();
      if ( loadEmptyOntologyForEditing === true ) {
        graph.editorMode(true);
        
      }
      graph.load();
      sidebar.updateOntologyInformation(data, statistics);
      exportMenu.setFilename(filename);
      graph.updateZoomSliderValueFromOutside();
      adjustSize();
      
      var flagOfCheckBox = d3.select("#editorModeModuleCheckbox").node().checked;
      graph.editorMode(flagOfCheckBox);// update gui
      
    }
  }
  
  function adjustSize(){
    var graphContainer = d3.select(GRAPH_SELECTOR),
      svg = graphContainer.select("svg"),
      height = window.innerHeight - 40,
      width = window.innerWidth - (window.innerWidth * 0.22);
    
    if ( sidebar.getSidebarVisibility() === "0" ) {
      height = window.innerHeight - 40;
      width = window.innerWidth;
    }
    
    directInputMod.updateLayout();
    d3.select("#blockGraphInteractions").style("width", window.innerWidth + "px");
    d3.select("#blockGraphInteractions").style("height", window.innerHeight + "px");
    
    d3.select("#WarningErrorMessagesContainer").style("width", width + "px");
    d3.select("#WarningErrorMessagesContainer").style("height", height + "px");
    
    d3.select("#WarningErrorMessages").style("max-height", (height - 12) + "px");
    
    graphContainer.style("height", height + "px");
    svg.attr("width", width)
      .attr("height", height);
    
    options.width(width)
      .height(height);
    
    graph.updateStyle();
    
    if ( isTouchDevice() === true ) {
      if ( graph.isEditorMode() === true )
        d3.select("#modeOfOperationString").node().innerHTML = "touch able device detected";
      graph.setTouchDevice(true);
      
    } else {
      if ( graph.isEditorMode() === true )
        d3.select("#modeOfOperationString").node().innerHTML = "point & click device detected";
      graph.setTouchDevice(false);
    }
    
    d3.select("#loadingInfo-container").style("height", 0.5 * (height - 80) + "px");
    loadingModule.checkForScreenSize();
    
    adjustSliderSize();
    // update also the padding options of loading and the logo positions;
    var warningDiv = d3.select("#browserCheck");
    if ( warningDiv.classed("hidden") === false ) {
      var offset = 10 + warningDiv.node().getBoundingClientRect().height;
      d3.select("#logo").style("padding", offset + "px 10px");
    } else {
      // remove the dynamic padding from the logo element;
      d3.select("#logo").style("padding", "10px");
    }
    
    // scrollbar tests;
    var element = d3.select("#menuElementContainer").node();
    var maxScrollLeft = element.scrollWidth - element.clientWidth;
    var leftButton = d3.select("#scrollLeftButton");
    var rightButton = d3.select("#scrollRightButton");
    if ( maxScrollLeft > 0 ) {
      // show both and then check how far is bar;
      rightButton.classed("hidden", false);
      leftButton.classed("hidden", false);
      navigationMenu.updateScrollButtonVisibility();
    } else {
      // hide both;
      rightButton.classed("hidden", true);
      leftButton.classed("hidden", true);
    }
    
    // adjust height of the leftSidebar element;
    editSidebar.updateElementWidth();
    
    
    var hs = d3.select("#drag_msg").node().getBoundingClientRect().height;
    var ws = d3.select("#drag_msg").node().getBoundingClientRect().width;
    d3.select("#drag_icon_group").attr("transform", "translate ( " + 0.25 * ws + " " + 0.25 * hs + ")");
    
  }
  
  function adjustSliderSize(){
    // TODO: refactor and put this into the slider it self
    var height = window.innerHeight - 40;
    var fullHeight = height;
    var zoomOutPos = height - 30;
    var sliderHeight = 150;
    
    // assuming DOM elements are generated in the index.html
    // todo: refactor for independent usage of graph and app
    if ( fullHeight < 150 ) {
      // hide the slider button;
      d3.select("#zoomSliderParagraph").classed("hidden", true);
      d3.select("#zoomOutButton").classed("hidden", true);
      d3.select("#zoomInButton").classed("hidden", true);
      d3.select("#centerGraphButton").classed("hidden", true);
      return;
    }
    d3.select("#zoomSliderParagraph").classed("hidden", false);
    d3.select("#zoomOutButton").classed("hidden", false);
    d3.select("#zoomInButton").classed("hidden", false);
    d3.select("#centerGraphButton").classed("hidden", false);
    
    var zoomInPos = zoomOutPos - 20;
    var centerPos = zoomInPos - 20;
    if ( fullHeight < 280 ) {
      // hide the slider button;
      d3.select("#zoomSliderParagraph").classed("hidden", true);//var sliderPos=zoomOutPos-sliderHeight;
      d3.select("#zoomOutButton").style("top", zoomOutPos + "px");
      d3.select("#zoomInButton").style("top", zoomInPos + "px");
      d3.select("#centerGraphButton").style("top", centerPos + "px");
      return;
    }
    
    var sliderPos = zoomOutPos - sliderHeight;
    zoomInPos = sliderPos - 20;
    centerPos = zoomInPos - 20;
    d3.select("#zoomSliderParagraph").classed("hidden", false);
    d3.select("#zoomOutButton").style("top", zoomOutPos + "px");
    d3.select("#zoomInButton").style("top", zoomInPos + "px");
    d3.select("#centerGraphButton").style("top", centerPos + "px");
    d3.select("#zoomSliderParagraph").style("top", sliderPos + "px");
  }
  
  function isTouchDevice(){
    try {
      document.createEvent("TouchEvent");
      return true;
    } catch ( e ) {
      return false;
    }
  }
  
  
  function getInternetExplorerVersion(){
    var ua,
      re,
      rv = -1;
    
    // check for edge
    var isEdge = /(?:\b(MS)?IE\s+|\bTrident\/7\.0;.*\s+rv:|\bEdge\/)(\d+)/.test(navigator.userAgent);
    if ( isEdge ) {
      rv = parseInt("12");
      return rv;
    }
    
    var isIE11 = /Trident.*rv[ :]*11\./.test(navigator.userAgent);
    if ( isIE11 ) {
      rv = parseInt("11");
      return rv;
    }
    if ( navigator.appName === "Microsoft Internet Explorer" ) {
      ua = navigator.userAgent;
      re = new RegExp("MSIE ([0-9]{1,}[\\.0-9]{0,})");
      if ( re.exec(ua) !== null ) {
        rv = parseFloat(RegExp.$1);
      }
    } else if ( navigator.appName === "Netscape" ) {
      ua = navigator.userAgent;
      re = new RegExp("Trident/.*rv:([0-9]{1,}[\\.0-9]{0,})");
      if ( re.exec(ua) !== null ) {
        rv = parseFloat(RegExp.$1);
      }
    }
    return rv;
  }
  
  return app;
}
;
