package oclastview.views;

import oclastview.visitors.XMLforOCL;
import oclastview.visitors.arithSimp.OCLArithSimplifier;

import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EModelElement;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.ocl.expressions.OCLExpression;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.DrillDownAdapter;
import org.eclipse.ui.part.ViewPart;
import org.jdom.Element;

import util.JDomContentProvider;
import util.JDomLabelProvider;

public class OCLASTView extends ViewPart implements ISelectionListener {

  public static final String oclNsUri = "http://www.eclipse.org/ocl/examples/OCL";
  // but the real one is Environment.OCL_NAMESPACE_URI;

  private TreeViewer viewer;

  private DrillDownAdapter drillDownAdapter;

  private Action action1;

  private Action action2;

  private Action doubleClickAction;

  private Element invisibleXml = null;

  /**
   * The constructor.
   */
  public OCLASTView() {
  }

  /**
   * This is a callback that will allow us to create the viewer and initialize
   * it.
   */
  public void createPartControl(Composite parent) {
    viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
    drillDownAdapter = new DrillDownAdapter(viewer);
    // create the content provider
    viewer.setContentProvider(new JDomContentProvider());
    // create the label provider
    JDomLabelProvider lp = new JDomLabelProvider();
    viewer.setLabelProvider(lp);
    // set initial content
    invisibleXml = new Element("invisible_root");
    viewer.setInput(invisibleXml);
    // set background and text colors
    setColors(parent, viewer);

    makeActions();
    hookContextMenu();
    hookDoubleClickAction();

    // add myself as a global selection listener
    getSite().getPage().addSelectionListener(this);

    // prime the selection
    selectionChanged(null, getSite().getPage().getSelection());

  }

  private void hookContextMenu() {
    MenuManager menuMgr = new MenuManager("#PopupMenu");
    menuMgr.setRemoveAllWhenShown(true);
    menuMgr.addMenuListener(new IMenuListener() {
      public void menuAboutToShow(IMenuManager manager) {
        OCLASTView.this.fillContextMenu(manager);
      }
    });
    Menu menu = menuMgr.createContextMenu(viewer.getControl());
    viewer.getControl().setMenu(menu);
    getSite().registerContextMenu(menuMgr, viewer);
  }

  private void fillContextMenu(IMenuManager manager) {
    manager.add(action1);
    manager.add(action2);
    manager.add(new Separator());
    drillDownAdapter.addNavigationActions(manager);
    // Other plug-ins can contribute there actions here
    manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
  }

  private void makeActions() {
    action1 = new Action() {
      public void run() {
        showMessage("Action 1 executed");
      }
    };
    action1.setText("Action 1");
    action1.setToolTipText("Action 1 tooltip");
    action1.setImageDescriptor(PlatformUI.getWorkbench().getSharedImages()
        .getImageDescriptor(ISharedImages.IMG_OBJS_INFO_TSK));

    action2 = new Action() {
      public void run() {
        showMessage("Action 2 executed");
      }
    };
    action2.setText("Action 2");
    action2.setToolTipText("Action 2 tooltip");
    action2.setImageDescriptor(PlatformUI.getWorkbench().getSharedImages()
        .getImageDescriptor(ISharedImages.IMG_OBJS_INFO_TSK));
    doubleClickAction = new Action() {
      public void run() {
        ISelection selection = viewer.getSelection();
        Object obj = ((IStructuredSelectionselection)
            .getFirstElement();
        showMessage("Double-click detected on " + obj.toString());
      }
    };
  }

  private void hookDoubleClickAction() {
    viewer.addDoubleClickListener(new IDoubleClickListener() {
      public void doubleClick(DoubleClickEvent event) {
        doubleClickAction.run();
      }
    });
  }

  private void showMessage(String message) {
    MessageDialog.openInformation(viewer.getControl().getShell(),
        "OCL AST View", message);
  }

  /**
   * Passing the focus request to the viewer's control.
   */
  public void setFocus() {
    viewer.getControl().setFocus();
  }

  static public void setColors(Composite parent, TreeViewer viewer) {
    Color back = parent.getDisplay().getSystemColor(
        SWT.COLOR_INFO_BACKGROUND);
    Color front = parent.getDisplay().getSystemColor(SWT.COLOR_DARK_BLUE);
    viewer.getTree().setBackground(back);
    viewer.getTree().setForeground(front);
  }

  public void selectionChanged(IWorkbenchPart part, ISelection selection) {
    clearView();
    EObject context = null;
    if (!(selection instanceof IStructuredSelection)) {
      return;
    }
    IStructuredSelection sel = (IStructuredSelectionselection;
    if (sel != null && !sel.isEmpty()) {
      Object selected = sel.getFirstElement();

      if (selected instanceof EObject) {
        context = (EObjectselected;
      else if (selected instanceof IAdaptable) {
        context = (EObject) ((IAdaptableselected)
            .getAdapter(EObject.class);
      }
    }
    if (context == null || !(context instanceof EModelElement)) {
      return;
    }
    EModelElement me = (EModelElementcontext;
    for (Object oea : me.getEAnnotations()) {
      EAnnotation ea = (EAnnotationoea;
      if (ea.getSource().toLowerCase().equals(oclNsUri.toLowerCase())) {
        Element tree = new Element("tree");
        for (Object key : ea.getDetails().keySet()) {
          if (key instanceof String) {
            String strKey = (Stringkey;
            if (key.equals("invariant")) {
              // the EClassifier, not the EOperation
              context = context.eContainer();
            }
            Object val = ea.getDetails().get(key);
            Element xmlNode = null;
            if (val instanceof String) {
              xmlNode = displayOCL((Stringval, context, strKey);
              tree.addContent(xmlNode);
            }
          }
        }
        openViewOn(tree);
      }
    }
  }

  private Element displayOCL(String expression, EObject context, String key) {
    org.eclipse.ocl.OCL ocl = null;
    org.eclipse.ocl.helper.OCLHelper helper = null;

    Element res = new Element(key);
    res.setAttribute("textualInput", expression);

    if (context instanceof org.eclipse.uml2.uml.NamedElement) {
      ocl = org.eclipse.ocl.uml.OCL.newInstance();
    else {
      ocl = org.eclipse.ocl.ecore.OCL.newInstance();
    }

    helper = ocl.createOCLHelper();

    // set our helper's context object to parse against it
    if ((context instanceof org.eclipse.emf.ecore.EClass)
        || (context instanceof org.eclipse.uml2.uml.Classifier)) {
      helper.setContext(context);
    else if (context instanceof org.eclipse.emf.ecore.EOperation) {
      EOperation eOp = (EOperationcontext;
      helper.setOperationContext(eOp.getEContainingClass(), eOp);
    else if (context instanceof org.eclipse.uml2.uml.Operation) {
      org.eclipse.uml2.uml.Operation op = (org.eclipse.uml2.uml.Operationcontext;
      helper.setOperationContext(op.getOwner(), op);
    else if (context instanceof org.eclipse.emf.ecore.EStructuralFeature) {
      EStructuralFeature sf = (EStructuralFeaturecontext;
      helper.setAttributeContext(sf.getEContainingClass(), sf);
    else if (context instanceof org.eclipse.uml2.uml.Property) {
      org.eclipse.uml2.uml.Property p = (org.eclipse.uml2.uml.Propertycontext;
      helper.setAttributeContext(p.getOwner(), p);
    }

    OCLExpression<EClassifier> oclExp = null;
    Element xmlAST = null;
    try {
      oclExp = helper.createQuery(expression);
    catch (Exception e) {
      xmlAST = reportException(e);
      res.addContent(xmlAST);
      return res;
    }

    XMLforOCL xfo = null;
    // OCLArithSimplifier simplifier = null;  

    if (context instanceof org.eclipse.uml2.uml.NamedElement) {
      xfo = XMLforOCL.getUML2Version();
      // simplifier = OCLArithSimplifier.getUML2Version();
    else {
      xfo = XMLforOCL.getEcoreVersion();
      // simplifier = OCLArithSimplifier.getEcoreVersion();
    }
    try {
      // oclExp = (OCLExpression) oclExp.accept(simplifier);
      xmlAST = (ElementoclExp.accept(xfo)// 
    catch (Exception e) {
      xmlAST = reportException(e);
    }

    res.addContent(xmlAST);
    return res;
  }

  private Element reportException(Exception e) {
    String elemName = e.getClass().getName();
    Element xmlAST = new Element(elemName);
    String msg = e.getMessage();
    if (msg == null) {
      msg = "NULL";
    }
    xmlAST.setAttribute("msg", msg);
    int counter = 0;
    StackTraceElement[] sttr = e.getStackTrace();
    for (StackTraceElement ste : sttr) {
      String string = ste.toString();
      Element eL = new Element("line_" + counter);
      eL.setAttribute("elem", string);
      xmlAST.addContent(eL);
      counter++;
    }
    return xmlAST;
  }

  private void clearView() {
    viewer.setInput(invisibleXml);
    viewer.refresh();
    viewer.expandAll();
  }

  /**
   * Opens this view showing <code>tree</code>
   
   @param elem
   */
  public void openViewOn(Element tree) {
    Element root = new Element("root");
    root.addContent(tree);
    viewer.setInput(root);
    viewer.refresh();
    viewer.expandAll();
  }

}