package tk.eclipse.plugin.htmleditor;

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

import jp.aonir.fuzzyxml.FuzzyXMLAttribute;
import jp.aonir.fuzzyxml.FuzzyXMLDocument;
import jp.aonir.fuzzyxml.FuzzyXMLElement;
import jp.aonir.fuzzyxml.FuzzyXMLParser;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.hyperlink.IHyperlink;
import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;

import tk.eclipse.plugin.htmleditor.editors.HTMLHyperlinkInfo;
import tk.eclipse.plugin.htmleditor.editors.HTMLSourceEditor;

/**
 * The <code>IHyperlinkDetector</code> implementation for 
 * the <code>HTMLSourceEditor</code>.
 * <p>
 * This class detects the <strong>href</string> attribute
 * as the hyperlink in default. And it's possible to add
 * additional rules by <code>addHyperlinkProvider()</code>.
 * 
 * @author Naoki Takezoe
 * @see tk.eclipse.plugin.htmleditor.editors.HTMLSourceEditor
 * @see tk.eclipse.plugin.htmleditor.IHyperlinkProvider
 */
public class HTMLHyperlinkDetector implements IHyperlinkDetector {

  private HTMLSourceEditor editor;
  private List<IHyperlinkProvider> providers = new ArrayList<IHyperlinkProvider>();

  /**
   * @param editor the <code>HTMLSourceEditor</code> instance
   */
  public void setEditor(HTMLSourceEditor editor) {
    this.editor = editor;
  }

  /**
   * Adds the additional hyperlink provider.
   * 
   * @param provider the additional hyperlink provider
   */
  public void addHyperlinkProvider(IHyperlinkProvider provider) {
    this.providers.add(provider);
  }

  /**
   * Returns the <code>IProject</code> of the editing file.
   * <p>
   * If the editor input isn't <code>IFileEditorInput</code>,
   * this method returns <code>null</code>.
   * 
   * @return the <code>IProject</code>
   */
  private IProject getProject() {
    IEditorInput input = editor.getEditorInput();
    if (input instanceof IFileEditorInput) {
      return ((IFileEditorInput) input).getFile().getProject();
    }
    return null;
  }

  public IHyperlink[] detectHyperlinks(ITextViewer textViewer, IRegion region, boolean canShowMultipleHyperlinks) {
    IHyperlink hyperlink = detectHyperlink(textViewer.getDocument(), region.getOffset());
    if (hyperlink != null) {
      return new IHyperlink[] { hyperlink };
    }
    return null;
  }

  private IHyperlink detectHyperlink(IDocument doc, int offset) {
    FuzzyXMLDocument document = new FuzzyXMLParser(false).parse(editor.getHTMLSource());
    FuzzyXMLElement element = document.getElementByOffset(offset);
    if (element == null) {
      return null;
    }
    FuzzyXMLAttribute[] attrs = element.getAttributes();
    HTMLHyperlinkInfo info = getOpenFileInfo(document, element, null, null, offset);
    if (info != null) {
      return (IHyperlink) info.getObject();
    }
    else {
      for (int i = 0; i < attrs.length; i++) {
        if (attrs[i].getOffset() < offset && offset < attrs[i].getOffset() + attrs[i].getLength()) {
          int attrOffset = getAttributeValueOffset(doc.get(), attrs[i]);
          int attrLength = attrs[i].getValue().length();
          if (attrOffset >= 0 && attrLength >= 0 && attrOffset <= offset) {
            info = getOpenFileInfo(document, element, attrs[i].getName(), attrs[i].getValue(), offset - attrOffset);
            IHyperlink hyperlink = null;
            if (info != null && info.getObject() != null) {
              if (info.getObject() instanceof IHyperlink) {
                hyperlink = (IHyperlink) info.getObject();
              }
              else {
                hyperlink = new HTMLHyperlink(new Region(attrOffset + info.getOffset(), info.getLength()), info.getObject());
              }
            }
            return hyperlink;
          }
        }
      }
    }
    return null;
  }

  /**
   * Returns a target of hyperlink.
   */
  private HTMLHyperlinkInfo getOpenFileInfo(FuzzyXMLDocument doc, FuzzyXMLElement element, String attrName, String attrValue, int offset) {
    try {
      IProject project = getProject();
      if (project == null) {
        return null;
      }
      IFile file = ((IFileEditorInput) editor.getEditorInput()).getFile();
      for (int i = 0; i < providers.size(); i++) {
        IHyperlinkProvider provider = this.providers.get(i);
        HTMLHyperlinkInfo info = provider.getHyperlinkInfo(file, doc, element, attrName, attrValue, offset);
        if (info != null && info.getObject() != null) {
          return info;
        }
      }
      if (attrName != null && attrName.equalsIgnoreCase("href")) {
        String href = attrValue;
        if (href.indexOf("#") > 0) {
          href = href.substring(0, href.indexOf("#"));
        }
        IPath path = file.getParent().getProjectRelativePath();
        IResource resource = project.findMember(path.append(href));
        if (resource != null && resource.exists() && resource instanceof IFile) {
          HTMLHyperlinkInfo info = new HTMLHyperlinkInfo();
          info.setObject(resource);
          info.setOffset(0);
          info.setLength(attrValue.length());
          return info;
        }
      }
    }
    catch (Exception ex) {
      HTMLPlugin.logException(ex);
    }
    return null;
  }

  /**
   * Returns an attribute value offset.
   * 
   * @param source the source code
   * @param attr the attribute
   * @return the offset of the attribute
   */
  private int getAttributeValueOffset(String source, FuzzyXMLAttribute attr) {
    int offset = source.indexOf('=', attr.getOffset());
    if (offset == -1) {
      return -1;
    }
    char c = ' ';
    while (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '"' || c == '\'') {
      offset++;
      if (source.length() == offset + 1) {
        break;
      }
      c = source.charAt(offset);
    }
    return offset;
  }

  private class HTMLHyperlink implements IHyperlink {

    private IRegion region;
    private Object openObject;

    public HTMLHyperlink(IRegion region, Object openObject) {
      this.region = region;
      this.openObject = openObject;
    }

    public IRegion getHyperlinkRegion() {
      return region;
    }

    public String getTypeLabel() {
      return null;
    }

    public String getHyperlinkText() {
      return null;
    }

    public void open() {
      try {
        if (openObject instanceof IFile) {
          IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
          IDE.openEditor(window.getActivePage(), (IFile) openObject, true);
        }
        else if (openObject instanceof IJavaElement) {
          JavaUI.revealInEditor(JavaUI.openInEditor((IJavaElement) openObject), (IJavaElement) openObject);
        }
      }
      catch (Exception ex) {
        HTMLPlugin.logException(ex);
      }
    }

  }

}
