package brn.gui.views;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.DrillDownAdapter;
import org.eclipse.ui.part.ViewPart;

import brn.gui.Activator;
import brn.gui.datasource.Provider;
import brn.gui.datasource.Source;
import brn.gui.datasource.Source.DataItem;
import brn.gui.datasource.Source.FwdGraphItem;
import brn.gui.datasource.Source.LinkTableItem;
import brn.gui.datasource.Source.TimeBarItem;


/**
 * This sample class demonstrates how to plug-in a new
 * workbench view. The view shows data obtained from the
 * model. The sample creates a dummy model on the fly,
 * but a real implementation would connect to the model
 * available either in this or another plug-in (e.g. the workspace).
 * The view is connected to the model using a content provider.
 * <p>
 * The view uses a label provider to define how model
 * objects should be presented in the view. Each
 * view can present the same model objects using
 * different labels and icons, if needed. Alternatively,
 * a single label provider can be shared between views
 * in order to ensure that objects of the same type are
 * presented in the same way everywhere.
 * <p>
 */

public class DataView extends ViewPart implements IPropertyChangeListener {
	public static final String ID = "brn.gui.views.DataView";
  private static final String IMAGE_OPEN_DATA = "/icons/openeditor.gif";  //  @jve:decl-index=0:

  private Image imageDiagram;
  private Image imageGraph;
  private Image imageTable;
  private Image imageTimeBar;

  private TreeViewer viewer;
	private DrillDownAdapter drillDownAdapter;
	private Action actionOpenData;  //  @jve:decl-index=0:

  private Provider provider;  //  @jve:decl-index=0:

  private boolean batch;

  private ViewContentProvider viewContentProvider;


	/*
	 * The content provider class is responsible for
	 * providing objects to the view. It can wrap
	 * existing objects in adapters or simply return
	 * objects as-is. These objects may be sensitive
	 * to the current input of the view, or ignore
	 * it and always show the same content
	 * (like Task List, for example).
	 */

	class TreeObject implements IAdaptable {
		private String name;
		private TreeParent parent;

		public TreeObject(String name) {
			this.name = name;
		}

    public String getName() {
			return name;
		}
		public void setParent(TreeParent parent) {
			this.parent = parent;
		}
		public TreeParent getParent() {
			return parent;
		}
		public String toString() {
			return getName();
		}
		@SuppressWarnings("unchecked")
    public Object getAdapter(Class key) {
			return null;
		}

    public void openInEditor() {
    }
	}

  class TreeObjectItem extends TreeObject implements IAdaptable {

    protected Source.Item item;

    public TreeObjectItem(Source.Item item) {
      super(item.getPath()[item.getPath().length - 1]);
      this.item = item;
    }

    public Source.Item getItem() {
      return item;
    }

    public void openInEditor() {
      item.openInEditor(getSite().getWorkbenchWindow());
    }

    /* (non-Javadoc)
     * @see brn.gui.views.DataView.TreeObject#getAdapter(java.lang.Class)
     */
    @SuppressWarnings("unchecked")
    @Override
    public Object getAdapter(Class key) {
      Object o = item.getAdapter(key);
      if (null != o)
        return o;

      return super.getAdapter(key);
    }
  }

  class TreeParent extends TreeObject {
		private List<TreeObject> children;
    private Map<String, TreeObject> mapChildren;
		public TreeParent(String name) {
			super(name);
			children = new ArrayList<TreeObject>();
      mapChildren = new HashMap<String, TreeObject>();
		}
		public void addChild(TreeObject child) {
			children.add(child);
      mapChildren.put(child.name, child);
			child.setParent(this);
		}
		public void removeChild(TreeObject child) {
			children.remove(child);
      mapChildren.remove(child.getName());
			child.setParent(null);
		}
    public TreeObject [] getChildrenArray() {
      return (TreeObject [])children.toArray(new TreeObject[children.size()]);
    }
//    public List<TreeObject> getChildren() {
//      return children;
//    }
    public TreeObject getChildren(String name) {
      return mapChildren.get(name);
    }
		public boolean hasChildren() {
			return children.size()>0;
		}
	}

	class ViewContentProvider implements IStructuredContentProvider, 
      ITreeContentProvider {
		private TreeParent invisibleRoot;

		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
		}
		public void dispose() {
		}
		public Object[] getElements(Object parent) {
			if (parent.equals(getViewSite())) {
				if (invisibleRoot==null) initialize();
				return getChildren(invisibleRoot);
			}
			return getChildren(parent);
		}
		public Object getParent(Object child) {
			if (child instanceof TreeObject) {
				return ((TreeObject)child).getParent();
			}
			return null;
		}
		public Object [] getChildren(Object parent) {
			if (parent instanceof TreeParent) {
				return ((TreeParent)parent).getChildrenArray();
			}
			return new Object[0];
		}
		public boolean hasChildren(Object parent) {
			if (parent instanceof TreeParent)
				return ((TreeParent)parent).hasChildren();
			return false;
		}
		private void initialize() {
      invisibleRoot = new TreeParent("");

      if (null == provider.getDataSource()
          ||!provider.getDataSource().isConnected())
        return;

      List<Source.Item> items = provider.getDataSource().getItems();
      for (int i=0; i < items.size(); i++)
        addSimulatorItem(items.get(i));
		}

    private TreeParent getParent(String[] path) {
      TreeParent currParent = invisibleRoot;
      TreeParent parent = null;

      for (int j=0; j < path.length - 1; j++) {
        parent = null;
        
        TreeObject child = currParent.getChildren(path[j]);
        if (null != child && child instanceof TreeParent) {
          parent = (TreeParent) child;
        } else {
          parent = new TreeParent(path[j]);
          currParent.addChild(parent);
        }

        currParent = parent;
      }

      return parent;
    }

    public void clear() {
      TreeObject[] children = invisibleRoot.getChildrenArray();

      for (int i = 0; i < children.length; i++)
        invisibleRoot.removeChild(children[i]);
    }

    public void addSimulatorItem(Source.Item item) {
      String[] path = item.getPath();
      if (path[0].startsWith("Node")) {
        String[] newPath = new String[path.length+1];
        int number = Integer.parseInt(path[0].substring(5));
        int interval = 25;
        int base = ((number-1) / interval) * interval + 1;
        newPath[0] = "Nodes " + base + ".." + (base+interval-1);
        for (int i = 0; i < path.length; i++)
          newPath[i+1] = path[i];
        path = newPath;
      }
      TreeParent parent = getParent(path);

      TreeObject treeObjectData = new TreeObjectItem(item);
      parent.addChild(treeObjectData);
    }
	}

	class ViewLabelProvider extends LabelProvider {

		public String getText(Object obj) {
			return obj.toString();
		}
		public Image getImage(Object obj) {
			String imageKey = ISharedImages.IMG_OBJ_ELEMENT;
			if (obj instanceof TreeParent)
			   imageKey = ISharedImages.IMG_OBJ_FOLDER;
      if (obj instanceof TreeObjectItem) {
        Source.Item item = ((TreeObjectItem) obj).getItem();
        if (item instanceof DataItem)
          return imageDiagram;
        else if (item instanceof FwdGraphItem)
          return imageGraph;
        else if (item instanceof LinkTableItem)
          return imageGraph;
        else if (item instanceof TimeBarItem)
          return imageTimeBar;
        return imageTable;
      }
			return PlatformUI.getWorkbench().getSharedImages().getImage(imageKey);
		}
	}
	class NameSorter extends ViewerSorter {
	}

	/**
	 * The constructor.
	 */
	public DataView() {
    imageDiagram = brn.gui.Activator.getImageDescriptor("/icons/diagram3.gif").createImage();
    imageGraph = brn.gui.Activator.getImageDescriptor("/icons/graph.gif").createImage();
    imageTable = brn.gui.Activator.getImageDescriptor("/icons/data2.gif").createImage();
    imageTimeBar = brn.gui.Activator.getImageDescriptor("/icons/timebar.gif").createImage();
    batch = false;
	}

	/**
	 * 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 | SWT.VIRTUAL);
		drillDownAdapter = new DrillDownAdapter(viewer);
		viewContentProvider = new ViewContentProvider();
    viewer.setContentProvider(viewContentProvider);
		viewer.setLabelProvider(new ViewLabelProvider());
		viewer.setSorter(new NameSorter());
		viewer.setInput(getViewSite());
    getSite().setSelectionProvider(viewer);
		makeActions();
		hookContextMenu();
		hookDoubleClickAction();
		contributeToActionBars();
	}

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

	private void contributeToActionBars() {
		IActionBars bars = getViewSite().getActionBars();
		fillLocalPullDown(bars.getMenuManager());
		fillLocalToolBar(bars.getToolBarManager());
	}

	private void fillLocalPullDown(IMenuManager manager) {
		manager.add(actionOpenData);
	}

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

	private void fillLocalToolBar(IToolBarManager manager) {
		manager.add(actionOpenData);
		manager.add(new Separator());
		drillDownAdapter.addNavigationActions(manager);
	}

	private void makeActions() {
		actionOpenData = new Action() {
			public void run() {
        ISelection selection = viewer.getSelection();
        Object obj = ((IStructuredSelection)selection).getFirstElement();

        if (obj instanceof TreeObject) {
          TreeObject treeObj = (TreeObject) obj;
          treeObj.openInEditor();
        }
			}
		};
    actionOpenData.setText("Open Data");
    actionOpenData.setToolTipText("Open Data in Editor");
    actionOpenData.setImageDescriptor(
        brn.gui.Activator.getImageDescriptor(IMAGE_OPEN_DATA));
	}

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

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

  public void propertyChange(final PropertyChangeEvent event) {
    getSite().getShell().getDisplay().syncExec(new Runnable(){

      public void run() {
        if (event.getProperty().equals(Source.DATA_ADDED)
            ||event.getProperty().equals(Source.XPLOT_ADDED)
            ||event.getProperty().equals(Source.FWDGRAPH_ADDED)
            ||event.getProperty().equals(Source.LINK_TABLE_ADDED)
            ||event.getProperty().equals(Source.ARP_TABLE_ADDED)
            ||event.getProperty().equals(Source.CONFIG_ADDED)
            ||event.getProperty().equals(Source.PROPERTIES_ADDED)
            ||event.getProperty().equals(Source.TIMEBAR_ADDED)) {
          Source.Item item = (Source.Item)event.getNewValue();
          viewContentProvider.addSimulatorItem(item);
        }

        if (null == provider.getDataSource()
            ||!provider.getDataSource().isConnected())
          viewContentProvider.clear();

        if (event.getProperty().equals(Source.DATASOURCE_BATCH)) {
          batch = (Boolean) event.getNewValue();
        }
        if (!batch)
          viewer.refresh(true);
      }
    });
  }

  @Override
  public void init(IViewSite site) throws PartInitException {
    super.init(site);
    provider = Activator.getDefault().getDataProvider();
    provider.addPropertyChangeListener(this);
  }

  @Override
  public void dispose() {
    provider.removePropertyChangeListener(this);
    provider = null;
    imageDiagram.dispose();
    imageGraph.dispose();
    imageTable.dispose();
    super.dispose();
  }
}