/////////////////////////////////////////////////////////////////////////////
//
// Project ProjectForge Community Edition
//         www.projectforge.org
//
// Copyright (C) 2001-2014 Kai Reinhard ([email protected])
//
// ProjectForge is dual-licensed.
//
// This community edition is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as published
// by the Free Software Foundation; version 3 of the License.
//
// This community edition 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 General
// Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see http://www.gnu.org/licenses/.
//
/////////////////////////////////////////////////////////////////////////////

package org.projectforge.web;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.projectforge.core.UserException;
import org.projectforge.plugins.todo.ToDoPlugin;
import org.projectforge.user.PFUserContext;
import org.projectforge.user.UserRights;
import org.projectforge.user.UserXmlPreferencesDO;
import org.projectforge.web.core.NavAbstractPanel;
import org.projectforge.web.user.UserPreferencesHelper;

/**
 * The customizable menu of the user (stored in the data-base and customizable).
 */
public class FavoritesMenu implements Serializable
{
  public static final String USER_PREF_FAVORITES_MENU_KEY = "usersFavoritesMenu";

  static final String USER_PREF_FAVORITES_MENU_ENTRIES_KEY = "usersFavoriteMenuEntries";

  private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(FavoritesMenu.class);

  private static final long serialVersionUID = -4954464926815538198L;

  private List<MenuEntry> menuEntries;

  private Menu menu;

  public static FavoritesMenu get()
  {
    FavoritesMenu favoritesMenu = (FavoritesMenu) UserPreferencesHelper.getEntry(USER_PREF_FAVORITES_MENU_KEY);
    if (favoritesMenu != null) {
      return favoritesMenu;
    }
    favoritesMenu = new FavoritesMenu();
    UserPreferencesHelper.putEntry(USER_PREF_FAVORITES_MENU_KEY, favoritesMenu, false);
    return favoritesMenu;
  }

  /**
   * @param userXmlPreferencesCache For storing and getting the persisted favorites menu.
   * @param accessChecker For building the menu entries regarding the access rights of the logged-in user.
   */
  FavoritesMenu()
  {
    this.menu = (Menu) UserPreferencesHelper.getEntry(NavAbstractPanel.USER_PREF_MENU_KEY);
    init();
  }

  public List<MenuEntry> getMenuEntries()
  {
    return this.menuEntries;
  }

  /**
   * Only for test cases.
   * @param menu the menu to set
   * @return this for chaining.
   */
  FavoritesMenu setMenu(final Menu menu)
  {
    this.menu = menu;
    return this;
  }

  public void readFromXml(final String menuAsXml)
  {
    if (menu == null) {
      log.error("User's menu is null, can't get FavoritesMenu!");
      return;
    }
    if (log.isDebugEnabled() == true) {
      log.debug("readFromXml: " + menuAsXml);
    }
    Document document = null;
    try {
      document = DocumentHelper.parseText(menuAsXml);
    } catch (final DocumentException ex) {
      log.error("Exception encountered " + ex, ex);
      return;
    }
    final MenuBuilderContext context = new MenuBuilderContext(menu, PFUserContext.getUser(), false);
    final Element root = document.getRootElement();
    menuEntries = new ArrayList<MenuEntry>();
    for (final Iterator< ? > it = root.elementIterator("item"); it.hasNext();) {
      final Element item = (Element) it.next();
      final MenuEntry menuEntry = readFromXml(item, context);
      menuEntries.add(menuEntry);
    }
  }

  private MenuEntry readFromXml(final Element item, final MenuBuilderContext context)
  {
    if ("item".equals(item.getName()) == false) {
      log.error("Tag 'item' expected instead of '" + item.getName() + "'. Ignoring this tag.");
      return null;
    }
    String id = item.attributeValue("id");
    MenuItemDef menuItemDef = null;
    if (id != null && id.startsWith("c-") == true) {
      id = id.substring(2);
    }
    if (id != null && menu != null) { // menu is only null for FavoritesMenuTest.
      final MenuEntry origEntry = menu.findById(id);
      menuItemDef = origEntry != null ? origEntry.menuItemDef : null;
    }
    final MenuEntry menuEntry;
    if (menuItemDef != null) {
      menuEntry = menu.getMenuEntry(menuItemDef);
    } else {
      menuEntry = new MenuEntry();
    }
    menuEntry.setSorted(false);
    if (item != null) {
      final String trimmedTitle = item.getTextTrim();
      if (trimmedTitle != null) {
        // menuEntry.setName(StringEscapeUtils.escapeXml(trimmedTitle));
        if (StringUtils.isBlank(trimmedTitle) == true) {
          menuEntry.setName("???");
        } else {
          menuEntry.setName(trimmedTitle);
        }
      }
    }
    for (final Iterator< ? > it = item.elementIterator("item"); it.hasNext();) {
      if (menuItemDef != null) {
        log.warn("Menu entry shouldn't have children, because it's a leaf node.");
      }
      final Element child = (Element) it.next();
      final MenuEntry childMenuEntry = readFromXml(child, context);
      if (childMenuEntry != null) {
        menuEntry.addMenuEntry(childMenuEntry);
      }
    }
    return menuEntry;
  }

  private void init()
  {
    this.menuEntries = new ArrayList<MenuEntry>();
    final String userPrefString = (String) UserPreferencesHelper.getEntry(USER_PREF_FAVORITES_MENU_ENTRIES_KEY);
    if (StringUtils.isBlank(userPrefString) == false) {
      if (userPrefString.contains("<root>") == false) {
        // Old format:
        buildFromOldUserPrefFormat(userPrefString);
      } else {
        readFromXml(userPrefString);
      }
    }
    if (this.menuEntries.size() == 0) {
      final MenuItemRegistry registry = MenuItemRegistry.instance();
      if (UserRights.getAccessChecker().isLoggedInUserMemberOfAdminGroup() == true) {
        final MenuEntry adminMenu = new MenuEntry().setName(PFUserContext.getLocalizedString(MenuItemDefId.ADMINISTRATION.getI18nKey()));
        menuEntries.add(adminMenu);
        addFavoriteMenuEntry(adminMenu, registry.get(MenuItemDefId.ACCESS_LIST));
        addFavoriteMenuEntry(adminMenu, registry.get(MenuItemDefId.USER_LIST));
        addFavoriteMenuEntry(adminMenu, registry.get(MenuItemDefId.GROUP_LIST));
        addFavoriteMenuEntry(adminMenu, registry.get(MenuItemDefId.SYSTEM));
      }
      if (UserRights.getAccessChecker().isRestrictedUser() == true) {
        // Restricted users see only the change password menu entry (as favorite).
        addFavoriteMenuEntry(registry.get(MenuItemDefId.CHANGE_PASSWORD));
      } else {
        final MenuEntry projectManagementMenu = new MenuEntry().setName(PFUserContext.getLocalizedString(MenuItemDefId.PROJECT_MANAGEMENT
            .getI18nKey()));
        menuEntries.add(projectManagementMenu);
        addFavoriteMenuEntry(projectManagementMenu, registry.get(MenuItemDefId.MONTHLY_EMPLOYEE_REPORT));
        addFavoriteMenuEntry(projectManagementMenu, registry.get(MenuItemDefId.TIMESHEET_LIST));
        addFavoriteMenuEntry(registry.get(MenuItemDefId.TASK_TREE));
        addFavoriteMenuEntry(registry.get(MenuItemDefId.CALENDAR));
        addFavoriteMenuEntry(registry.get(MenuItemDefId.ADDRESS_LIST));
        addFavoriteMenuEntry(registry.get(MenuItemDefId.BOOK_LIST));
        addFavoriteMenuEntry(registry.get(MenuItemDefId.PHONE_CALL));
        addFavoriteMenuEntry(registry.get(ToDoPlugin.ID));
      }
    }
  }

  private void addFavoriteMenuEntry(final MenuEntry parent, final MenuItemDef menuItemDef)
  {
    if (menu == null) {
      return;
    }
    final MenuEntry menuEntry = menu.getMenuEntry(menuItemDef);
    if (menuEntry == null) {
      return;
    }
    parent.addMenuEntry(menuEntry);
  }

  private void addFavoriteMenuEntry(final MenuItemDef menuItemDef)
  {
    if (menu == null) {
      return;
    }
    final MenuEntry menuEntry = menu.getMenuEntry(menuItemDef);
    if (menuEntry == null) {
      return;
    }
    for (final MenuEntry entry : this.menuEntries) {
      if (entry.menuItemDef == menuItemDef) {
        // Entry does already exist, ignore it.
        return;
      }
    }
    this.menuEntries.add(menuEntry);
  }

  /**
   * @param userPrefEntry coma separated list of MenuItemDefs.
   */
  void buildFromOldUserPrefFormat(final String userPrefEntry)
  {
    this.menuEntries = new ArrayList<MenuEntry>();
    if (userPrefEntry == null) {
      return;
    }
    final StringTokenizer tokenizer = new StringTokenizer(userPrefEntry, ",");
    while (tokenizer.hasMoreTokens() == true) {
      String token = tokenizer.nextToken();
      if (token.startsWith("M_") == true) {
        token = token.substring(2);
      }
      try {
        final MenuEntry origEntry = menu.findById(token);
        final MenuItemDef menuItemDef = origEntry != null ? origEntry.menuItemDef : null;
        if (menuItemDef == null) {
          continue;
        }
        addFavoriteMenuEntry(menuItemDef);
      } catch (final Exception ex) {
        log.info("Menu '" + token + "' not found: " + ex.getMessage(), ex);
      }
    }
  }

  public void storeAsUserPref()
  {
    if (CollectionUtils.isEmpty(menuEntries) == true) {
      UserPreferencesHelper.putEntry(USER_PREF_FAVORITES_MENU_ENTRIES_KEY, "", true);
      UserPreferencesHelper.removeEntry(USER_PREF_FAVORITES_MENU_KEY);
      return;
    }
    final Document document = DocumentHelper.createDocument();
    final Element root = document.addElement("root");
    for (final MenuEntry menuEntry : menuEntries) {
      buildElement(root.addElement("item"), menuEntry);
    }
    final String xml = document.asXML();
    if (xml.length() > UserXmlPreferencesDO.MAX_SERIALIZED_LENGTH) {
      throw new UserException("menu.favorite.maxSizeExceeded");
    }
    UserPreferencesHelper.putEntry(USER_PREF_FAVORITES_MENU_ENTRIES_KEY, xml, true);
    UserPreferencesHelper.putEntry(USER_PREF_FAVORITES_MENU_KEY, this, false);
    if (log.isDebugEnabled() == true) {
      log.debug("Favorites menu stored: " + xml);
    }
    log.info("Favorites menu stored: " + xml);
  }

  private void buildElement(final Element element, final MenuEntry menuEntry)
  {
    if (menuEntry.getId() != null) {
      element.addAttribute("id", menuEntry.getId());
    }
    if (menuEntry.getName() != null) {
      element.addText(menuEntry.getName());
    }
    if (menuEntry.hasSubMenuEntries() == true) {
      for (final MenuEntry subMenuEntry : menuEntry.getSubMenuEntries()) {
        buildElement(element.addElement("item"), subMenuEntry);
      }
    }
  }
}