/*
 * DomUI Java User Interface library
 * Copyright (c) 2010 by Frits Jalvingh, Itris B.V.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * See the "sponsors" file for a list of supporters.
 *
 * The latest version of DomUI and related code, support and documentation
 * can be found at http://www.domui.org/
 * The contact for the project is Frits Jalvingh <jal@etc.to>.
 */
package to.etc.domui.component.menu;

import org.eclipse.jdt.annotation.NonNull;
import to.etc.domui.annotations.UIRights;
import to.etc.domui.dom.html.UrlPage;
import to.etc.domui.login.IUser;
import to.etc.domui.login.IUserRightChecker;
import to.etc.domui.login.User2RightsChecker;

import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * The singleton which maintains the full system menu and all personal copies.
 *
 * @author <a href="mailto:jal@etc.to">Frits Jalvingh</a>
 * Created on Apr 3, 2009
 */
public class MenuManager {
	static private MenuManager m_instance = new MenuManager();

	private final MenuItem m_root = new MenuItem(this);

	@NonNull
	private IUserRightChecker<IUser> m_userRightChecker = new User2RightsChecker();

	private Map<String, MenuItem> m_idMap;

	static public Comparator<MenuItem> C_BY_ORDER_AND_CHILDREN = (o1, o2) -> {
		boolean c1 = o1.getChildren().size() > 0;
		boolean c2 = o2.getChildren().size() > 0;
		if(c1 != c2)
			return c1 ? 1 : -1;
		return o1.getOrder() - o2.getOrder();
	};

	/**
	 * Forbidden constructor.
	 */
	private MenuManager() {
	}

	public static MenuManager getInstance() {
		return m_instance;
	}

	public MenuItem getRoot() {
		return m_root;
	}

	public synchronized Map<String, MenuItem> getIdMap() {
		Map<String, MenuItem> idMap = m_idMap;
		if(null == idMap) {
			m_idMap = idMap = new HashMap<>();
			calculateIds(getRoot(), idMap);
		}
		return idMap;
	}

	private void calculateIds(MenuItem root, Map<String, MenuItem> idMap) {
		String id = root.getId();
		if(null != id)
			idMap.put(id, root);
		for(MenuItem menuItem : root.getChildren()) {
			calculateIds(menuItem, idMap);
		}
	}

	/**
	 * Calculate the user-specific menu by applying the users' rights on all menu items.
	 */
	public MenuItem createUserMenu(IUser user) {
		MenuItem root = getRoot();

		MenuItem userRoot = new MenuItem(this);

		//-- First: build a tree of all nodes the user is authorized to see
		buildAuthorization(userRoot, root, user);
		pruneEmpties(userRoot);
		return userRoot;
	}

	/**
	 * Do a depth-first traversal of the user menu and remove all empty submenus.
	 */
	private void pruneEmpties(MenuItem item) {
		for(MenuItem menuItem : item.getChildren()) {
			pruneEmpties(menuItem);
		}

		//-- Now check: which of my children are empty?
		List<MenuItem> children = item.getChildren();
		for(int i = children.size() - 1; i >= 0; i--) {
			MenuItem menuItem = children.get(i);
			if(menuItem.isSubMenu() && menuItem.getChildren().size() == 0) {
				children.remove(i);
			}
		}
	}

	private void buildAuthorization(MenuItem userMenu, MenuItem systemMenu, IUser user) {
		for(MenuItem sysItem : systemMenu.getChildren()) {
			if(isNodeAuthorized(sysItem, user)) {
				//-- We're allowed to use this, so copy it
				MenuItem userItem = userMenu.addClone(sysItem);

				buildAuthorization(userItem, sysItem, user);
			}
		}
	}

	private boolean isNodeAuthorized(MenuItem item, IUser user) {
		//-- Collect all applicable rights.
		String[] requiredRights = item.getRequiredRights();
		if(null != requiredRights) {
			boolean menuOk = false;

			//-- If the user has none of these then do not show
			for(String rr : requiredRights) {
				if(m_userRightChecker.hasRight(user, rr)) {
					menuOk = true;
					break;
				}
			}
			if(! menuOk)
				return false;
		}

		Class<? extends UrlPage> pageClass = item.getPageClass();
		if(null != pageClass) {
			UIRights uir = pageClass.getAnnotation(UIRights.class);
			if(null != uir && uir.value().length > 0) {
				for(String rr : uir.value()) {
					if(m_userRightChecker.hasRight(user, rr))
						return true;
				}
				return false;
			}
		}

		return true;
	}

	public void setUserRightChecker(@NonNull IUserRightChecker<IUser> userRightChecker) {
		m_userRightChecker = userRightChecker;
	}
}

@@ -1355,16 +1355,53 @@
 Checker;
+%0Aimport to.etc.function.BiFunctionEx;
 %0A%0Aimport
@@ -1944,16 +1944,119 @@
 idMap;%0A%0A
+%09@NonNull%0A%09private BiFunctionEx%3CMenuItem, IUser, Boolean%3E m_pageAccessCheck = this::isNodeAuthorized;%0A%0A
 %09static 
@@ -3061,24 +3061,41 @@
 (IUser user)
+ throws Exception
  %7B%0A%09%09MenuIte
@@ -3907,24 +3907,41 @@
  IUser user)
+ throws Exception
  %7B%0A%09%09for(Men
@@ -3990,32 +3990,39 @@
 %7B%0A%09%09%09if(
-isNodeAuthorized
+m_pageAccessCheck.apply
 (sysItem
@@ -5063,16 +5063,16 @@
 cker) %7B%0A
-
 %09%09m_user
@@ -5106,11 +5106,153 @@
 cker;%0A%09%7D
+%0A%0A%09public void setPageAccessCheck(@NonNull BiFunctionEx%3CMenuItem, IUser, Boolean%3E pageAccessCheck) %7B%0A%09%09m_pageAccessCheck = pageAccessCheck;%0A%09%7D
 %0A%7D%0A

Handle menu authorization, and fix the first set of bugs.