/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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 org.apache.uima.caseditor.editor.styleview;

import java.util.Collection;
import java.util.List;

import org.apache.uima.cas.CAS;
import org.apache.uima.cas.Type;
import org.apache.uima.cas.TypeSystem;
import org.apache.uima.caseditor.CasEditorPlugin;
import org.apache.uima.caseditor.Images;
import org.apache.uima.caseditor.editor.AnnotationEditor;
import org.apache.uima.caseditor.editor.AnnotationStyle;
import org.apache.uima.caseditor.editor.AnnotationStyleChangeListener;
import org.apache.uima.caseditor.editor.IAnnotationEditorModifyListener;
import org.apache.uima.caseditor.ui.property.EditorAnnotationPropertyPage;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.preference.IPreferenceNode;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceManager;
import org.eclipse.jface.preference.PreferenceNode;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.IColorProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.ui.dialogs.PropertyPage;
import org.eclipse.ui.internal.dialogs.PropertyDialog;
import org.eclipse.ui.part.Page;

// TODO: Subscribe to style change events
/**
 * The Class AnnotationStyleViewPage.
 */
// create new listener interface for this
class AnnotationStyleViewPage extends Page {

  /**
   * The Class AnnotationTypeContentProvider.
   */
  static class AnnotationTypeContentProvider implements ITreeContentProvider {

    /** The annotation types. */
    private AnnotationTypeNode[] annotationTypes;

    /** The editor. */
    private AnnotationEditor editor;

    /**
     * Instantiates a new annotation type content provider.
     *
     * @param editor
     *          the editor
     */
    AnnotationTypeContentProvider(AnnotationEditor editor) {
      this.editor = editor;
    }

    @Override
    public void dispose() {
    }

    @Override
    public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {

      if (newInput instanceof TypeSystem) {
        TypeSystem ts = (TypeSystem) newInput;

        List<Type> annotationTypeList = ts
                .getProperlySubsumedTypes(ts.getType(CAS.TYPE_NAME_ANNOTATION));
        annotationTypeList.add(ts.getType(CAS.TYPE_NAME_ANNOTATION));

        annotationTypes = new AnnotationTypeNode[annotationTypeList.size()];

        for (int i = 0; i < annotationTypeList.size(); i++) {
          annotationTypes[i] = new AnnotationTypeNode(editor, annotationTypeList.get(i));
        }
      } else {
        annotationTypes = null;
      }

    }

    @Override
    public Object[] getElements(Object inputElement) {
      return annotationTypes;
    }

    @Override
    public Object[] getChildren(Object parentElement) {
      return null;
    }

    @Override
    public Object getParent(Object element) {
      return null;
    }

    @Override
    public boolean hasChildren(Object element) {
      return false;
    }
  }

  /**
   * The Class AnnotationStylingLabelProvider.
   */
  static class AnnotationStylingLabelProvider implements ITableLabelProvider, IColorProvider {

    /** The type name column. */
    private static int TYPE_NAME_COLUMN = 0;

    /** The style name column. */
    private static int STYLE_NAME_COLUMN = 1;

    /** The editor. */
    private AnnotationEditor editor;

    /**
     * Instantiates a new annotation styling label provider.
     *
     * @param editor
     *          the editor
     */
    AnnotationStylingLabelProvider(AnnotationEditor editor) {
      this.editor = editor;
    }

    @Override
    public void addListener(ILabelProviderListener listener) {
    }

    @Override
    public void dispose() {
    }

    @Override
    public boolean isLabelProperty(Object element, String property) {
      return false;
    }

    @Override
    public void removeListener(ILabelProviderListener listener) {
    }

    @Override
    public Image getColumnImage(Object element, int columnIndex) {
      return null;
    }

    @Override
    public String getColumnText(Object element, int columnIndex) {

      AnnotationTypeNode typeNode = (AnnotationTypeNode) element;

      Type type = typeNode.getAnnotationType();

      // TODO: Get this information trough the editor ... its easier
      AnnotationStyle style = editor.getAnnotationStyle(type);

      if (TYPE_NAME_COLUMN == columnIndex) {
        return type.getShortName().trim();
      } else if (STYLE_NAME_COLUMN == columnIndex) {
        return style.getStyle().toString();
      } else {
        throw new IllegalStateException("Unknown column!");
      }
    }

    @Override
    public Color getForeground(Object element) {
      return null;
    }

    @Override
    public Color getBackground(Object element) {

      AnnotationTypeNode typeNode = (AnnotationTypeNode) element;

      Type type = typeNode.getAnnotationType();

      AnnotationStyle style = editor.getAnnotationStyle(type);

      return new Color(Display.getCurrent(), style.getColor().getRed(), style.getColor().getGreen(),
              style.getColor().getBlue());
    }
  }

  /** The editor. */
  private AnnotationEditor editor;

  /** The editor listener. */
  private IAnnotationEditorModifyListener editorListener;

  /** The change listener. */
  private AnnotationStyleChangeListener changeListener;

  /** The tree viewer. */
  private CheckboxTableViewer treeViewer;

  /**
   * Instantiates a new annotation style view page.
   *
   * @param editor
   *          the editor
   */
  AnnotationStyleViewPage(AnnotationEditor editor) {
    this.editor = editor;
  }

  /**
   * Types to nodes.
   *
   * @param types
   *          the types
   * @param editor
   *          the editor
   * @return the annotation type node[]
   */
  private static AnnotationTypeNode[] typesToNodes(Collection<Type> types,
          AnnotationEditor editor) {
    Collection<Type> shownTypes = editor.getShownAnnotationTypes();

    AnnotationTypeNode[] selectedNodes = new AnnotationTypeNode[shownTypes.size()];

    int typeIndex = 0;
    for (Type shownType : shownTypes) {
      selectedNodes[typeIndex++] = new AnnotationTypeNode(editor, shownType);
    }

    return selectedNodes;
  }

  @Override
  public void createControl(Composite parent) {

    Table table = new Table(parent,
            SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL | SWT.CHECK | SWT.NO_FOCUS);
    table.setHeaderVisible(true);
    table.setLinesVisible(true);

    final Color defaultForegroundColor = table.getForeground();

    table.addListener(SWT.EraseItem, new Listener() {
      @Override
      public void handleEvent(Event event) {
        if ((event.detail & SWT.SELECTED) != 0) {
          event.detail &= ~SWT.SELECTED;
          event.gc.setForeground(defaultForegroundColor);
        }
      }
    });

    treeViewer = new CheckboxTableViewer(table);

    TableColumn typeColumn = new TableColumn(table, SWT.LEFT);
    typeColumn.setAlignment(SWT.LEFT);
    typeColumn.setText("Type");
    typeColumn.setWidth(120);

    TableColumn styleColumn = new TableColumn(table, SWT.LEFT);
    styleColumn.setAlignment(SWT.LEFT);
    styleColumn.setText("Style");
    styleColumn.setWidth(100);

    treeViewer.setContentProvider(new AnnotationTypeContentProvider(editor));
    treeViewer.setLabelProvider(new AnnotationStylingLabelProvider(editor));

    treeViewer.setInput(editor.getDocument().getCAS().getTypeSystem());
    getSite().setSelectionProvider(treeViewer);

    changeListener = new AnnotationStyleChangeListener() {

      @Override
      public void annotationStylesChanged(Collection<AnnotationStyle> styles) {

        // Update all changed style elements in the table

        AnnotationTypeNode typeNodes[] = new AnnotationTypeNode[styles.size()];

        int i = 0;
        for (AnnotationStyle style : styles) {

          typeNodes[i++] = new AnnotationTypeNode(editor,
                  editor.getDocument().getType(style.getAnnotation()));
        }

        treeViewer.update(typeNodes, null);
      }
    };

    editor.getCasDocumentProvider().getTypeSystemPreferenceStore(editor.getEditorInput())
            .addPropertyChangeListener(changeListener);

    treeViewer.setCheckedElements(typesToNodes(editor.getShownAnnotationTypes(), editor));

    treeViewer.setGrayed(new AnnotationTypeNode(editor, editor.getAnnotationMode()), true);

    treeViewer.addCheckStateListener(new ICheckStateListener() {

      @Override
      public void checkStateChanged(CheckStateChangedEvent event) {

        AnnotationTypeNode typeNode = (AnnotationTypeNode) event.getElement();

        // The grayed mode annotation cannot be unselected, if the
        // user clicks on it prevent the state change
        if (typeNode.getAnnotationType().equals(editor.getAnnotationMode())) {
          treeViewer.setChecked(event.getElement(), true);
        } else {
          editor.setShownAnnotationType(typeNode.getAnnotationType(), event.getChecked());
        }
      }
    });

    editorListener = new IAnnotationEditorModifyListener() {

      @Override
      public void showAnnotationsChanged(Collection<Type> shownAnnotationTypes) {
        treeViewer.setCheckedElements(typesToNodes(shownAnnotationTypes, editor));
      }

      @Override
      public void annotationModeChanged(Type newMode) {
        // maybe slow if there are many types
        treeViewer.setAllGrayed(false);
        treeViewer.setGrayed(new AnnotationTypeNode(editor, newMode), true);
      }
    };

    // TODO: must this listener be removed ?!
    editor.addAnnotationListener(editorListener);

    // register a listener to change state
    // create a action handle to push changes to editor
  }

  @Override
  public void makeContributions(IMenuManager menuManager, IToolBarManager toolBarManager,
          IStatusLineManager statusLineManager) {
    super.makeContributions(menuManager, toolBarManager, statusLineManager);

    // TODO: Figure out how to use open properties dialog action here correctly
    // see http://wiki.eclipse.org/FAQ_How_do_I_open_a_Property_dialog%3F

    IAction action = new Action() {
      @Override
      public void run() {
        super.run();

        ISelection sel = new StructuredSelection(new AnnotationTypeNode(editor, null));
        PropertyPage page = new EditorAnnotationPropertyPage();
        page.setElement(new AnnotationTypeNode(editor, null));
        page.setTitle("Styles");
        PreferenceManager mgr = new PreferenceManager();
        IPreferenceNode node = new PreferenceNode("1", page);
        mgr.addToRoot(node);
        PropertyDialog dialog = new PropertyDialog(getSite().getShell(), mgr, sel);
        dialog.create();
        dialog.setMessage(page.getTitle());
        dialog.open();
      }
    };

    action.setImageDescriptor(CasEditorPlugin.getTaeImageDescriptor(Images.MODEL_PROCESSOR_FOLDER));

    toolBarManager.add(action);
  }

  @Override
  public Control getControl() {
    return treeViewer.getControl();
  }

  @Override
  public void setFocus() {
    treeViewer.getControl().setFocus();
  }

  @Override
  public void dispose() {
    super.dispose();

    IPreferenceStore store = editor.getCasDocumentProvider()
            .getTypeSystemPreferenceStore(editor.getEditorInput()); // TODO: Use old input, which
                                                                    // was used when the view was
                                                                    // created!

    if (store != null && changeListener != null)
      store.removePropertyChangeListener(changeListener);

    editor.removeAnnotationListener(editorListener);
  }
}
