package org.objectstyle.wolips.templateeditor;

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.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextListener;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.TextEvent;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.IVerticalRuler;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.Saveable;
import org.eclipse.ui.texteditor.AbstractTextEditor;
import org.eclipse.ui.texteditor.AnnotationPreferenceLookup;
import org.eclipse.ui.texteditor.MarkerAnnotationPreferences;
import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;
import org.objectstyle.wolips.bindings.wod.IWodElement;
import org.objectstyle.wolips.components.editor.ComponentEditorInteraction;
import org.objectstyle.wolips.components.editor.IEmbeddedEditor;
import org.objectstyle.wolips.locate.LocateException;
import org.objectstyle.wolips.variables.BuildProperties;
import org.objectstyle.wolips.wodclipse.core.Activator;
import org.objectstyle.wolips.wodclipse.core.completion.WodParserCache;
import org.objectstyle.wolips.wodclipse.core.document.ITextWOEditor;
import org.objectstyle.wolips.wodclipse.core.refactoring.DeleteTagRefactoring;
import org.objectstyle.wolips.wodclipse.core.util.WodHtmlUtils;

import tk.eclipse.plugin.htmleditor.HTMLPlugin;
import tk.eclipse.plugin.htmleditor.editors.HTMLConfiguration;
import tk.eclipse.plugin.htmleditor.editors.HTMLSourceEditor;
import tk.eclipse.plugin.htmleditor.editors.IHTMLOutlinePage;

public class TemplateSourceEditor extends HTMLSourceEditor implements ITextWOEditor, IEmbeddedEditor {
  public static final String ACTION_DELETE_TAG = "_template_deleteTag";
  public static final String ACTION_UNWRAP_TAG = "_template_unwrapTag";

  private TemplateOutlinePage _templateOutlinePage;
  private WodParserCache _cache;
  private TemplateBreadcrumb _breadcrumb;
  private boolean _cacheOutOfSync;
  private ComponentEditorInteraction _editorInteraction;

  public TemplateSourceEditor(HTMLConfiguration config) {
    super(config);
    setAction(TemplateSourceEditor.ACTION_DELETE_TAG, new DeleteTagAction());
    setAction(TemplateSourceEditor.ACTION_UNWRAP_TAG, new UnwrapTagAction());
  }

  public void initEditorInteraction(ComponentEditorInteraction editorInteraction) {
    _editorInteraction = editorInteraction;
  }

  public ComponentEditorInteraction getEditorInteraction() {
    return _editorInteraction;
  }

  @Override
  public void doSave(IProgressMonitor monitor) {
    if (_editorInteraction == null || _editorInteraction.embeddedEditorWillSave(monitor)) {
      super.doSave(monitor);
    }
  }

  @Override
  protected void initializeKeyBindingScopes() {
    setKeyBindingScopes(new String[] { "org.objectstyle.wolips.componenteditor.componentEditorScope" }); //$NON-NLS-1$
  }

  @Override
  protected void doSetInput(IEditorInput input) throws CoreException {
    super.doSetInput(input);
    _cache = null;
  }

  @Override
  public MarkerAnnotationPreferences getAnnotationPreferences() {
    return super.getAnnotationPreferences();
  }

  @Override
  public AnnotationPreferenceLookup getAnnotationPreferenceLookup() {
    return super.getAnnotationPreferenceLookup();
  }

  @Override
  public void createPartControl(Composite parent) {
    Composite templateParent = new Composite(parent, SWT.NONE);
    GridLayout templateLayout = new GridLayout();
    templateLayout.marginTop = 0;
    templateLayout.marginLeft = 0;
    templateLayout.marginBottom = 0;
    templateLayout.marginRight = 0;
    templateLayout.marginHeight = 0;
    templateLayout.marginWidth = 0;
    templateLayout.verticalSpacing = 0;
    templateLayout.horizontalSpacing = 0;
    templateParent.setLayout(templateLayout);

    Composite editorParent = new Composite(templateParent, SWT.NONE);
    editorParent.setLayoutData(new GridData(GridData.FILL_BOTH));
    FillLayout editorLayout = new FillLayout();
    editorLayout.marginHeight = 0;
    editorLayout.marginWidth = 0;
    editorParent.setLayout(editorLayout);
    super.createPartControl(editorParent);

    _breadcrumb = new TemplateBreadcrumb(this, templateParent, SWT.NONE);
    _breadcrumb.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

    getViewer().addTextListener(new ITextListener() {
      public void textChanged(TextEvent event) {
        setCacheOutOfSync(true);
      }
    });

  }

  @Override
  protected ISourceViewer createSourceViewer(Composite parent, IVerticalRuler ruler, int styles) {
    return super.createSourceViewer(parent, ruler, styles);
  }

  protected void setCacheOutOfSync(boolean cacheOutOfSync) {
    _cacheOutOfSync = cacheOutOfSync;
  }

  @Override
  protected void handleCursorPositionChanged() {
    super.handleCursorPositionChanged();
  }

  public Point getSelectedRange() {
    return getSourceViewer().getSelectedRange();
  }

  @Override
  public void dispose() {
    try {
      WodParserCache cache = getParserCache();
      cache.getHtmlEntry().setDocument(null);
    }
    catch (Exception e) {
      e.printStackTrace();
    }
    super.dispose();

  }

  @Override
  public void update() {
    try {
      WodParserCache cache = TemplateSourceEditor.this.getParserCache();
      cache.getHtmlEntry().setDocument(getDocumentProvider().getDocument(getEditorInput()));
    }
    catch (Exception e) {
      e.printStackTrace();
    }
    super.update();
  }

  @Override
  protected void doValidate() {
    //    if(!isFileEditorInput()){
    //      return;
    //    }
    try {
      String[] natureIds = HTMLPlugin.getDefault().getNoValidationNatureId();
      IFile file = ((IFileEditorInput) getEditorInput()).getFile();
      for (int i = 0; i < natureIds.length; i++) {
        if (file.getProject().hasNature(natureIds[i])) {
          return;
        }
      }

      boolean autoBuild = ResourcesPlugin.getPlugin().getPluginPreferences().getDefaultBoolean(ResourcesPlugin.PREF_AUTO_BUILDING);
      if (!autoBuild) {
        ResourcesPlugin.getWorkspace().run(new IWorkspaceRunnable() {
          public void run(IProgressMonitor monitor) {
            _validate();
          }
        }, null);
      }
    }
    catch (Exception ex) {
      HTMLPlugin.logException(ex);
    }
  }

  protected void _validate() {
    try {
      WodParserCache cache = TemplateSourceEditor.this.getParserCache();
      cache.parse();
      cache.validate(false, true);
      if (_breadcrumb != null) {
        _breadcrumb.updateBreadcrumb();
      }
    }
    catch (Exception ex) {
      Activator.getDefault().log(ex);
    }
  }

  public WodParserCache getParserCache() throws CoreException, LocateException {
    if (_cache == null) {
      IFileEditorInput input = (IFileEditorInput) getEditorInput();
      IFile inputFile = input.getFile();
      _cache = WodParserCache.parser(inputFile);
    }
    return _cache;
  }

  @Override
  protected void handleElementContentReplaced() {
    super.handleElementContentReplaced();
  }

  public FuzzyXMLDocument getHtmlXmlDocument(boolean refreshModel) throws Exception {
    FuzzyXMLDocument doc;
    if (refreshModel || isDirty()) {
      BuildProperties buildProperties = (BuildProperties)getParserCache().getProject().getAdapter(BuildProperties.class);
      FuzzyXMLParser parser = new FuzzyXMLParser(buildProperties != null ? buildProperties.isWellFormedTemplateRequired() : false, true);
      doc = parser.parse(getHTMLSource());
      getParserCache().getHtmlEntry().setModel(doc);
      setCacheOutOfSync(false);
    }
    else {
      doc = getParserCache().getHtmlEntry().getModel();
    }
    return doc;
  }

  public IWodElement getSelectedElement(boolean resolveWodElement, boolean refreshModel) throws Exception {
    IWodElement wodElement = null;
    WodParserCache cache = getParserCache();
    if (getSelectionProvider() != null) {
      ISelection realSelection = getSelectionProvider().getSelection();
      if (realSelection instanceof ITextSelection) {
        ITextSelection textSelection = (ITextSelection) realSelection;
        FuzzyXMLDocument document = getHtmlXmlDocument(refreshModel);
        if (document != null) {
          FuzzyXMLElement element = document.getElementByOffset(textSelection.getOffset());
          BuildProperties buildProperties = (BuildProperties)cache.getProject().getAdapter(BuildProperties.class);
          wodElement = WodHtmlUtils.getWodElement(element, buildProperties, resolveWodElement, cache);
        }
      }
      else if (realSelection instanceof IStructuredSelection) {
        IStructuredSelection structuredSelection = (IStructuredSelection) realSelection;
        Object obj = structuredSelection.getFirstElement();
        if (obj instanceof FuzzyXMLElement) {
          FuzzyXMLElement element = (FuzzyXMLElement) obj;
          BuildProperties buildProperties = (BuildProperties)cache.getProject().getAdapter(BuildProperties.class);
          wodElement = WodHtmlUtils.getWodElement(element, buildProperties, resolveWodElement, cache);
        }
      }
    }
    return wodElement;
  }

  public TemplateOutlinePage getTemplateOutlinePage() {
    if (_templateOutlinePage == null) {
      _templateOutlinePage = (TemplateOutlinePage) createOutlinePage();
    }
    return _templateOutlinePage;
  }

  @Override
  protected IHTMLOutlinePage createOutlinePage() {
    _templateOutlinePage = new TemplateOutlinePage(this);
    return _templateOutlinePage;
  }

  //  public String getHTMLSource(){
  //    String source = super.getHTMLSource();
  //    source = HTMLUtil.scriptlet2space(source,false);
  //    return source;
  //  }

  @Override
  protected void addContextMenuActions(IMenuManager menu) {
    super.addContextMenuActions(menu);
    addAction(menu, TemplateSourceEditor.GROUP_HTML, TemplateSourceEditor.ACTION_DELETE_TAG);
    addAction(menu, TemplateSourceEditor.GROUP_HTML, TemplateSourceEditor.ACTION_UNWRAP_TAG);
  }

  @Override
  protected void updateSelectionDependentActions() {
    super.updateSelectionDependentActions();
    //    ITextSelection sel = (ITextSelection) getSelectionProvider().getSelection();
    //    if (sel.getText().equals("")) {
    //      getAction(ACTION_JSP_COMMENT).setEnabled(false);
    //    }
    //    else {
    //      getAction(ACTION_JSP_COMMENT).setEnabled(true);
    //    }
  }

  @Override
  protected void rulerContextMenuAboutToShow(IMenuManager menu) {
    super.rulerContextMenuAboutToShow(menu);
  }

  /**
   * Update informations about code-completion.
   */
  @Override
  protected void updateAssist() {
    super.updateAssist();
  }

  public int getOffsetAtPoint(Point point) {
    StyledText st = getViewer().getTextWidget();
    int modelOffset;
    if (!st.getBounds().contains(point)) {
      modelOffset = -1;
    }
    else {
      try {
        int offset = st.getOffsetAtLocation(point);
        modelOffset = AbstractTextEditor.widgetOffset2ModelOffset(getSourceViewer(), offset);
      }
      catch (IllegalArgumentException e) {
        modelOffset = -1;
      }
    }
    return modelOffset;
  }

  public ISourceViewer getWOSourceViewer() {
    return getViewer();
  }

  public StyledText getWOEditorControl() {
    return getViewer().getTextWidget();
  }

  public IWodElement getWodElementAtPoint(Point point, boolean resolveWodElement, boolean refreshModel) throws Exception {
    IWodElement wodElement = null;
    FuzzyXMLElement element = getElementAtPoint(point, refreshModel);
    if (WodHtmlUtils.isWOTag(element)) {
      WodParserCache cache = getParserCache();
      BuildProperties buildProperties = (BuildProperties)cache.getProject().getAdapter(BuildProperties.class);
      wodElement = WodHtmlUtils.getWodElement(element, buildProperties, resolveWodElement, cache);
    }
    return wodElement;
  }

  public FuzzyXMLElement getElementAtPoint(Point point, boolean refreshModel) throws Exception {
    int offset = getOffsetAtPoint(point);
    FuzzyXMLElement element = getElementAtOffset(offset, refreshModel);
    return element;
  }

  public FuzzyXMLElement getElementAtOffset(int offset, boolean refreshModel) throws Exception {
    FuzzyXMLElement element = null;
    if (offset >= 0) {
      FuzzyXMLDocument xmlDocument = getHtmlXmlDocument(refreshModel);
      if (xmlDocument != null) {
        element = xmlDocument.getElementByOffset(offset);
      }
    }
    return element;
  }

  public IRegion getSelectionRegionAtPoint(Point point, boolean regionForInsert, boolean refreshModel) throws Exception {
    int offset = getOffsetAtPoint(point);
    return getSelectionRegionAtOffset(offset, regionForInsert, refreshModel);
  }

  public IRegion getSelectionRegionForElementAtPoint(FuzzyXMLElement element, Point point, boolean regionForInsert) throws Exception {
    int offset = getOffsetAtPoint(point);
    return getSelectionRegionForElementAtOffset(element, offset, regionForInsert);
  }

  public IRegion getSelectionRegionAtOffset(int offset, boolean regionForInsert, boolean refreshModel) throws Exception {
    FuzzyXMLElement element = getElementAtOffset(offset, refreshModel);
    IRegion region = getSelectionRegionForElementAtOffset(element, offset, regionForInsert);
    return region;
  }

  public IRegion getSelectionRegionForElementAtOffset(FuzzyXMLElement element, int offset, boolean regionForInsert) throws Exception {
    IRegion region = null;
    if (element != null) {
      IDocument doc = getParserCache().getHtmlEntry().getDocument();
      region = element.getRegionAtOffset(offset, doc, regionForInsert);
    }
    return region;
  }

  @Override
  protected SourceViewerDecorationSupport getSourceViewerDecorationSupport(ISourceViewer viewer) {
    if (fSourceViewerDecorationSupport == null) {
      fSourceViewerDecorationSupport = new TemplateSourceViewerDecorationSupport(viewer, getOverviewRuler(), getAnnotationAccess(), getSharedColors());
      configureSourceViewerDecorationSupport(fSourceViewerDecorationSupport);
    }
    return fSourceViewerDecorationSupport;
  }

  @Override
  public Saveable[] getSaveables() {
    return super.getSaveables();
  }

  public class DeleteTagAction extends Action {
    public DeleteTagAction() {
      super("Delete Tag");
    }

    @Override
    public void run() {
      try {
        ITextSelection templateSelection = (ITextSelection) getSelectionProvider().getSelection();
        if (templateSelection != null) {
          int offset = templateSelection.getOffset();
          FuzzyXMLElement element = getElementAtOffset(offset, true);
          if (element != null) {
            BuildProperties buildProperties = (BuildProperties)getParserCache().getProject().getAdapter(BuildProperties.class);
            DeleteTagRefactoring.run(element, false, buildProperties, getParserCache(), new NullProgressMonitor());
          }
        }
      }
      catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  public class UnwrapTagAction extends Action {
    public UnwrapTagAction() {
      super("Unwrap Tag");
    }

    @Override
    public void run() {
      try {
        ITextSelection templateSelection = (ITextSelection) getSelectionProvider().getSelection();
        if (templateSelection != null) {
          int offset = templateSelection.getOffset();
          FuzzyXMLElement element = getElementAtOffset(offset, true);
          if (element != null) {
            BuildProperties buildProperties = (BuildProperties)getParserCache().getProject().getAdapter(BuildProperties.class);
            DeleteTagRefactoring.run(element, true, buildProperties, getParserCache(), new NullProgressMonitor());
          }
        }
      }
      catch (Exception e) {
        e.printStackTrace();
      }
    }
  }
}
