(function (undefined) {
'use strict';

// client
if (typeof ko !== undefined + '') {
  bindContextMenu(ko);
}

// // node
// if (typeof module !== undefined + '' && module.exports && typeof require !== undefined + '') {
//   bindContextMenu(ko);
// }

function bindContextMenu(ko) {
  var currentMenu;
  var elementMapping = [];
  var utils = ko.utils;
  var registerEvent = utils.registerEventHandler;
  var isObservable = ko.isObservable;

  registerEvent(document, 'click', function (event) {
    var button = event.which || event.button;
    if (!event.defaultPrevented && button < 2) {
      hideCurrentMenu();
    }
  });

  utils.contextMenu = {
    getMenuFor: function (element, event) {
      var result = getMapping(element);

      if (result) {
        return result.get(event);
      }
    },

    openMenuFor: function (element, event) {
      var result = getMapping(element);

      if (result) {
        return result.open(event);
      }
    },

  };

  ko.bindingHandlers.contextMenu = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
      var eventsToHandle = valueAccessor() || {};
      var allBindings = allBindingsAccessor();
      var defaultClass = allBindings.contextMenuClass || 'context-menu';
      var activeElement;

      // bind on click? bind on context click?
      if (allBindings.bindMenuOnClick) {
        registerEvent(element, 'click', openMenu);
      }

      if (allBindings.bindMenuOnContextMenu === undefined || allBindings.bindMenuOnContextMenu) {
              registerEvent(element, 'contextmenu', openMenu);
        }

        elementMapping.push({
            element: element,
            get: function() {
                return activeElement;
            },

            open: openMenu,
            hide: function() {
                if (activeElement) {
                    activeElement.hide();
                }
            },
        });

        function mouseX(evt) {
            if (evt.pageX) {
                return evt.pageX;
            } else if (evt.clientX) {
                return evt.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft);
            } else {
                return null;
            }
        }

        function mouseY(evt) {
            if (evt.pageY) {
                return evt.pageY;
            } else if (evt.clientY) {
                return evt.clientY + (document.documentElement.scrollTop || document.body.scrollTop);
            } else {
                return null;
            }
        }


        function openMenu(event) {
            var menuElement;
            var context
            var tr = $(event.target).closest("tr");
            var li = $(event.target).closest("li.template-tab");
            if (tr.length) {
                var tr = $(event.target).closest("tr");
                context = ko.contextFor(tr[0]);
                if (context.$parent["rightClickSelectRow"] !== null && context.$parent["rightClickSelectRow"] !== undefined)
                    context.$parent.rightClickSelectRow(context.$data, event.target, event);
                else {
                    $.each(context.$parents,
                        function (key, value) {
                            if (value["rightClickSelectRow"] !== null && value["rightClickSelectRow"] !== undefined)
                                context.$parents[key].rightClickSelectRow(context.$data, event.target, event);
                        });
                }
            } 
            if (li.length) {
                context = ko.contextFor(li[0]);
                context.$parent.rightClickSelectTab(context.$data, event.target, event);
            }

            activeElement = getMenu(event);
            if (event.target.nodeName === "LABEL")
                return false;
            menuElement = activeElement.element;
            hideCurrentMenu();
            if (menuElement) {
                // make visibility hidden, then add to DOM so that we can get the height/width of the menu
                menuElement.style.visibility = 'hidden';
                (document.body || document).appendChild(menuElement);

                // set location
                if (event) {
                    var bottomOfViewport = window.innerHeight + window.pageYOffset;
                    var rightOfViewport = window.innerWidth + window.pageXOffset;

                    if (mouseY(event) + menuElement.offsetHeight > bottomOfViewport) {
                        menuElement.style.top = 1 * (bottomOfViewport - menuElement.offsetHeight - 10) + 'px';
                    } else {
                        menuElement.style.top = mouseY(event) + 'px';
                    }

                    if (mouseX(event) + menuElement.offsetWidth > rightOfViewport) {
                        menuElement.style.left = 1 * (rightOfViewport - menuElement.offsetWidth - 10) + 'px';
                    } else {
                        menuElement.style.left = mouseX(event) + 'px';
                    }

                    event.preventDefault();
                    event.stopPropagation();
                } else {
                    menuElement.style.top = (element.offsetTop + element.offsetHeight) + 'px';
                    menuElement.style.left = (element.offsetLeft + element.offsetWidth) + 'px';
                }

                menuElement.style.visibility = '';
            }
            currentMenu = menuElement;
            return activeElement;
        }

        function getMenu(event) {
            var menu;
            var hasChecks = false;
            var elements = [];
            var actions = [];
            var items = [];
            var props = Object.keys(
                ko.isObservable(eventsToHandle) ? eventsToHandle() : eventsToHandle
            );

            props.forEach(function(eventNameOutsideClosure) {
                pushItem(eventNameOutsideClosure);
            });

            if (elements.length) {
                menu = document.createElement('nav');
                menu.className = defaultClass;

                // you may need padding to menus that has checks
                menu.innerHTML = '<ul class="mt-contextmenu">' +
                    elements.join('') +
                    '</ul>';

                // map items to actions
                elements.forEach(function(item, index) {
                    registerEvent(menu.children[0].children[index],
                        'click',
                        function(event) {
                            var result = actions[index](viewModel, event);

                            if (!result && event) {
                                event.preventDefault();
                            }
                        });
                });
            }

            return {
                element: menu,
                items: items,
                open: openMenu,
                hide: function() {
                    if (menu && menu.parentNode) {
                        menu.parentNode.removeChild(menu);
                    }

                    currentMenu = null;
                },
            };

            function pushItem(eventName) {
                var item = getMenuProperties(eventName);
                var classes = [];
                var id = '';
                var liHtml;

                if (item.isVisible) {
                    hasChecks = hasChecks || item.isBoolean;

                    if (item.id) {
                        id = item.id;
                    }

                    // set css classes
                    if (item.isChecked) {
                        classes.push('checked');
                    }

                    if (item.isDisabled) {
                        classes.push('disabled');
                    }

                    if (item.isSeparator) {
                        classes.push('separator');
                    }

                    //if (item.url) {
                    //  classes.push('with-url');
                    //}

                    liHtml = '<li><a class="' + (item.isDisabled === true ? 'disabled' :'') + '"><span class="mt-contextmenu-text">' +
                        item.html +
                        '</span><span class="mt-contextmenu-icon"><img src="/App/src/images/ico/' +
                        _h.getIcon(item.icon) +
                        '"></span></a></li>';

                    elements.push(liHtml);
                    actions.push(item.action);
                }

                items.push(item);
            }
        }
        function getMenuProperties(eventName) {
        var text = '';
        var html = '';
        var icon = '';
        var currentEvent = ko.isObservable(eventsToHandle) ?
          eventsToHandle()[eventName] :
          eventsToHandle[eventName];
        var item = currentEvent || {};
        var id = item.id;
        var url = (isObservable(item.url) ? item.url() : item.url);
        var isVisible = item.visible === undefined || item.visible === null ||
            (isObservable(item.visible) && item.visible()) ||
            (!isObservable(item.visible) && !!item.visible);
        var isChecked = false;
        var isEnabled = !item.disabled ||
            (isObservable(item.disabled) && !item.disabled()) ||
            (isObservable(item.enabled) && item.enabled()) ||
            (!isObservable(item.enabled) && !!item.enabled);
        var isBoolean = false;
        var isDisabled = !isEnabled;
        var isSeparator = !!currentEvent.separator;

        if (!isSeparator) {
          text = isObservable(item.text) ? item.text() : item.text;
          icon = isObservable(item.icon) ? item.icon() : item.icon;


          if (!text) {
            text = eventName;
          }

          if (url) {
            html = '<a href="' + url + '">' + text + '</a>';
          } else {
            html = text;
          }
        }

        if ((isObservable(item) && typeof item() === 'boolean') ||
          (isObservable(item.action) && typeof item.action() === 'boolean')) {
          isBoolean = true;

          if ((item.action && item.action()) ||
            (typeof item === 'function' && item())) {
            isChecked = true;
          }
        }

        return {
          html: html,
          text: text,
          url: url,
          id: id,
          icon: icon,
          isVisible: isVisible,
          isChecked: isChecked,
          isEnabled: isEnabled,
          isDisabled: isDisabled,
          isBoolean: isBoolean,
          isSeparator: isSeparator,
          action: action,
        };

        function action(viewModel, event) {
          var error = eventName + ' option must have an action or an url.';

          if (isDisabled) {
            return false;
          }

          // check if option is a boolean
          if (isObservable(item) && typeof item() === 'boolean') {
            item(!item());
          }

          // is an object? well, lets check it properties
          else if (typeof item === 'object') {
            // check if has an action or if its a separator
            if (!item.action && !url && !isSeparator) {
              throw error;
            }

            // evaluate action
            else if (item.action) {
              if (isObservable(item.action) && typeof item.action() === 'boolean') {
                item.action(!item.action());
              } else {
                item.action(viewModel, event);
              }
            }
          }

          // its not an observable, should be a function
          else if (typeof item === 'function') {
            item(viewModel, event);
          }

          // nothing to do with this
          else {
            throw error;
          }

          return true;
        }
      }
    },
  };

  function hideCurrentMenu() {
    if (currentMenu && currentMenu.parentNode) {
      currentMenu.parentNode.removeChild(currentMenu);
    }

    currentMenu = null;
  }

  function getMapping(element) {
    var i = 0;

    for (; i < elementMapping.length; i++) {
      if (elementMapping[i].element === element) {
        return elementMapping[i];
      }
    }
  }
}
})();
