/*   **********************************************************************  **
 **   Copyright notice                                                       **
 **                                                                          **
 **   (c) 2003-2006 RSSOwl Development Team                                  **
 **   http://www.rssowl.org/                                                 **
 **                                                                          **
 **   All rights reserved                                                    **
 **                                                                          **
 **   This program and the accompanying materials are made available under   **
 **   the terms of the Eclipse Public License 1.0 which accompanies this     **
 **   distribution, and is available at:                                     **
 **   http://www.rssowl.org/legal/epl-v10.html                               **
 **                                                                          **
 **   A copy is found in the file epl-v10.html and important notices to the  **
 **   license from the team is found in the textfile LICENSE.txt distributed **
 **   in this package.                                                       **
 **                                                                          **
 **   This copyright notice MUST APPEAR in all copies of the file!           **
 **                                                                          **
 **   Contributors:                                                          **
 **     RSSOwl - initial API and implementation (bpasero@rssowl.org)         **
 **                                                                          **
 **  **********************************************************************  */

package net.sourceforge.rssowl.controller.dialog;

import net.sourceforge.rssowl.controller.GUI;
import net.sourceforge.rssowl.controller.MenuManager;
import net.sourceforge.rssowl.model.Category;
import net.sourceforge.rssowl.model.Favorite;
import net.sourceforge.rssowl.util.GlobalSettings;
import net.sourceforge.rssowl.util.shop.FontShop;
import net.sourceforge.rssowl.util.shop.LayoutDataShop;
import net.sourceforge.rssowl.util.shop.LayoutShop;
import net.sourceforge.rssowl.util.shop.PaintShop;
import net.sourceforge.rssowl.util.shop.StringShop;
import net.sourceforge.rssowl.util.shop.WidgetShop;

import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;

import java.text.Collator;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;

/**
 * This dialog is used to select a category from a tree that is displaying all
 * categories, without blogrolls in case the Dialog is not readonly.
 * 
 * @author <a href="mailto:bpasero@rssowl.org">Benjamin Pasero </a>
 * @version 1.2.3
 */
public class SelectCategoryDialog extends Dialog {

  /** Bounds of the shell in DLUs */
  private static final Rectangle dialogShellSize = new Rectangle(0, 0, 280, 280);

  private String catPath;
  private boolean readonly;
  private String title;
  Tree categoryTree;
  TreeItem rootItem;

  /**
   * Creates an dialog with a tree filled with all categories. Note that the
   * dialog will have no visual representation (no widgets) until it is told to
   * open.
   * <p>
   * Note that the <code>open</code> method blocks for input dialogs.
   * </p>
   * 
   * @param parentShell the parent shell
   * @param dialogTitle the dialog title, or <code>null</code> if none
   */
  public SelectCategoryDialog(Shell parentShell, String dialogTitle) {
    super(parentShell);
    this.title = dialogTitle;
  }

  /**
   * Get the selected category as String path
   * 
   * @return String The selected category as category String path
   */
  public String getCatPath() {
    return catPath;
  }

  /**
   * Sets the input category as String path
   * 
   * @param catPath The input category as category String path.
   */
  public void setCatPath(String catPath) {
    this.catPath = catPath;
  }

  /**
   * If this flag is set to TRUE, it is not possible to create new categories
   * from the Dialog.
   * 
   * @param readonly Set to TRUE, if it should not be allowed to create new
   * categories from the Dialog.
   */
  public void setReadonly(boolean readonly) {
    this.readonly = readonly;
  }

  /**
   * Recursivly write all categorys and subcategories to the tree
   * 
   * @param parent The parent treeitem
   * @param cat Current Category
   */
  private void buildCatTree(TreeItem parent, Category cat) {
    Hashtable subCats = cat.getSubCategories();
    Iterator keys = cat.getSortedSubCatTitles().iterator();

    /** For each subcategory */
    while (keys.hasNext()) {
      Category rssOwlCategory = (Category) subCats.get(keys.next());

      /** Only show categories if not readonly */
      if (readonly || !rssOwlCategory.isBlogroll()) {
        TreeItem child = new TreeItem(parent, SWT.NONE);
        child.setImage(getImage(rssOwlCategory));
        child.setText(rssOwlCategory.getName());

        /** Recursivly call buildCatTree */
        buildCatTree(child, rssOwlCategory);
      }
    }
  }

  /**
   * Get the category path beginning with the given treeitem.
   * 
   * @param treeItem The entry point for the category path String
   * @return String A String represenation of the selected category
   */
  private String getCatPath(TreeItem treeItem) {

    /** Vector to store parents */
    ArrayList parents = new ArrayList();
    parents.add(treeItem.getText());
    String treePath;
    TreeItem tempItem = treeItem;

    /** Foreach parent treeitem until Root */
    while ((tempItem = tempItem.getParentItem()) != null && tempItem != rootItem)
      parents.add(tempItem.getText());

    /** Built treepath */
    treePath = (String) parents.get(parents.size() - 1);
    for (int a = parents.size() - 2; a >= 0; a--)
      treePath += (StringShop.CAT_TOKENIZER + parents.get(a));

    return treePath;
  }

  /**
   * Get the Image for the given Category.
   * 
   * @param category The Category to get an Image for.
   * @return Image for this Category.
   */
  private Image getImage(Category category) {
    boolean containsUnreadFavorites = false;
    Hashtable favorites = category.getFavorites();
    Enumeration elements = favorites.elements();

    /** For each direct Favorite of this Category */
    while (elements.hasMoreElements()) {
      Favorite favorite = (Favorite) elements.nextElement();
      if (favorite != null && favorite.getUnreadNewsCount() > 0) {
        containsUnreadFavorites = true;
        break;
      }
    }

    /** Category is a Blogroll */
    if (category.isBlogroll())
      return containsUnreadFavorites ? PaintShop.iconFolderSubscribeUnread : PaintShop.iconFolderSubscribe;

    /** Category is normal */
    return containsUnreadFavorites ? PaintShop.iconFolderUnread : PaintShop.iconFolder;
  }

  /**
   * Selects the given Category in the Tree.
   * 
   * @param catPath The Path to the Category to select.
   */
  private void selectCategory(String catPath) {
    String[] categories = catPath.split(StringShop.CAT_TOKENIZER);
    TreeItem items[] = rootItem.getItems();
    TreeItem selectedItem = rootItem;

    /** Foreach Category */
    for (int i = 0; i < categories.length; i++) {
      String category = categories[i];

      /** Foreach Items in the currently selected TreeItem */
      for (int j = 0; j < items.length; j++) {
        TreeItem item = items[j];

        /** The Item matches the current Category */
        if (item.getText().equals(category)) {
          selectedItem = item;
          selectedItem.setExpanded(true);
          items = selectedItem.getItems();

          break;
        }
      }
    }

    /** Set and Show the selection */
    categoryTree.setSelection(new TreeItem[] { selectedItem });
    categoryTree.showSelection();
  }

  /**
   * @see org.eclipse.jface.dialogs.Dialog#buttonPressed(int)
   */
  protected void buttonPressed(int buttonId) {

    /** Save selected category as String representation */
    if (buttonId == IDialogConstants.OK_ID && categoryTree.getSelectionCount() > 0)
      catPath = getCatPath(categoryTree.getSelection()[0]);

    super.buttonPressed(buttonId);
  }

  /**
   * @see org.eclipse.jface.window.Window#configureShell(org.eclipse.swt.widgets.Shell)
   */
  protected void configureShell(Shell shell) {
    super.configureShell(shell);

    /** On Mac do not set Shell Image since it will change the Dock Image */
    if (!GlobalSettings.isMac())
      shell.setImages(PaintShop.iconOwl);

    shell.setText(title);
  }

  /**
   * @see org.eclipse.jface.dialogs.Dialog#createButtonsForButtonBar(org.eclipse.swt.widgets.Composite)
   */
  protected void createButtonsForButtonBar(Composite parent) {

    /** Button order on Mac is different */
    if (GUI.display.getDismissalAlignment() == SWT.RIGHT) {
      createButton(parent, IDialogConstants.CANCEL_ID, GUI.i18n.getTranslation("BUTTON_CANCLE"), false).setFont(FontShop.dialogFont);
      createButton(parent, IDialogConstants.OK_ID, GUI.i18n.getTranslation("BUTTON_OK"), true).setFont(FontShop.dialogFont);
    } else {
      createButton(parent, IDialogConstants.OK_ID, GUI.i18n.getTranslation("BUTTON_OK"), true).setFont(FontShop.dialogFont);
      createButton(parent, IDialogConstants.CANCEL_ID, GUI.i18n.getTranslation("BUTTON_CANCLE"), false).setFont(FontShop.dialogFont);
    }

    /** Update enabled state of OK Button */
    onSelection();
  }

  /**
   * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite)
   */
  protected Control createDialogArea(Composite parent) {

    /** Composite to hold all components */
    Composite composite = (Composite) super.createDialogArea(parent);
    composite.setLayout(LayoutShop.createGridLayout(1, 5, 10));

    /** Tree containing all categories */
    categoryTree = new Tree(composite, SWT.BORDER | SWT.SINGLE);
    categoryTree.setLayoutData(new GridData(GridData.FILL_BOTH));
    categoryTree.setFont(FontShop.dialogFont);
    categoryTree.addListener(SWT.MouseDoubleClick, new Listener() {
      public void handleEvent(Event event) {
        if (categoryTree.getSelectionCount() <= 0)
          return;

        TreeItem selection = categoryTree.getSelection()[0];
        Rectangle selectedRect = selection.getBounds();

        /** Expand location to fit left image of Tree Item */
        Rectangle imgRect = selection.getImageBounds(0);
        if (imgRect != null)
          selectedRect.add(imgRect);

        /** Only handle event, if Mouse is over treeitem */
        if (!selectedRect.contains(event.x, event.y))
          return;

        /** Expand / Collapse on doubleclick if treeitem has childs */
        if (selection.getItems().length > 0)
          selection.setExpanded(!selection.getExpanded());

        /** Finish dialog on doubleclick if treeitem has no childs */
        else if (selection != rootItem)
          buttonPressed(IDialogConstants.OK_ID);
      }
    });

    /** Update enabled state of OK Button in dependance of Selection */
    categoryTree.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        onSelection();
      }
    });

    /** Context Menu of the Category Tree */
    if (!readonly) {
      Menu treeMenu = new Menu(categoryTree);
      MenuItem addCategoryItem = new MenuItem(treeMenu, SWT.POP_UP);
      addCategoryItem.setText(GUI.i18n.getTranslation("DIALOG_ADD_CATEGORY_TITLE") + "...");
      addCategoryItem.addSelectionListener(new SelectionAdapter() {
        public void widgetSelected(SelectionEvent e) {
          createNewCategory();
        }
      });
      categoryTree.setMenu(treeMenu);

      /** Init Mnemonics */
      MenuManager.initMnemonics(treeMenu);
    }

    /** Fill tree with categories */
    buildCatTree();

    /** Button to create a new category */
    if (!readonly) {
      Button createCategoryButton = new Button(composite, SWT.NONE);
      createCategoryButton.setText(GUI.i18n.getTranslation("DIALOG_ADD_CATEGORY_TITLE") + "...");
      createCategoryButton.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING));
      createCategoryButton.setFont(FontShop.dialogFont);
      createCategoryButton.addSelectionListener(new SelectionAdapter() {
        public void widgetSelected(SelectionEvent e) {
          createNewCategory();
        }
      });

      /** If no category is present, focus this Button */
      if (rootItem.getItemCount() <= 0)
        createCategoryButton.setFocus();

      /** Set Mnemonics to Buttons */
      WidgetShop.initMnemonics(new Button[] { createCategoryButton });
    }

    /** Holder for the separator to the OK and Cancel buttons */
    Composite sepHolder = new Composite(parent, SWT.NONE);
    sepHolder.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_HORIZONTAL, 2));
    sepHolder.setLayout(LayoutShop.createGridLayout(1, 0, 0));

    /** Separator */
    Label separator = new Label(sepHolder, SWT.SEPARATOR | SWT.HORIZONTAL);
    separator.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

    /** Select and show the input */
    if (StringShop.isset(catPath))
      selectCategory(catPath);

    return composite;
  }

  /**
   * @see org.eclipse.jface.dialogs.Dialog#getButton(int)
   */
  protected Button getButton(int id) {
    return super.getButton(id);
  }

  /**
   * @see org.eclipse.jface.window.Window#getInitialSize()
   */
  protected Point getInitialSize() {
    return new Point(convertHorizontalDLUsToPixels(dialogShellSize.width), convertVerticalDLUsToPixels(dialogShellSize.height));
  }

  /**
   * @see org.eclipse.jface.window.Window#getShellStyle()
   */
  protected int getShellStyle() {
    int style = SWT.TITLE | SWT.BORDER | SWT.RESIZE | SWT.APPLICATION_MODAL | getDefaultOrientation();

    /** Follow Apple's Human Interface Guidelines for Application Modal Dialogs */
    if (!GlobalSettings.isMac())
      style |= SWT.CLOSE;

    return style;
  }

  /**
   * @see org.eclipse.jface.window.Window#initializeBounds()
   */
  protected void initializeBounds() {
    super.initializeBounds();

    /** Define minimium size */
    Point size = getShell().getSize();
    getShell().setMinimumSize(size.x, size.y);
  }

  /**
   * Set the layout data of the button to a GridData with appropriate widths
   * This method was slightly modified so that it is not setting a heightHint.
   * 
   * @param button The button to layout
   */
  protected void setButtonLayoutData(Button button) {
    GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
    int widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
    data.widthHint = Math.max(widthHint, button.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x);
    button.setLayoutData(data);
  }

  /**
   * Recursivly write all categorys and subcategories to the tree
   */
  void buildCatTree() {

    /** Begin with root category */
    Category rootCat = Category.getRootCategory();
    Hashtable subCats = rootCat.getSubCategories();
    Iterator keys = rootCat.getSortedSubCatTitles().iterator();

    /** Add the Root item to allow creation of root-leveled Categories */
    rootItem = new TreeItem(categoryTree, SWT.NONE);
    rootItem.setText(GUI.i18n.getTranslation("HEADER_RSS_FAVORITES"));
    rootItem.setImage(PaintShop.loadImage("/img/icons/root_category.gif"));
    rootItem.setData(Boolean.TRUE);
    rootItem.setFont(FontShop.dialogBoldFont);
    rootItem.addDisposeListener(new DisposeListener() {
      public void widgetDisposed(DisposeEvent e) {
        rootItem.getImage().dispose();
      }
    });

    /** For each subcategory */
    while (keys.hasNext()) {
      Category rssOwlCategory = (Category) subCats.get(keys.next());

      /** Only show categories if not readonly */
      if (readonly || !rssOwlCategory.isBlogroll()) {
        TreeItem child = new TreeItem(rootItem, SWT.NONE);
        child.setImage(getImage(rssOwlCategory));
        child.setText(rssOwlCategory.getName());

        /** Recursivly call buildCatTree */
        buildCatTree(child, rssOwlCategory);
      }
    }

    /** Set selection to an item */
    if (rootItem.getItemCount() > 0)
      categoryTree.setSelection(new TreeItem[] { rootItem.getItems()[0] });
    else
      categoryTree.setSelection(new TreeItem[] { rootItem });

    /** For usability, expand first items in the root */
    rootItem.setExpanded(true);
    int itemCount = rootItem.getItemCount();
    for (int a = 0; a < itemCount; a++)
      rootItem.getItems()[a].setExpanded(true);
  }

  /**
   * Opens the New Category dialog and prompts for a name of the new category.
   * The new category will be created as sub-category of the selected one, or in
   * the case the tree is empty, created as child of the tree.
   */
  void createNewCategory() {

    /** Get name of the new category */
    String newCatTitle = GUI.rssOwlGui.getEventManager().actionNewCategory(getShell(), getSelectedCategory());

    /** New category was created, add to category tree */
    if (newCatTitle != null) {
      TreeItem newCategory;
      TreeItem parent;

      /** The Parent is the selection */
      if (categoryTree.getSelectionCount() > 0)
        parent = categoryTree.getSelection()[0];

      /** The Parent is the Root Item */
      else
        parent = rootItem;

      /** Find the correct index for the new category */
      Collator collator = Collator.getInstance();
      int index;
      TreeItem items[] = parent.getItems();
      for (index = 0; index < items.length; index++) {
        TreeItem item = items[index];
        if (collator.compare(item.getText(), newCatTitle) <= 0)
          continue;
        break;
      }

      /** Create the category at the correct position */
      newCategory = new TreeItem(parent, SWT.NONE, index);
      newCategory.setText(newCatTitle);
      newCategory.setImage(PaintShop.iconFolder);
      categoryTree.setSelection(new TreeItem[] { newCategory });
      categoryTree.showSelection();
      categoryTree.setFocus();
      onSelection();
    }
  }

  /**
   * Get the associated Category model object for the selection in the category
   * tree or the root category if no selection was made or the tree is empty
   * 
   * @return Category The selected category or the root category
   */
  Category getSelectedCategory() {

    /** Begin with the root category */
    Category selectedCategory = Category.getRootCategory();
    ArrayList treePathVector = new ArrayList();
    String treePath = null;

    /** Tree is empty, no selection made or root selected */
    if (categoryTree.getSelectionCount() == 0 || categoryTree.getSelection()[0] == rootItem)
      return selectedCategory;

    /** First item to start */
    TreeItem tempTreeItem = categoryTree.getSelection()[0];
    treePathVector.add(tempTreeItem.getText());

    /** Foreach parent treeitem until root */
    while ((tempTreeItem = tempTreeItem.getParentItem()) != rootItem)
      treePathVector.add(tempTreeItem.getText());

    /** Create Treepath */
    if (treePathVector.size() > 0) {
      treePath = (String) treePathVector.get(treePathVector.size() - 1);
      for (int a = treePathVector.size() - 2; a >= 0; a--)
        treePath += (StringShop.CAT_TOKENIZER + treePathVector.get(a));
    }

    /** TreePath is set */
    if (StringShop.isset(treePath)) {
      String[] categorys = treePath.split(StringShop.CAT_TOKENIZER);

      /** Foreach category in the path */
      for (int a = 0; a < categorys.length; a++)
        selectedCategory = (Category) selectedCategory.getSubCategories().get(categorys[a]);
    }

    return selectedCategory;
  }

  /**
   * Called upon selection change in the Category Tree.
   */
  void onSelection() {
    TreeItem selection = categoryTree.getSelection().length > 0 ? categoryTree.getSelection()[0] : null;

    /** Root Element selected. Not a valid Category */
    if (WidgetShop.isset(selection) && Boolean.TRUE.equals(selection.getData()))
      getButton(IDialogConstants.OK_ID).setEnabled(false);

    /** Other valid Category selected */
    else
      getButton(IDialogConstants.OK_ID).setEnabled(true);
  }
}