/**
 * Itterates though the router tree looking for meta keys to generate state
 * for matching mata keys. A new state will be created with the given MenuName
 * for all the router values matching the meta keys. The tree order will be kept
 *
 * Keep in mind the key is generated and stored in local storage to persist the state of the menu when user
 * refresh the page. In case new menus need to be generated, you need to call clearMenu action for the function to
 * generate the menu automatically on the next menutree call or regenerateMenu action to for the state to be
 * cleared and generated
 *
 * @param store Vuex store
 * @param router vue-router object
 * @param MetaKey Meta keys to look for in the router config
 * @param StateExtender Extra keys and values to be used in the state
 * @returns Callable function(location, payload) wich updates the given location in the menu tree with the payload
 */
export const MenuTreeStateFromRouter = function(store, router, MetaKey, StateExtender) {
  var stateExtender = StateExtender || {}
  stateExtender = Object.assign({ priority: 0 }, stateExtender)
  if (store !== undefined) {
    store.registerModule(MetaKey, {
      namespaced: true,
      state:
        JSON.parse(localStorage.getItem('MenuTreeStateFromRouter-' + MetaKey)) ||
        generateMenu(router, MetaKey, stateExtender),
      mutations: {
        GENERATE_TREE: function ROUTE_CHANGED(state, payload) {
          store.state[MetaKey] = updateMenuPath(store.state[MetaKey], MetaKey, payload.location, payload.payload)
          localStorage.setItem('MenuTreeStateFromRouter-' + MetaKey, JSON.stringify(store.state[MetaKey]))
        },
        ADD_TO_TREE: function ROUTE_CHANGED(state, payload) {
          if (payload instanceof Array) {
            store.state[MetaKey] = store.state[MetaKey].concat(payload)
            localStorage.setItem('MenuTreeStateFromRouter-' + MetaKey, JSON.stringify(store.state[MetaKey]))
          }
        },
        REGENERATE_MENU: function REGENERATE_MENU() {
          store.state[MetaKey] = generateMenu(router, MetaKey, stateExtender)
          localStorage.setItem('MenuTreeStateFromRouter-' + MetaKey, JSON.stringify(store.state[MetaKey]))
        },
        CLEAR_MENU: function CLEAR_MENU() {
          localStorage.removeItem('MenuTreeStateFromRouter-' + MetaKey)
          store.state[MetaKey] = []
        },
      },
      actions: {
        generateTree: ({ commit }, payload) => {
          commit('GENERATE_TREE', payload)
        },
        addToTree: ({ commit }, payload) => {
          commit('ADD_TO_TREE', payload)
        },
        regenerateMenu: ({ commit }) => {
          commit('REGENERATE_MENU')
        },
        clearMenu: ({ commit }) => {
          commit('CLEAR_MENU')
        },
      },
      getters: {
        sortedMenu: stateMenuItems => {
          let items = [...stateMenuItems].sort(function(a, b) {
            return parseFloat(b[MetaKey].priority) - parseFloat(a[MetaKey].priority)
          })
          return items
        },
        menu: state => {
          return state[MetaKey]
        },
      },
    })
  }

  return (location, payload) => {
    if (store !== undefined) {
      store.dispatch(MetaKey + '/generateTree', { location: location, payload: payload })
    }
  }
}

/**
 * Generates Menu tree, Menu is generated keeping the hierarchy. Eg if the top menu
 * has the MetaKey defined then the sub menus with the MetaKey will be attached
 * to the parent menu, if not the sub menu with the MetaKey will be a the root of the
 * hierachy
 *
 * @param router vue-router object
 * @param MetaKey MetaKey Meta keys to look for in the router config
 * @param StateExtender Extra keys and values to be used in the state
 * @returns {Array}
 */
function generateMenu(router, MetaKey, StateExtender) {
  let treeMap = []
  for (var i = 0; i < router.options.routes.length; i++) {
    let tree = null
    let asChild = false
    if (
      typeof router.options.routes[i].meta === 'object' &&
      typeof router.options.routes[i].meta[MetaKey] !== 'undefined'
    ) {
      let cloneStateExtender = Object.assign({}, StateExtender)
      tree = {
        [MetaKey]: Object.assign(cloneStateExtender, router.options.routes[i].meta[MetaKey]),
        name: router.options.routes[i].name,
        path: router.options.routes[i].path,
        meta: router.options.routes[i].meta,
      }
      asChild = true
    }
    if (typeof router.options.routes[i].children !== 'undefined') {
      processChildren(tree, asChild, router.options.routes[i], treeMap, MetaKey, StateExtender)
    }
    if (tree !== null) {
      treeMap.push(tree)
    }
  }
  return treeMap
}

/**
 * Preocess the children of the routers route map
 *
 * @param tree The current tree segment
 * @param asChild If to attach to the children key of the tree object
 * @param route The current route from the vue-route tree
 * @param treeMap The full tree map of the current generated treeMap, to be used if @param tree is empty
 * @param MetaKey MetaKey Meta keys to look for in the router config
 * @param StateExtender Extra keys and values to be used in the state
 */
function processChildren(tree, asChild, route, treeMap, MetaKey, StateExtender) {
  let treePosition = tree || treeMap // use treeMap if tree is not available
  if (asChild) {
    // attach as child if parent is available
    treePosition =
      treePosition.children ||
      (() => {
        treePosition.children = []
        return treePosition.children
      })()
  }
  for (var c = 0; c < route.children.length; c++) {
    if (typeof route.children[c].meta !== 'undefined' && typeof route.children[c].meta[MetaKey] !== 'undefined') {
      let cloneStateExtender = Object.assign({}, StateExtender)
      var treeChild = {
        [MetaKey]: Object.assign(cloneStateExtender, route.children[c].meta[MetaKey]),
        name: route.children[c].name,
        path: route.children[c].path,
        meta: route.children[c].meta,
      }
      treePosition.push(treeChild)
      asChild = true
    }
    var childPosition = treeChild
      ? (() => {
          treeChild.children = []
          return treeChild
        })()
      : treePosition
    if (typeof route.children[c].children !== 'undefined') {
      processChildren(childPosition, asChild, route.children[c], treeMap, MetaKey, StateExtender)
    }
  }
}

/**
 * Update the menu path of the given state menu tree
 *
 * @param state the state object
 * @param location the location in the syntac [name]\[name]
 * @param name the name to set to the location
 * @param path the path to set to the location
 * @returns {*}
 */
function updateMenuPath(state, MetaKey, location, payload) {
  let names = location.split('/')
  let position = []
  for (var i = 0; i < names.length; i++) {
    position = position.children || state
    try {
      for (var n = 0; position.length; n++) {
        if (position[n]['name'] === names[i]) {
          position = position[n]
          break
        }
      }
    } catch (e) {
      /* eslint no-console: ["error", { allow: ["warn", "error"] }] */
      console.error('The location path is not corret, it should be a valid path with the syntax [name]/[name]/[name]')
    }
  }
  if (position.name === names[names.length - 1]) {
    position[MetaKey] = Object.assign(position[MetaKey], payload)
  }
  return state
}
