/**
 * Strategy Map
 * https://github.com/jackietian/strategy-map
 *
 * Licensed under the MIT license:
 * http://www.opensource.org/licenses/MIT
 */
(function ($) {
  $.fn.strategyMap = function (options) {
    var opts = $.extend({}, $.fn.strategyMap.defaults, options);
    return new StrategyMap($(this), opts);
  };

  var dummyFunc = function () {
  };

  var undoStorageKey = 'undo-strategy-map-history';
  var redoStorageKey = 'redo-strategy-map-history';

  $.fn.strategyMap.defaults = {
    groupHeight: 320,
    groups: [],
    links: [],
    allowEdit: true,
    onAddNode: dummyFunc,
    onDeleteNode: dummyFunc,
    onClickNode: dummyFunc,
    onAddGroup: dummyFunc,
    onDeleteGroup: dummyFunc,
    onEditGroupName: dummyFunc,
    onLinkBtnClick: dummyFunc,
    onUnlinkBtnClick: dummyFunc,
    onSwapLinkBtnClick: dummyFunc,
    onClearLinksBtnClick: dummyFunc,
    onClearNodesBtnClick: dummyFunc,
    onClearAllBtnClick: dummyFunc,
    onDragEnd: dummyFunc,
    onResize: dummyFunc,
    onUnoRedo: dummyFunc
  };

  function StrategyMap($container, opts) {
    var self = this;

    self.draw = function () {
      $container.empty();
      $container.unbind();
      $container.addClass('strategyMap');

      if (!opts.allowEdit) {
        $container.addClass('viewMode');
        $container.append('<label>In view only mode</label>');
      }

      $container.append(generateGloablBtns(opts.groups, opts.links))
      $container.append(generateGroups(opts.groups));
      $container.append(generateLinks(opts.groups, opts.links));

      if (opts.allowEdit) {
        var clickableBtns =
          '#add-group-btn, ' +
          '.del-node-btn, ' +
          '.add-node-btn, ' +
          '.del-node-btn, ' +
          '.link-node-btn, ' +
          '#unselect-btn, ' +
          '#link-btn, ' +
          '#unlink-btn, ' +
          '#swap-link-btn, ' +
          '.del-group-btn, ' +
          '#clear-links-btn, ' +
          '#clear-nodes-btn, ' +
          '#clear-all-btn';

        var historyWatchedBtns =
          '#add-group-btn, ' +
          '.save-group-btn, ' +
          '.del-node-btn, ' +
          '.add-node-btn, ' +
          '.del-node-btn, ' +
          '#link-btn, ' +
          '#unlink-btn, ' +
          '#swap-link-btn, ' +
          '.del-group-btn, ' +
          '#clear-links-btn, ' +
          '#clear-nodes-btn, ' +
          '#clear-all-btn';

        $(historyWatchedBtns).click(function (el) {
          saveUndo(opts.groups, opts.links);
        });

        $container.on('click', 'span.edit-group-btn', editGroupBtnListener);
        $container.on('click', '.save-group-btn', function (el) {
          validateRedoUndoBtns($container);
          saveGroupBtnListner(el, opts.groups, opts.onEditGroupName);
        });
        $container.on('click', '#add-group-btn', function (el) {
          addGroupBtnListner($container, opts.groups, function (groups) {
            opts.groups = groups;
            opts.onAddGroup(groups);
          });
        });
        $container.on('click', '.add-node-btn', function (el) {
          addNodeBtnListner(el, opts.onAddNode);
        });
        $container.on('click', '.del-node-btn', function (el) {
          deleteNodeBtnListner(el, opts.groups, opts.links, opts.onDeleteNode);
        });
        $container.on('click', '.link-node-btn', function (el) {
          linkNodeBtnListner(el, $container, opts.links);
        });
        $container.on('click', '#unselect-btn', function (el) {
          unselectBtnListner(el, $container);
        });
        $container.on('click', '#link-btn', function (el) {
          linkBtnListner(el, $container, opts.links, opts.onLinkBtnClick);
        });
        $container.on('click', '#unlink-btn', function (el) {
          unlinkBtnListner(el, $container, opts.links, opts.onUnlinkBtnClick);
        });
        $container.on('click', '#swap-link-btn', function (el) {
          swaplinkBtnListner(el, $container, opts.links, opts.onSwapLinkBtnClick);
        });
        $container.on('click', 'i.del-group-btn', function (el) {
          deleteGroupBtnListner(el, $container, opts.groups, opts.links, opts.onDeleteGroup);
        });
        $container.on('click', '#clear-links-btn', function (el) {
          clearLinksBtnListner(opts.links, function (links) {
            opts.links = [];
            opts.onClearLinksBtnClick(links);
          });
        });
        $container.on('click', '#clear-nodes-btn', function (el) {
          clearNodesBtnListner(opts.groups, opts.links, function (groups, links) {
            opts.groups = groups;
            opts.links = links;
            opts.onClearNodesBtnClick(groups, links);
          });
        });
        $container.on('click', '#clear-all-btn', function (el) {
          clearAllBtnListner(opts.groups, opts.links, function (groups, links) {
            opts.groups = groups;
            opts.links = links;
            opts.onClearAllBtnClick(groups, links);
          });
        });

        setNodesDraggable($container, opts.groups, opts.links, function (groups) {
          opts.groups = groups;
          opts.onDragEnd(groups);
          validateRedoUndoBtns($container);
        }, function (nodeId) {
          var links = findRelatedLinksByNodeId(nodeId, opts.links);
          links.forEach(function (item) {
            var link = new Link(item);
            $container.append(link.update());
          })
        });

        setGroupDroppable(opts.groups, opts.groupHeight);

        setGroupResizable($container, opts.groups, opts.links, opts.groupHeight, function (targetGroupId, height) {
          //save history
          saveUndo(opts.groups, opts.links);

          var groupIndex = opts.groups.map(function (group) {
            return group.id.toString();
          }).indexOf(targetGroupId);

          opts.groups[groupIndex]['height'] = height;

          validateRedoUndoBtns($container);
          opts.onResize(opts.groups);
        }, function () {
          opts.links.forEach(function (item) {
            var link = new Link(item);
            $container.append(link.update());
          });
        });

        $('#undo-btn').click(function (el) {
          saveRedo(opts.groups, opts.links);
        });

        $('#undo-btn').click(function (el) {
          var map = undo();
          opts.groups = map.groups;
          opts.links = map.links;
          self.draw();
          opts.onUnoRedo(opts.groups, opts.links);
        });

        $('#redo-btn').click(function (el) {
          saveUndo(opts.groups, opts.links);
        });

        $('#redo-btn').click(function () {
          var map = redo();
          opts.groups = map.groups;
          opts.links = map.links;
          self.draw();
          opts.onUnoRedo(opts.groups, opts.links);
        });

        $container.on('click', clickableBtns, function (el) {
          validateBtns(el, $container, opts.gorups, opts.links);
        });
      }
    };

    self.addNewNode = function (nodeData, targetGroupId) {
      saveUndo(opts.groups, opts.links);
      var targetGroup = findGroupById(opts.groups, targetGroupId);
      targetGroup.nodes.push(nodeData);

      var node = new Node(nodeData);
      $('#' + targetGroupId).find('.data-container').append(node.render());
    }

    self.init = function () {
      initSessionStorage();
      self.draw();
    };

    self.init();

    window.addEventListener('resize', function () {
      opts.links.forEach(function (item) {
        var link = new Link(item);
        link.update();
        $container.append(link.render());
      })
    });
  }

  function generateGloablBtns(groups, links) {
    var globalBtns = '';

    globalBtns += '<ul class="list-inline list--actions">';
    globalBtns += '<li><button class="btn" id="add-group-btn"><i class="icon-add md-18"></i> group</li></button>';
    globalBtns += '<li>';
    if (countUndoHistory() > 0) {
      globalBtns += '<button class="btn" id="undo-btn"><i class="icon-undo md-18" ></i></button>';
    } else {
      globalBtns += '<button class="btn" id="undo-btn" disabled><i class="icon-undo md-18" ></i></button>';
    }
    if (countRedoHistory() > 0) {
      globalBtns += '<button class="btn" id="redo-btn"><i class="icon-redo md-18" ></i></button>';
    } else {
      globalBtns += '<button class="btn" id="redo-btn" disabled><i class="icon-redo md-18" ></i></button>';
    }
    globalBtns += '</li>';
    globalBtns += '<li><button class="btn" id="unselect-btn" disabled><i class="icon-select_all md-18"></i>unselect</button></li>';
    globalBtns += '<li><button class="btn" id="link-btn" disabled><i class="icon-link md-18"></i>Link</button></li>';
    globalBtns += '<li><button class="btn" id="unlink-btn" disabled><i class="icon-link md-18"></i> unlink</button></li>';
    globalBtns += '<li><button class="btn" id="swap-link-btn" disabled><i class="icon-swap_vert md-18 rotate-45"></i>direction</button></li>';

    globalBtns += '<li class="dropdown" id="clear-map-dropdown">';
    globalBtns += '<buton class="btn"><i class="icon-delete md-18"></i></buton>';
    globalBtns += '<div class="dropdown-content">';
    globalBtns += '<ul class="list list--dropdown">';
    // globalBtns += '<li class="header">remove</li>';
    if (countLinks(links) > 0) {
      globalBtns += '<li><button class="btn" id="clear-links-btn"><i class="icon-link md-18"></i> links</button></li>';
    } else {
      globalBtns += '<li><button class="btn" id="clear-links-btn" disabled><i class="icon-link md-18"></i> links</button></li>';
    }

    if (countNodes(groups) > 0) {
      globalBtns += '<li><button class="btn" id="clear-nodes-btn"><i class="icon-sitemap md-18 rotate-270"></i> nodes</button></li>';
    } else {
      globalBtns += '<li><button class="btn" id="clear-nodes-btn" disabled><i class="icon-sitemap md-18 rotate-270"></i> nodes</button></li>';
    }

    if (groups.length > 0) {
      globalBtns += '<li><button class="btn" id="clear-all-btn"><i class="icon-view_agenda md-18"></i> groups</button></li>';
    } else {
      globalBtns += '<li><button class="btn" id="clear-all-btn" disabled><i class="icon-view_agenda md-18"></i> groups</button></li>';
    }
    globalBtns += '</ul></div></li></ul>';

    return globalBtns;
  }

  function generateGroups(groups) {
    var result = '';
    groups.forEach(function (item) {
      var group = new Group(item);
      result += group.render();
    });
    return result;
  }

  function generateLinks(groups, links) {
    var result = '';
    var allNodes = queryAllNodes(groups);
    links.forEach(function (item) {
      var link = new Link(item);
      if (validateLink(link, allNodes)) {
        result += link.render();
      }
    });
    return result;
  }

  function validateLink(link, nodes) {
    var result = false;
    var nodeIds = nodes.map(function (node) {
      return node.id.toString();
    });

    if (nodeIds.indexOf(link.from.toString()) > -1 && nodeIds.indexOf(link.to.toString()) > -1) {
      result = true;
    }
    return result;
  }

  function setNodesDraggable($container, groups, links, dragEndCallBack, refreshLinks) {
    interact('.node')
      .draggable({
        restrict: {
          restriction: function () {
            return $.extend({}, $container.offset(), {
              right: $container.offset().left + $container.outerWidth() - 220, // node width
              bottom: $container.offset().top + $container.outerHeight() - 120 // node height
            });
          },
          elementRect: {top: 0, left: 0, bottom: 0, right: 0}
        },
        onmove: onDragMove,
        onend: onDragEnd
      });

    function onDragMove(event) {
      var target = event.target,
        nodeId = target.id,
        x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx,
        y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;

      target.style.webkitTransform =
        target.style.transform =
          'translate(' + x + 'px, ' + y + 'px)';

      target.setAttribute('data-x', x);
      target.setAttribute('data-y', y);

      refreshLinks(nodeId);
    }

    function onDragEnd(event) {
      var target = event.target,
        nodeId = target.id,
        left = parseInt(target.style.left.replace('px', ''), 10) + getTranslateXValue(target.style.transform),
        top = parseInt(target.style.top.replace('px', ''), 10) + getTranslateYValue(target.style.transform);

      target.style.left = left + 'px';
      target.style.top = top + 'px';

      target.style.webkitTransform =
        target.style.transform =
          'translate(0, 0)';

      target.setAttribute('data-x', 0);
      target.setAttribute('data-y', 0);

      //save history
      if (target.getAttribute('origin-group') && target.getAttribute('target-group')) {
        var originGroupId = target.getAttribute('origin-group'),
          targetGroupId = target.getAttribute('target-group'),
          groupsBeforeDrop = $.extend(true, [], groups);

        var originGroup = findGroupById(groupsBeforeDrop, originGroupId),
          targetGroup = findGroupById(groupsBeforeDrop, targetGroupId);
        var targetNode = targetGroup.nodes.find(function (node) {
          return node.id == nodeId;
        });
        var nodeIds = targetGroup.nodes.map(function (node) {
          return node.id.toString();
        });
        targetGroup.nodes.splice(nodeIds.indexOf(nodeId.toString()), 1);
        originGroup.nodes.push(targetNode);

        saveUndo(groupsBeforeDrop, links);
        target.removeAttribute('origin-group');
        target.removeAttribute('target-group');
      } else {
        saveUndo(groups, links);
      }

      for (var i = 0; i < groups.length; i++) {
        for (var j = 0; j < groups[i].nodes.length; j++) {
          if (groups[i].nodes[j].id == nodeId) {
            groups[i].nodes[j].posi = {x: left, y: top};
            break;
          }
        }
      }

      refreshLinks(nodeId);
      dragEndCallBack(groups);
    }
  }

  function setGroupResizable($container, groups, links, defaultGroupHeight, resizeCallBack, refreshLinks) {
    var onResizeEnd;
    interact('.group')
      .resizable({
        preserveAspectRatio: false,
        edges: {bottom: true}
      })
      .on('resizemove', resizeMove);

    function resizeMove(event) {
      var target = event.target;
      var groupId = target.id;

      if (event.rect.height > defaultGroupHeight) {
        target.style.height = event.rect.height + 'px';
      }

      refreshLinks();

      clearTimeout(onResizeEnd);
      onResizeEnd = setTimeout(function () {
        resizeCallBack(groupId, event.rect.height);
      }, 1000);
    }
  }

  function setGroupDroppable(groups, groupDefaultHeight) {
    interact('.dropzone').dropzone({
      accept: '.node',
      overlap: 0.5,
      ondragenter: ondragenter,
      ondragleave: ondragleave,
      ondrop: onDrop,
      ondropdeactivate: ondropdeactivate
    });

    function ondragenter(event) {
      var draggableElement = event.relatedTarget,
        dropzoneElement = event.target;

      dropzoneElement.classList.add('drop-target');
      draggableElement.classList.add('can-drop');
    }

    function ondragleave(event) {
      event.target.classList.remove('drop-target');
      event.relatedTarget.classList.remove('can-drop');
    }

    function onDrop(event) {
      var draggableElement = event.relatedTarget,
        originGroupId = event.relatedTarget.parentElement.parentElement.id,
        dropzone = event.target,
        targetGroupId = event.target.id;

      if (originGroupId != targetGroupId) {
        //check whether the node is already in target group
        var targetGroup = groups.find(function (group) {
          return group.id == targetGroupId;
        });
        var found = targetGroup.nodes.find(function (node) {
          if (node.structId && node.recordId) {
            var nodeIndex = node.id.indexOf("-", node.id.indexOf("-") + 1);
            return draggableElement.id.slice(0, nodeIndex) == node.structId + '-' + node.recordId;
          } else {
            return node.id == draggableElement.id;
          }
        });
        if (found) {
          draggableElement.style.webkitTransform =
            draggableElement.style.transform =
              'translate( 0, 0)';
          draggableElement.setAttribute('data-x', 0);
          draggableElement.setAttribute('data-y', 0);
          return;
        }

        var top = parseInt(draggableElement.style.top.replace('px', ''), 10) + event.dragEvent.dy;

        if (getIntGroupId(targetGroupId) > getIntGroupId(originGroupId)) {
          //y2 -gh1
          top = top - calcHeightDiff(targetGroupId, originGroupId, groups, groupDefaultHeight);
        } else {
          //y2 + gh1
          top = top + calcHeightDiff(targetGroupId, originGroupId, groups, groupDefaultHeight);
        }

        draggableElement.style.left = (parseInt(draggableElement.style.left.replace('px', ''), 10) + event.dragEvent.dx) + 'px';
        draggableElement.style.top = top + 'px';

        draggableElement.style.webkitTransform =
          draggableElement.style.transform =
            'translate( 0, 0)';

        // update the posiion attributes
        draggableElement.setAttribute('data-x', 0);
        draggableElement.setAttribute('data-y', 0);
        draggableElement.setAttribute('origin-group', originGroupId);
        draggableElement.setAttribute('target-group', targetGroupId);

        $('#' + dropzone.id).find('.data-container').append(draggableElement);

        //clear origin group
        var originGroup = findGroupById(groups, originGroupId)
        var nodeIds = originGroup.nodes.map(function (node) {
          return node.id.toString();
        });
        var draggedNode = originGroup.nodes[nodeIds.indexOf(draggableElement.id)];
        originGroup.nodes.splice(nodeIds.indexOf(draggableElement.id), 1);

        //update target group
        var targetGroup = findGroupById(groups, targetGroupId);
        targetGroup.nodes.push(draggedNode);
      }
    }

    function ondropdeactivate(event) {
      event.target.classList.remove('drop-active');
      event.target.classList.remove('drop-target');
    }
  }

  function editGroupBtnListener(el) {
    $(el.currentTarget).parent().addClass('hidden');
    $(el.currentTarget).parent().parent().find('.edit-group').removeClass('hidden');
  }

  function saveGroupBtnListner(el, groups, onEditGroupNameCallback) {
    var group_id = $(el.currentTarget).parent().parent()[0].id;
    var groupName = $(el.currentTarget).parent().find($('input[type=text]')).val();
    var viewGroupContainer = $(el.currentTarget).parent().parent().find('.view-group');

    viewGroupContainer.find($('label')).text(groupName);
    viewGroupContainer.removeClass('hidden');

    var editGroupContainer = $(el.currentTarget).parent();
    editGroupContainer.addClass('hidden');

    for (var i = 0; i < groups.length; i++) {
      if (groups[i].id == group_id) {
        groups[i].title = groupName;
        break;
      }
    }
    onEditGroupNameCallback(groups);
  }

  function addGroupBtnListner($container, groups, addGroupCallback) {
    var defaultGroup = {
      id: 'group-' + Date.now(),
      title: 'new group',
      nodes: []
    };
    var group = new Group(defaultGroup);

    $container.append(group.render());
    groups.push(defaultGroup);
    addGroupCallback(groups);
  }

  function addNodeBtnListner(el, addNodeCallback) {
    var group_id = $(el.currentTarget).parent()[0].id;
    addNodeCallback(group_id);
  }

  function deleteNodeBtnListner(el, groups, links, deleteNodeCallback) {
    var targetNodeId = $(el.currentTarget).parent()[0].id,
      targetGroupId = $(el.currentTarget).parent().parent().parent()[0].id,
      targetGroupIndex = getGroupIds(groups).indexOf(targetGroupId);


    var targetNodeIndex = groups[targetGroupIndex].nodes.map(function (node) {
        return node.id.toString();
      }).indexOf(targetNodeId),
      targetNode = groups[targetGroupIndex].nodes[targetNodeIndex];

    var linkIndexes = findRelatedLinkIndexesByNodeId(targetNodeId, links);

    linkIndexes.sort(function (a, b) {
      return b - a;
    });

    linkIndexes.forEach(function (linkIndex) {
      var link = new Link(links[linkIndex]);
      link.delete();
      links.splice(linkIndex, 1);
    });

    groups[targetGroupIndex].nodes.splice(targetNodeIndex, 1);
    var node = new Node(targetNode);
    node.delete();

    deleteNodeCallback(groups, links);
  }

  function linkNodeBtnListner(el, $container, links) {
    var node_id = el.currentTarget.parentElement.id;
    $('#' + node_id).toggleClass("highlighted");
  }

  function unselectBtnListner(el, $container) {
    var selectedNodes = $container.find('.highlighted').toArray();
    selectedNodes.forEach(function (item) {
      $('#' + item.id).removeClass('highlighted');
    });
  }

  function linkBtnListner(el, $container, links, linkBtnCallback) {
    var node1 = $container.find('.highlighted')[0].id,
      node2 = $container.find('.highlighted')[1].id,
      newLink = {from: node1, to: node2};

    if (!linkExist(node1, node2, links)) {
      var link = new Link(newLink);
      $container.append(link.render());
      links.push(newLink);
      linkBtnCallback(links);
    }
  }

  function unlinkBtnListner(el, $container, links, unLinkBtnCallback) {
    var node1 = $container.find('.highlighted')[0].id,
      node2 = $container.find('.highlighted')[1].id;

    if (linkExist(node1, node2, links)) {
      var link = new Link(linkExist(node1, node2, links));
      link.delete();

      links = deleteLinkByNodeIds(node1, node2, links);
      unLinkBtnCallback(links);
    }
  }

  function swaplinkBtnListner(el, $container, links, swapLinkBtnCallback) {
    var node1 = $container.find('.highlighted')[0].id,
      node2 = $container.find('.highlighted')[1].id;

    if (linkExist(node1, node2, links)) {
      var link = new Link(linkExist(node1, node2, links));
      link.delete();

      links = deleteLinkByNodeIds(node1, node2, links);

      var temp = link.from;
      link.from = link.to;
      link.to = temp;
      $container.append(link.render());

      links.push({from: link.from, to: link.to});
      swapLinkBtnCallback(links);
    }
  }

  function deleteGroupBtnListner(el, $container, groups, links, deleteGroupCallback) {
    var targetGroupId = $(el.currentTarget).parent()[0].id;
    var targetGroupIndex = groups.findIndex(function (group, index) {
      return group.id == targetGroupId;
    });

    if (groups[targetGroupIndex].nodes) {
      groups[targetGroupIndex].nodes.forEach(function (node) {
        var indexes = findRelatedLinkIndexesByNodeId(node.id, links);
        indexes.sort(function (a, b) {
          return b - a;
        });

        indexes.forEach(function (index) {
          var link = new Link(links[index]);
          link.delete();
          links.splice(index, 1);
        });
      });
    }
    var targetGroupObj = new Group(groups[targetGroupIndex]);
    targetGroupObj.delete();

    groups.splice(targetGroupIndex, 1);

    links.forEach(function (item) {
      var link = new Link(item);
      $container.append(link.update());
    });

    deleteGroupCallback(groups, links);
  }

  function clearLinksBtnListner(links, clearLinksCallback) {
    links.forEach(function (item) {
      var link = new Link(item);
      link.delete();
    })

    links = [];
    clearLinksCallback([]);
  }

  function clearNodesBtnListner(groups, links, clearNodesCallback) {
    links.forEach(function (item) {
      var link = new Link(item);
      link.delete();
    })

    groups.forEach(function (group) {
      group.nodes.forEach(function (item) {
        var node = new Node(item);
        node.delete();
      })
      group.nodes = [];
    })

    links = [];
    clearNodesCallback(groups, links);
  }

  function clearAllBtnListner(groups, links, clearAllCallback) {
    links.forEach(function (item) {
      var link = new Link(item);
      link.delete();
    })

    groups.forEach(function (item) {
      var group = new Group(item);
      group.delete();
    })

    groups = links = [];
    clearAllCallback(groups, links);
  }

  function validateRedoUndoBtns($container) {
    $container.find('#undo-btn').each(function () {
      this.disabled = countUndoHistory() == 0;
    });
    $container.find('#redo-btn').each(function () {
      this.disabled = countRedoHistory() == 0;
    });
  }

  function validateBtns(el, $container, gorups, links) {
    validateRedoUndoBtns($container);

    //unselect btn
    var selectedNodes = $container.find('.highlighted').toArray();
    $container.find('#unselect-btn').each(function () {
      this.disabled = selectedNodes.length == 0;
    });

    //link and swap btn
    if (selectedNodes.length == 2) {
      var node1 = $container.find('.highlighted')[0].id,
        node2 = $container.find('.highlighted')[1].id;

      if (linkExist(node1, node2, links)) {
        $container.find('#link-btn').each(function () {
          this.disabled = true;
        });
        $container.find('#swap-link-btn').each(function () {
          this.disabled = false;
        });
        $container.find('#unlink-btn').each(function () {
          this.disabled = false;
        });
      } else {
        $container.find('#link-btn').each(function () {
          this.disabled = false;
        });
        $container.find('#swap-link-btn').each(function () {
          this.disabled = true;
        });
        $container.find('#unlink-btn').each(function () {
          this.disabled = true;
        });
      }
    } else {
      $container.find('#link-btn').each(function () {
        this.disabled = true;
      });
      $container.find('#swap-link-btn').each(function () {
        this.disabled = true;
      });
      $container.find('#unlink-btn').each(function () {
        this.disabled = true;
      });
    }

    //clear links
    var linksFound = $container.find('[id^=line-]').toArray();
    $container.find('#clear-links-btn').each(function () {
      this.disabled = linksFound.length == 0;
    });

    //clear nodes
    var nodesFound = $container.find('.node').toArray();
    $container.find('#clear-nodes-btn').each(function () {
      this.disabled = nodesFound.length == 0;
    });

    //clear all
    var groupsFound = $container.find('.group').toArray();
    $container.find('#clear-all-btn').each(function () {
      this.disabled = groupsFound.length == 0;
    });
  }

  function queryAllNodes(groups) {
    var result = [];
    groups.forEach(function (group) {
      if (group.nodes || group.nodes.length > 0) {
        group.nodes.forEach(function (node) {
          result.push(node);
        })
      }
    })
    return result;
  }

  function countNodes(groups) {
    var number = 0;
    groups.forEach(function (group) {
      if (group.nodes) {
        number += group.nodes.length;
      }
    })
    return number;
  }

  function countLinks(links) {
    return links.length;
  }

  function getTranslateXValue(translateString) {
    var n = translateString.indexOf("(");
    var n1 = translateString.indexOf(",");
    return parseInt(translateString.slice(n + 1, n1 - 2));
  }

  function getTranslateYValue(translateString) {
    var n = translateString.indexOf(",");
    var n1 = translateString.indexOf(")");
    return parseInt(translateString.slice(n + 1, n1 - 1));
  }

  function getIntGroupId(groupName) {
    return parseInt(groupName.replace('group-', ''), 10);
  }

  function getGroupIds(groups) {
    return groups.map(function (group) {
      return group.id;
    });
  }

  function findGroupById(groups, id) {
    return groups.find(function (group) {
      return group.id == id;
    });
  }

  function findRelatedLinkIndexesByNodeId(nodeId, links) {
    var result = [];
    links.forEach(function (link, index) {
      if (link.from == nodeId || link.to == nodeId) {
        result.push(index);
      }
    })
    return result;
  }

  function findRelatedLinksByNodeId(nodeId, links) {
    var result = [];
    links.forEach(function (link) {
      if (link.from == nodeId || link.to == nodeId) {
        result.push(link);
      }
    })
    return result;
  }

  //get height difference between origin group and target group
  function calcHeightDiff(targetGroupId, originGroupId, groups, defaultGroupHeight) {
    var result = 0,
      groupIds = getGroupIds(groups);

    var targetGroupIndex = groupIds.indexOf(targetGroupId),
      originGroupIndex = groupIds.indexOf(originGroupId),
      startIndex = targetGroupIndex > originGroupIndex ? originGroupIndex : targetGroupIndex,
      endIndex = targetGroupIndex > originGroupIndex ? targetGroupIndex : originGroupIndex;

    for (startIndex; startIndex <= endIndex - 1; startIndex++) {
      result += (getIntGroupId($('#' + groupIds[startIndex])[0].style.height) || defaultGroupHeight);
    }
    return result;
  }

  function linkExist(node1, node2, links) {
    return links.find(function (link) {
      return (link.from == node1 && link.to == node2) || (link.from == node2 && link.to == node1);
    })
  }

  function deleteLinkByNodeIds(node1, node2, links) {
    var targetIndex = links.findIndex(function (link, index) {
      return (link.from == node1 && link.to == node2) || (link.from == node2 && link.to == node1);
    });

    links.splice(targetIndex, 1);
    return links;
  }

  function Group(data) {
    this.id = data.id;
    this.title = data.title;
    this.data = data;
    this.nodes = data.nodes;

    this.render = function () {
      var result = '<div class="group dropzone" id=' + data.id;

      if (data.height) {
        result += ' style="height:' + data.height + 'px;"';
      }
      result += '>';
      result += '<i class="icon-add md-18 add-node-btn"></i>';
      result += '<div class="view-group">';
      result += '<label class="group__title">' + data.title + '</label>';
      result += '<span class="icon-mode_edit md-18 edit-group-btn btn"></span>';
      result += '</div>';
      result += '<div class="edit-group hidden">';
      result += '<input type="text" value="' + data.title + '">';
      result += '<button type="button" class="btn btn-xs save-group-btn">save</button>';
      result += '</div>';
      result += '<i class="icon-delete md-18 del-group-btn"></i>';
      result += '<i class="icon-drag_handle md-18 drag-handle-btn"></i>';
      result += '<div class="data-container">';

      this.nodes.forEach(function (item) {
        var node = new Node(item, this.id);
        result += node.render();
      })
      result += '</div></div>';
      return result;
    };

    this.delete = function () {
      $('#' + this.id).remove();
    }
  }

  function Node(data, groupId) {
    this.id = data.id;
    this.title = data.title;
    this.type = data.type;
    this.score = data.score;
    this.posi = data.posi || {x: 0, y: 0};
    this.groupId = groupId;

    this.render = function () {
      var result = '';
      var result = '<div class="node" style="background:' + data.score.color.bg +
        '; color: ' + data.score.color.fg +
        '; left: ' + data.posi.x + 'px; top: ' + data.posi.y + 'px; transform: translate(0, 0)" id=' + data.id + '>';
      result += '<i class="icon-link md-18 link-node-btn"></i>'
      result += '<h2 class="node__title">' + data.title + '</h2>';
      result += '<h3 class="node__type">' + data.type + '</h3>';
      result += '<p class="node__score">' + data.score.value + '</p>';
      result += '<i class="icon-delete md-18 del-node-btn"></i>'
      result += '</div>';
      return result;
    };

    this.delete = function () {
      $('#' + data.id).remove();
    };
  }

  function Link(data) {
    this.from = data.from;
    this.to = data.to;
    this.defaultLineColor = '#010';
    this.defaultLineThickness = 2;

    this.render = function () {
      return connect(this.from, this.to, this.defaultLineColor, this.defaultLineThickness);
    };

    this.delete = function () {
      $("#line-" + this.from + "-" + this.to).remove();
      $("#arrow-" + this.from + "-" + this.to).remove();
    };

    this.update = function () {
      this.delete();
      return this.render();
    }

    function connect(div1, div2, color, thickness) {
      if (document.getElementById(div1) == null || document.getElementById(div2) == null) {
        return null;
      }

      var off1 = getOffset(document.getElementById(div1));
      var off2 = getOffset(document.getElementById(div2));
      var x1, y1, x2, y2;
      if (Math.abs(off1.top - off2.top) > 132) {
        if (off1.top < off2.top) {
          //from div1 bottom to div2 top
          x1 = off1.left + off1.width / 2;
          y1 = off1.top + off1.height;

          x2 = off2.left + off2.width / 2;
          y2 = off2.top;
        } else {
          //from div1 top to div2 bottom
          x1 = off1.left + off1.width / 2;
          y1 = off1.top;

          x2 = off2.left + off2.width / 2;
          y2 = off2.top + off2.height;
        }
      } else {
        if (off1.left < off2.left) {
          //from div1 right to div2 left
          x1 = off1.left + off1.width;
          y1 = off1.top + off1.height / 2;

          x2 = off2.left;
          y2 = off2.top + off2.height / 2;
        } else {
          //from div1 left to div2 right
          x1 = off1.left;
          y1 = off1.top + off1.height / 2;

          x2 = off2.left + off2.width;
          y2 = off2.top + off2.height / 2;
        }
      }

      var length = Math.sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1)));
      var cx = ((x1 + x2) / 2) - (length / 2);
      var cy = ((y1 + y2) / 2) - (thickness / 2);

      var lineAngle = Math.degrees(Math.atan2((y1 - y2), (x1 - x2)));

      if (lineAngle < 0) {
        lineAngle = 180 + lineAngle;
      }

      var arrowAngle = y2 - y1 > 0 ? (135 + Math.abs(lineAngle)) : (Math.abs(lineAngle) - 45);

      var lineDiv = "<div id='line-" + div1 + "-" + div2 + "' style='padding:0; margin:0; height:" + thickness +
        "px; background-color:" + color +
        "; line-height:1px; position:absolute; left:" + cx +
        "px; top:" + cy +
        "px; width:" + length +
        "px; -moz-transform:rotate(" + lineAngle +
        "deg); -webkit-transform:rotate(" + lineAngle +
        "deg); -o-transform:rotate(" + lineAngle +
        "deg); -ms-transform:rotate(" + lineAngle +
        "deg); transform:rotate(" + lineAngle +
        "deg);' />";

      var arrow = "<div id='arrow-" + div1 + "-" + div2 + "'  style='left:" + x2 +
        "px; top:" + y2 +
        "px; width: 10px; height: 10px; position: absolute; transform-origin: top left; " +
        "border-left: 1px solid black; border-top: 1px solid black; -moz-transform:rotate(" + arrowAngle +
        "deg); -webkit-transform:rotate(" + arrowAngle +
        "deg); -o-transform:rotate(" + arrowAngle +
        "deg); -ms-transform:rotate(" + arrowAngle +
        "deg); transform:rotate(" + arrowAngle +
        "deg);' />";

      return lineDiv + arrow;
    }

    function getOffset(el) {
      var rect = el.getBoundingClientRect();
      return {
        left: rect.left + window.pageXOffset,
        top: rect.top + window.pageYOffset,
        width: rect.width || el.offsetWidth,
        height: rect.height || el.offsetHeight
      };
    }

    Math.degrees = function (radians) {
      return radians * 180 / Math.PI;
    };

  }

  function initSessionStorage() {
    // if (!sessionStorage.getItem(undoStorageKey)) {
    sessionStorage.setItem(undoStorageKey, '[]');
    // }

    // if (!sessionStorage.getItem(redoStorageKey)) {
    sessionStorage.setItem(redoStorageKey, '[]');
    // }
  }

  function saveUndo(groups, links) {
    if (groups !== undefined && links !== undefined) {
      var undoHistory = JSON.parse(sessionStorage.getItem(undoStorageKey)),
        redoHistory = JSON.parse(sessionStorage.getItem(redoStorageKey));

      var history = {
        timestamp: Date.now(),
        groups: groups,
        links: links
      };

      undoHistory.push(history);
      sessionStorage.setItem(undoStorageKey, JSON.stringify(undoHistory));
    }
  }

  function saveRedo(groups, links) {
    if (groups !== undefined && links !== undefined) {
      var undoHistory = JSON.parse(sessionStorage.getItem(undoStorageKey)),
        redoHistory = JSON.parse(sessionStorage.getItem(redoStorageKey));

      var history = {
        timestamp: Date.now(),
        groups: groups,
        links: links
      };

      redoHistory.push(history);
      sessionStorage.setItem(redoStorageKey, JSON.stringify(redoHistory));
    }
  }

  function undo() {
    var undoHistory = JSON.parse(sessionStorage.getItem(undoStorageKey));
    var map = undoHistory.pop();
    sessionStorage.setItem(undoStorageKey, JSON.stringify(undoHistory));
    return {groups: map.groups, links: map.links};
  }

  function redo() {
    var redoHistory = JSON.parse(sessionStorage.getItem(redoStorageKey));
    var map = redoHistory.pop();
    sessionStorage.setItem(redoStorageKey, JSON.stringify(redoHistory));
    return {groups: map.groups, links: map.links};
  }

  function countRedoHistory() {
    return JSON.parse(sessionStorage.getItem(redoStorageKey)).length;
  }

  function countUndoHistory() {
    return JSON.parse(sessionStorage.getItem(undoStorageKey)).length;
  }
})(jQuery);

