package tk.eclipse.plugin.htmleditor.editors;

import java.util.ArrayList;
import java.util.List;

import jp.aonir.fuzzyxml.FuzzyXMLAttribute;
import jp.aonir.fuzzyxml.FuzzyXMLCDATA;
import jp.aonir.fuzzyxml.FuzzyXMLComment;
import jp.aonir.fuzzyxml.FuzzyXMLDocType;
import jp.aonir.fuzzyxml.FuzzyXMLDocument;
import jp.aonir.fuzzyxml.FuzzyXMLElement;
import jp.aonir.fuzzyxml.FuzzyXMLNode;
import jp.aonir.fuzzyxml.FuzzyXMLParser;

import org.eclipse.core.resources.IFile;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.views.contentoutline.ContentOutlinePage;

import tk.eclipse.plugin.htmleditor.HTMLPlugin;

/**
 * An implementaion of IContentOutlinePage for the HTML editor.
 * This shows the outline of HTML document.
 */
public class HTMLOutlinePage extends ContentOutlinePage implements IHTMLOutlinePage {

  private RootNode root;
  private HTMLSourceEditor editor;
  private FuzzyXMLDocument doc;

  public HTMLOutlinePage(HTMLSourceEditor editor) {
    super();
    this.editor = editor;
  }

  public FuzzyXMLDocument getDoc() {
    return doc;
  }

  @Override
  public void createControl(Composite parent) {
    super.createControl(parent);
    TreeViewer viewer = getTreeViewer();
    root = new RootNode();
    viewer.setContentProvider(new HTMLContentProvider());
    viewer.setLabelProvider(new HTMLLabelProvider());
    viewer.setInput(root);
    viewer.addSelectionChangedListener(new HTMLSelectionChangedListener());

    try {
      viewer.setExpandedState(root.getChildren()[0], true);
    }
    catch (Exception ex) {
      // ignore
    }

    update();
  }

  protected boolean isHTML() {
    return true;
  }

  protected FuzzyXMLParser createParser() {
    FuzzyXMLParser parser = new FuzzyXMLParser(isHTML());
    return parser;
  }

  public void update() {
    if (getControl() == null || getControl().isDisposed()) {
      return;
    }
    IFile file = ((FileEditorInput) editor.getEditorInput()).getFile();
    try {
      //this.doc = WodParserCache.parser(file).getHtmlDocument();
      //if (this.doc == null) {
      this.doc = createParser().parse(editor.getHTMLSource());
      //}

      TreeViewer viewer = getTreeViewer();
      if (viewer != null) {
        viewer.refresh();
      }
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

  protected Image getNodeImage(FuzzyXMLNode element) {
    if (element instanceof FuzzyXMLCDATA) {
      return null;
    }
    else if (element instanceof FuzzyXMLElement) {
      FuzzyXMLElement e = (FuzzyXMLElement) element;
      if (e.getName().equalsIgnoreCase("html")) {
        return HTMLPlugin.getDefault().getImageRegistry().get(HTMLPlugin.ICON_TAG_HTML);
      }
      else if (e.getName().equalsIgnoreCase("title")) {
        return HTMLPlugin.getDefault().getImageRegistry().get(HTMLPlugin.ICON_TITLE);
      }
      else if (e.getName().equalsIgnoreCase("body")) {
        return HTMLPlugin.getDefault().getImageRegistry().get(HTMLPlugin.ICON_BODY);
      }
      else if (e.getName().equalsIgnoreCase("form")) {
        return HTMLPlugin.getDefault().getImageRegistry().get(HTMLPlugin.ICON_FORM);
      }
      else if (e.getName().equalsIgnoreCase("img")) {
        return HTMLPlugin.getDefault().getImageRegistry().get(HTMLPlugin.ICON_IMAGE);
      }
      else if (e.getName().equalsIgnoreCase("a")) {
        return HTMLPlugin.getDefault().getImageRegistry().get(HTMLPlugin.ICON_LINK);
      }
      else if (e.getName().equalsIgnoreCase("table")) {
        return HTMLPlugin.getDefault().getImageRegistry().get(HTMLPlugin.ICON_TABLE);
      }
      else if (e.getName().equalsIgnoreCase("input")) {
        String type = e.getAttributeValue("type");
        if (type != null) {
          if (type.equalsIgnoreCase("button") || type.equalsIgnoreCase("reset") || type.equalsIgnoreCase("submit")) {
            return HTMLPlugin.getDefault().getImageRegistry().get(HTMLPlugin.ICON_BUTTON);
          }
          else if (type.equalsIgnoreCase("radio")) {
            return HTMLPlugin.getDefault().getImageRegistry().get(HTMLPlugin.ICON_RADIO);
          }
          else if (type.equalsIgnoreCase("checkbox")) {
            return HTMLPlugin.getDefault().getImageRegistry().get(HTMLPlugin.ICON_CHECK);
          }
          else if (type.equalsIgnoreCase("text")) {
            return HTMLPlugin.getDefault().getImageRegistry().get(HTMLPlugin.ICON_TEXT);
          }
          else if (type.equalsIgnoreCase("password")) {
            return HTMLPlugin.getDefault().getImageRegistry().get(HTMLPlugin.ICON_PASS);
          }
          else if (type.equalsIgnoreCase("hidden")) {
            return HTMLPlugin.getDefault().getImageRegistry().get(HTMLPlugin.ICON_HIDDEN);
          }
        }
      }
      else if (e.getName().equalsIgnoreCase("select")) {
        return HTMLPlugin.getDefault().getImageRegistry().get(HTMLPlugin.ICON_SELECT);
      }
      else if (e.getName().equalsIgnoreCase("textarea")) {
        return HTMLPlugin.getDefault().getImageRegistry().get(HTMLPlugin.ICON_TEXTAREA);
      }
      return HTMLPlugin.getDefault().getImageRegistry().get(HTMLPlugin.ICON_TAG);
    }
    else if (element instanceof FuzzyXMLDocType) {
      return HTMLPlugin.getDefault().getImageRegistry().get(HTMLPlugin.ICON_DOCTYPE);
    }
    else if (element instanceof FuzzyXMLComment) {
      return HTMLPlugin.getDefault().getImageRegistry().get(HTMLPlugin.ICON_COMMENT);
    }
    return null;
  }

  protected Object[] getNodeChildren(FuzzyXMLElement element) {
    List<FuzzyXMLNode> children = new ArrayList<FuzzyXMLNode>();
    FuzzyXMLNode[] nodes = element.getChildren();
    for (int i = 0; i < nodes.length; i++) {
      if (nodes[i] instanceof FuzzyXMLElement) {
        children.add(nodes[i]);
      }
      else if (nodes[i] instanceof FuzzyXMLDocType) {
        children.add(nodes[i]);
      }
      else if (nodes[i] instanceof FuzzyXMLComment) {
        children.add(nodes[i]);
      }
    }
    return children.toArray(new FuzzyXMLNode[children.size()]);
  }

  protected String getNodeText(FuzzyXMLNode node) {
    if (node instanceof FuzzyXMLCDATA) {
      return "CDATA";
    }
    if (node instanceof FuzzyXMLElement) {
      StringBuffer sb = new StringBuffer();
      FuzzyXMLAttribute[] attrs = ((FuzzyXMLElement) node).getAttributes();
      for (int i = 0; i < attrs.length; i++) {
        if (sb.length() != 0) {
          sb.append(", ");
        }
        sb.append(attrs[i].getName() + "=" + attrs[i].getValue());
      }
      if (sb.length() == 0) {
        return ((FuzzyXMLElement) node).getName();
      }
      else {
        return ((FuzzyXMLElement) node).getName() + "(" + sb.toString() + ")";
      }
    }
    if (node instanceof FuzzyXMLDocType) {
      return "DOCTYPE";
    }
    if (node instanceof FuzzyXMLComment) {
      return "#comment";
    }
    return node.toString();
  }

  /** root element. */
  private class RootNode {
    private boolean _updating;

    public RootNode() {
      super();
    }

    public Object[] getChildren() {
      //			IFile  file = ((FileEditorInput)editor.getEditorInput()).getFile();
      //			return new FileNode[]{
      //				new FileNode(file.getName())
      //			};
      List<FuzzyXMLNode> children = new ArrayList<FuzzyXMLNode>();
      if (doc == null) {
        if (_updating) {
          System.out.println("RootNode.getChildren: Attempted to update root node while already in an update pass.");
        }
        else {
          _updating = true;
          try {
            update();
          }
          finally {
            _updating = false;
          }
        }
      }

      if (doc != null) {
        if (doc.getDocumentType() != null) {
          children.add(doc.getDocumentType());
        }
        FuzzyXMLNode[] nodes = doc.getDocumentElement().getChildren();
        for (int i = 0; i < nodes.length; i++) {
          if (nodes[i] instanceof FuzzyXMLElement) {
            children.add(nodes[i]);
            //				} else if(nodes[i] instanceof FuzzyXMLText && ((FuzzyXMLText)nodes[i]).getValue().startsWith("<%")){
            //					children.add(nodes[i]);
          }
        }
      }

      return children.toArray(new FuzzyXMLNode[children.size()]);
    }

    @Override
    public boolean equals(Object obj) {
      if (obj instanceof RootNode) {
        return true;
      }
      return false;
    }
  }

  /** ContentProvider of HTMLOutlinePage. */
  private class HTMLContentProvider implements ITreeContentProvider {

    public Object[] getChildren(Object parentElement) {
      if (parentElement instanceof RootNode) {
        return ((RootNode) parentElement).getChildren();
      }
      else if (parentElement instanceof FuzzyXMLElement) {
        return getNodeChildren((FuzzyXMLElement) parentElement);
      }
      return new Object[0];
    }

    public Object getParent(Object element) {
      if (element instanceof FuzzyXMLNode) {
        FuzzyXMLNode parent = ((FuzzyXMLNode) element).getParentNode();
        if (parent == null) {
          return root.getChildren()[0];
        }
        return parent;
      }
      return null;
    }

    public boolean hasChildren(Object element) {
      if (getChildren(element).length == 0) {
        return false;
      }
      else {
        return true;
      }
    }

    public Object[] getElements(Object inputElement) {
      return getChildren(inputElement);
    }

    public void dispose() {
    }

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

  /** LabelProvider of HTMLOutlinePage */
  private class HTMLLabelProvider extends LabelProvider {

    @Override
    public Image getImage(Object element) {
      if (element instanceof FuzzyXMLNode) {
        return getNodeImage((FuzzyXMLNode) element);
      }
      return null;
    }

    @Override
    public String getText(Object element) {
      if (element instanceof FuzzyXMLNode) {
        return getNodeText((FuzzyXMLNode) element);
      }
      return super.getText(element);
    }
  }

  /** This listener is called when selection of TreeViewer is changed. */
  private class HTMLSelectionChangedListener implements ISelectionChangedListener {
    public void selectionChanged(SelectionChangedEvent event) {
      IStructuredSelection sel = (IStructuredSelection) event.getSelection();
      Object element = sel.getFirstElement();
      if (element instanceof FuzzyXMLNode) {
        int offset = ((FuzzyXMLNode) element).getOffset();
        IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
        IEditorPart editorPart = page.getActiveEditor();
        if (editorPart instanceof HTMLEditor) {
          ((HTMLEditor) editorPart).setOffset(offset);
        }
        else if (editorPart instanceof HTMLSourceEditor) {
          ((HTMLSourceEditor) editorPart).selectAndReveal(offset, 0);
        }
        else {
          HTMLSourceEditor sourceEditor = (HTMLSourceEditor) editorPart.getAdapter(HTMLSourceEditor.class);
          if (sourceEditor != null) {
            sourceEditor.selectAndReveal(offset, 0);
          }
        }
      }
    }
  }
}
