/*******************************************************************************
 * Copyright 2011 Google Inc. All Rights Reserved.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package com.google.gwt.eclipse.oophm.breadcrumbs;

import org.eclipse.swt.SWT;
import org.eclipse.swt.accessibility.AccessibleAdapter;
import org.eclipse.swt.accessibility.AccessibleEvent;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MenuDetectEvent;
import org.eclipse.swt.events.MenuDetectListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;

/**
 * The label and icon part of the breadcrumb item.
 * 
 * NOTE: This code was derived from
 * org.eclipse.jdt.internal.ui.javaeditor.breadcrumb.BreadcrumbItemDetails.
 * 
 * @since 3.4
 */
class BreadcrumbItemDetails {

  private final Label fElementImage;
  private final Label fElementText;
  private final Composite fDetailComposite;
  private final BreadcrumbItem fParent;
  private final Composite fTextComposite;
  private final Composite fImageComposite;

  private boolean fTextVisible;
  private boolean fSelected;
  private boolean fHasFocus;

  public BreadcrumbItemDetails(BreadcrumbItem parent, Composite parentContainer) {
    fParent = parent;
    fTextVisible = true;

    fDetailComposite = new Composite(parentContainer, SWT.NONE);
    fDetailComposite.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER,
        false, false));
    GridLayout layout = new GridLayout(2, false);
    layout.marginHeight = 0;
    layout.marginWidth = 0;
    layout.horizontalSpacing = 0;
    fDetailComposite.setLayout(layout);
    addElementListener(fDetailComposite);

    fImageComposite = new Composite(fDetailComposite, SWT.NONE);
    fImageComposite.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER,
        false, false));
    layout = new GridLayout(1, false);
    layout.marginHeight = 1;
    layout.marginWidth = 2;
    fImageComposite.setLayout(layout);
    fImageComposite.addPaintListener(new PaintListener() {
      public void paintControl(PaintEvent e) {
        if (fHasFocus && !isTextVisible()) {
          e.gc.drawFocus(e.x, e.y, e.width, e.height);
        }
      }
    });
    installFocusComposite(fImageComposite);
    addElementListener(fImageComposite);

    fElementImage = new Label(fImageComposite, SWT.NONE);
    GridData layoutData = new GridData(SWT.BEGINNING, SWT.CENTER, false, false);
    fElementImage.setLayoutData(layoutData);
    addElementListener(fElementImage);

    fTextComposite = new Composite(fDetailComposite, SWT.NONE);
    fTextComposite.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false,
        false));
    layout = new GridLayout(1, false);
    layout.marginHeight = 2;
    layout.marginWidth = 2;
    fTextComposite.setLayout(layout);
    addElementListener(fTextComposite);
    fTextComposite.addPaintListener(new PaintListener() {
      public void paintControl(PaintEvent e) {
        if (fHasFocus && isTextVisible()) {
          e.gc.drawFocus(e.x, e.y, e.width, e.height);
        }
      }
    });
    installFocusComposite(fTextComposite);
    addElementListener(fTextComposite);

    fElementText = new Label(fTextComposite, SWT.NONE);
    layoutData = new GridData(SWT.BEGINNING, SWT.CENTER, false, false);
    fElementText.setLayoutData(layoutData);
    addElementListener(fElementText);

    fTextComposite.getAccessible().addAccessibleListener(
        new AccessibleAdapter() {
          public void getName(AccessibleEvent e) {
            e.result = fElementText.getText();
          }
        });
    fImageComposite.getAccessible().addAccessibleListener(
        new AccessibleAdapter() {
          public void getName(AccessibleEvent e) {
            e.result = fElementText.getText();
          }
        });

    fDetailComposite.setTabList(new Control[] {fTextComposite});
  }

  /**
   * Returns the width of this element.
   * 
   * @return current width of this element
   */
  public int getWidth() {
    int result = 2;

    if (fElementImage.getImage() != null)
      result += fElementImage.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;

    if (fTextVisible && fElementText.getText().length() > 0)
      result += fElementText.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;

    return result;
  }

  /**
   * Returns whether this element has the keyboard focus.
   * 
   * @return true if this element has the keyboard focus.
   */
  public boolean hasFocus() {
    return fHasFocus;
  }

  /**
   * Tells whether this item shows a text or only an image.
   * 
   * @return <code>true</code> if it shows a text and an image, false if it only
   *         shows the image
   */
  public boolean isTextVisible() {
    return fTextVisible;
  }

  public void setFocus(boolean enabled) {
    if (enabled == fHasFocus)
      return;

    fHasFocus = enabled;
    if (fHasFocus) {
      if (isTextVisible()) {
        fTextComposite.setFocus();
      } else {
        fImageComposite.setFocus();
      }
    }
    updateSelection();
  }

  /**
   * Sets the image to the given image.
   * 
   * @param image the image to use
   */
  public void setImage(Image image) {
    if (image != fElementImage.getImage()) {
      fElementImage.setImage(image);
    }
  }

  public void setSelected(boolean selected) {
    if (selected == fSelected)
      return;

    fSelected = selected;
    if (!fSelected)
      fHasFocus = false;

    updateSelection();
  }

  /**
   * Sets the text to the given text.
   * 
   * @param text the text to use
   */
  public void setText(String text) {
    if (text == null) {
      text = ""; //$NON-NLS-1$
    }
    if (!text.equals(fElementText.getText())) {
      fElementText.setText(text);
    }
  }

  public void setTextVisible(boolean enabled) {
    if (fTextVisible == enabled)
      return;

    fTextVisible = enabled;

    GridData data = (GridData) fTextComposite.getLayoutData();
    data.exclude = !enabled;
    fTextComposite.setVisible(enabled);

    if (fTextVisible) {
      fDetailComposite.setTabList(new Control[] {fTextComposite});
    } else {
      fDetailComposite.setTabList(new Control[] {fImageComposite});
    }

    if (fHasFocus) {
      if (isTextVisible()) {
        fTextComposite.setFocus();
      } else {
        fImageComposite.setFocus();
      }
    }
    updateSelection();
  }

  /**
   * Sets the tool tip to the given text.
   * 
   * @param text the tool tip
   */
  public void setToolTip(String text) {
    if (isTextVisible()) {
      fElementText.getParent().setToolTipText(text);
      fElementText.setToolTipText(text);

      fElementImage.setToolTipText(text);
    } else {
      fElementText.getParent().setToolTipText(null);
      fElementText.setToolTipText(null);

      fElementImage.setToolTipText(text);
    }
  }

  /**
   * Sets whether details should be shown.
   * 
   * @param visible <code>true</code> if details should be shown
   */
  public void setVisible(boolean visible) {
    fDetailComposite.setVisible(visible);

    GridData data = (GridData) fDetailComposite.getLayoutData();
    data.exclude = !visible;
  }

  /**
   * Add mouse listeners to the given control.
   * 
   * @param control the control to which may be clicked
   */
  private void addElementListener(Control control) {
    control.addMouseListener(new MouseListener() {
      public void mouseDoubleClick(MouseEvent e) {
      }

      public void mouseDown(MouseEvent e) {
        BreadcrumbViewer viewer = fParent.getViewer();

        int parentIndex = viewer.getIndexOfItem(fParent) - 1;
        Shell shell = null;
        if (parentIndex >= 0) {// sanity check, must always hold
          BreadcrumbItem dropDownItem = viewer.getItem(parentIndex);
          shell = dropDownItem.getDropDownShell();
        }

        viewer.selectItem(fParent);
        if (shell == null && e.button == 1 && e.stateMask == 0) {
          fParent.getViewer().fireDoubleClick();
        }
      }

      public void mouseUp(MouseEvent e) {
      }
    });
    control.addMenuDetectListener(new MenuDetectListener() {
      public void menuDetected(MenuDetectEvent e) {
        BreadcrumbViewer viewer = fParent.getViewer();
        viewer.selectItem(fParent);
        fParent.getViewer().fireMenuDetect(e);
      }
    });
  }

  /**
   * Install focus and key listeners to the given composite.
   * 
   * @param composite the composite which may get focus
   */
  private void installFocusComposite(Composite composite) {
    composite.addTraverseListener(new TraverseListener() {
      public void keyTraversed(TraverseEvent e) {
        if (e.detail == SWT.TRAVERSE_TAB_NEXT
            || e.detail == SWT.TRAVERSE_TAB_PREVIOUS) {
          int index = fParent.getViewer().getIndexOfItem(fParent);
          if (e.detail == SWT.TRAVERSE_TAB_NEXT) {
            index++;
          } else {
            index--;
          }

          if (index > 0 && index < fParent.getViewer().getItemCount()) {
            fParent.getViewer().selectItem(fParent.getViewer().getItem(index));
          }

          e.doit = true;
        }
      }
    });
    composite.addKeyListener(new KeyListener() {
      public void keyPressed(KeyEvent e) {
        BreadcrumbViewer viewer = fParent.getViewer();

        switch (e.keyCode) {
          case SWT.ARROW_LEFT:
            if (fSelected) {
              viewer.doTraverse(false);
              e.doit = false;
            } else {
              viewer.selectItem(fParent);
            }
            break;
          case SWT.ARROW_RIGHT:
            if (fSelected) {
              viewer.doTraverse(true);
              e.doit = false;
            } else {
              viewer.selectItem(fParent);
            }
            break;
          case SWT.ARROW_DOWN:
            if (!fSelected) {
              viewer.selectItem(fParent);
            }
            openDropDown();
            e.doit = false;
            break;
          case SWT.KEYPAD_ADD:
            if (!fSelected) {
              viewer.selectItem(fParent);
            }
            openDropDown();
            e.doit = false;
            break;
          case SWT.CR:
            if (!fSelected) {
              viewer.selectItem(fParent);
            }
            viewer.fireOpen();
            break;
          default:
            if (e.character == ' ') {
              if (!fSelected) {
                viewer.selectItem(fParent);
              }
              openDropDown();
              e.doit = false;
            }
            break;
        }
      }

      public void keyReleased(KeyEvent e) {
      }

      private void openDropDown() {
        BreadcrumbViewer viewer = fParent.getViewer();

        int index = viewer.getIndexOfItem(fParent);
        BreadcrumbItem parent = fParent.getViewer().getItem(index - 1);

        Shell shell = parent.getDropDownShell();
        if (shell == null) {
          parent.openDropDownMenu();
          shell = parent.getDropDownShell();
        }
        shell.setFocus();
      }
    });

    composite.addFocusListener(new FocusListener() {
      public void focusGained(FocusEvent e) {
        if (!fHasFocus) {
          fHasFocus = true;
          updateSelection();
        }
      }

      public void focusLost(FocusEvent e) {
        if (fHasFocus) {
          fHasFocus = false;
          updateSelection();
        }
      }
    });
  }

  private void updateSelection() {
    Color background;
    Color foreground;

    if (fSelected && fHasFocus) {
      background = Display.getDefault().getSystemColor(SWT.COLOR_LIST_SELECTION);
      foreground = Display.getDefault().getSystemColor(
          SWT.COLOR_LIST_SELECTION_TEXT);
    } else {
      foreground = null;
      background = null;
    }

    if (isTextVisible()) {
      fTextComposite.setBackground(background);
      fElementText.setBackground(background);
      fElementText.setForeground(foreground);

      fImageComposite.setBackground(null);
      fElementImage.setBackground(null);
    } else {
      fImageComposite.setBackground(background);
      fElementImage.setBackground(background);

      fTextComposite.setBackground(null);
      fElementText.setBackground(null);
      fElementText.setForeground(null);
    }

    fTextComposite.redraw();
    fImageComposite.redraw();
  }
}