package tk.eclipse.plugin.csseditor.editors;

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

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewerExtension2;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.IVerticalRuler;
import org.eclipse.jface.text.source.MatchingCharacterPainter;
import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel;
import org.eclipse.jface.text.source.projection.ProjectionSupport;
import org.eclipse.jface.text.source.projection.ProjectionViewer;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IStorageEditorInput;
import org.eclipse.ui.editors.text.TextEditor;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
import org.eclipse.ui.texteditor.ChainedPreferenceStore;
import org.eclipse.ui.texteditor.ContentAssistAction;
import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;

import tk.eclipse.plugin.htmleditor.ColorProvider;
import tk.eclipse.plugin.htmleditor.HTMLPlugin;
import tk.eclipse.plugin.htmleditor.editors.FoldingInfo;
import tk.eclipse.plugin.htmleditor.editors.SoftTabVerifyListener;

/**
 * CSS Editor
 * 
 * @author Naoki Takezoe
 */
public class CSSEditor extends TextEditor {

  private ColorProvider colorProvider;
  private CSSOutlinePage outline;
  private CSSCharacterPairMatcher pairMatcher;
  private SoftTabVerifyListener softTabListener;
  private ProjectionSupport fProjectionSupport;

  public static final String GROUP_CSS = "_css";
  public static final String ACTION_CHOOSE_COLOR = "_choose_color";

  public CSSEditor() {
    super();
    colorProvider = HTMLPlugin.getDefault().getColorProvider();
    setSourceViewerConfiguration(new CSSConfiguration(colorProvider));
    setPreferenceStore(new ChainedPreferenceStore(new IPreferenceStore[] { getPreferenceStore(), HTMLPlugin.getDefault().getPreferenceStore() }));

    outline = new CSSOutlinePage(this);

    setAction(ACTION_CHOOSE_COLOR, new ChooseColorAction(this));

    IPreferenceStore store = HTMLPlugin.getDefault().getPreferenceStore();
    softTabListener = new SoftTabVerifyListener();
    softTabListener.setUseSoftTab(store.getBoolean(HTMLPlugin.PREF_USE_SOFTTAB));
    softTabListener.setSoftTabWidth(store.getInt(HTMLPlugin.PREF_SOFTTAB_WIDTH));
  }

  @Override
  protected final void editorContextMenuAboutToShow(IMenuManager menu) {
    super.editorContextMenuAboutToShow(menu);
    menu.add(new Separator(GROUP_CSS));
    addAction(menu, GROUP_CSS, ACTION_CHOOSE_COLOR);
  }

  @Override
  protected void doSetInput(IEditorInput input) throws CoreException {
    if (input instanceof IFileEditorInput) {
      setDocumentProvider(new CSSTextDocumentProvider());
    }
    else if (input instanceof IStorageEditorInput) {
      setDocumentProvider(new CSSFileDocumentProvider());
    }
    else {
      setDocumentProvider(new CSSTextDocumentProvider());
    }
    super.doSetInput(input);
  }

  @Override
  public void doSave(IProgressMonitor progressMonitor) {
    super.doSave(progressMonitor);
    outline.update();
    updateFolding();
  }

  @Override
  protected ISourceViewer createSourceViewer(Composite parent, IVerticalRuler ruler, int styles) {
    ISourceViewer viewer = new ProjectionViewer(parent, ruler, getOverviewRuler(), true, styles);
    getSourceViewerDecorationSupport(viewer);
    viewer.getTextWidget().addVerifyListener(softTabListener);
    return viewer;
  }

  @Override
  public void createPartControl(Composite parent) {
    super.createPartControl(parent);

    ProjectionViewer viewer = (ProjectionViewer) getSourceViewer();
    fProjectionSupport = new ProjectionSupport(viewer, getAnnotationAccess(), getSharedColors());
    fProjectionSupport.install();
    viewer.doOperation(ProjectionViewer.TOGGLE);
    updateFolding();

    StyledText widget = viewer.getTextWidget();
    widget.setTabs(getPreferenceStore().getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH));
    widget.addVerifyListener(new SoftTabVerifyListener());

    ITextViewerExtension2 extension = (ITextViewerExtension2) getSourceViewer();
    pairMatcher = new CSSCharacterPairMatcher();
    pairMatcher.setEnable(getPreferenceStore().getBoolean(HTMLPlugin.PREF_PAIR_CHAR));
    MatchingCharacterPainter painter = new MatchingCharacterPainter(getSourceViewer(), pairMatcher);
    painter.setColor(Display.getDefault().getSystemColor(SWT.COLOR_GRAY));
    extension.addPainter(painter);
  }

  @Override
  public void dispose() {
    pairMatcher.dispose();
    fProjectionSupport.dispose();
    super.dispose();
  }

  @Override
  public void doSaveAs() {
    super.doSaveAs();
    outline.update();
    updateFolding();
  }

  @Override
  protected boolean affectsTextPresentation(PropertyChangeEvent event) {
    return super.affectsTextPresentation(event) || colorProvider.affectsTextPresentation(event);
  }

  @Override
  protected void handlePreferenceStoreChanged(PropertyChangeEvent event) {
    colorProvider.handlePreferenceStoreChanged(event);

    String key = event.getProperty();
    if (key.equals(HTMLPlugin.PREF_PAIR_CHAR)) {
      boolean enable = ((Boolean) event.getNewValue()).booleanValue();
      pairMatcher.setEnable(enable);
    }

    super.handlePreferenceStoreChanged(event);
    softTabListener.preferenceChanged(event);
  }

  @Override
  protected void createActions() {
    super.createActions();
    // Add a content assist action
    IAction action = new ContentAssistAction(HTMLPlugin.getDefault().getResourceBundle(), "ContentAssistProposal", this);
    action.setActionDefinitionId(ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS);
    setAction("ContentAssistProposal", action);
  }

  @Override
  public Object getAdapter(Class adapter) {
    if (IContentOutlinePage.class.equals(adapter)) {
      return outline;
    }
    if (ProjectionAnnotationModel.class.equals(adapter) && fProjectionSupport != null) {
      Object obj = fProjectionSupport.getAdapter(getSourceViewer(), adapter);
      if (obj != null) {
        return obj;
      }
    }
    return super.getAdapter(adapter);
  }

  private static final int FOLDING_NONE = 0;
  private static final int FOLDING_STYLE = 1;
  private static final int FOLDING_COMMENT = 2;

  /**
   * Update folding informations.
   */
  private void updateFolding() {
    try {
      ProjectionViewer viewer = (ProjectionViewer) getSourceViewer();
      if (viewer == null) {
        return;
      }
      ProjectionAnnotationModel model = viewer.getProjectionAnnotationModel();
      if (model == null) {
        return;
      }

      List<FoldingInfo> list = new ArrayList<FoldingInfo>();
      IDocument doc = getDocumentProvider().getDocument(getEditorInput());
      String source = doc.get();

      int type = FOLDING_NONE;
      int start = -1;
      int startBackup = -1;

      for (int i = 0; i < source.length(); i++) {
        char c = source.charAt(i);
        // start comment
        if (c == '/' && type != FOLDING_COMMENT && source.length() > i + 1) {
          if (source.charAt(i + 1) == '*') {
            if (type == FOLDING_STYLE) {
              startBackup = start;
            }
            type = FOLDING_COMMENT;
            start = i;
            i++;
          }
          // end comment
        }
        else if (c == '*' && type == FOLDING_COMMENT && source.length() > i + 1) {
          if (source.charAt(i + 1) == '/') {
            if (doc.getLineOfOffset(start) != doc.getLineOfOffset(i)) {
              list.add(new FoldingInfo(start, i + 2 + FoldingInfo.countUpLineDelimiter(source, i + 2)));
            }
            if (startBackup != -1) {
              type = FOLDING_STYLE;
              start = startBackup;
            }
            else {
              type = FOLDING_NONE;
            }
            startBackup = -1;
            i++;
          }
          // start blace
        }
        else if (c == '{' && type == FOLDING_NONE) {
          if (type == FOLDING_COMMENT) {
            startBackup = start;
          }
          start = i;
          type = FOLDING_STYLE;
          // end blace
        }
        else if (type == FOLDING_STYLE && c == '}') {
          if (doc.getLineOfOffset(start) != doc.getLineOfOffset(i)) {
            list.add(new FoldingInfo(start, i + 1 + FoldingInfo.countUpLineDelimiter(source, i + 1)));
          }
          if (startBackup != -1) {
            type = FOLDING_COMMENT;
            start = startBackup;
          }
          startBackup = -1;
          type = FOLDING_NONE;
        }
      }

      FoldingInfo.applyModifiedAnnotations(model, list);

    }
    catch (Exception ex) {
      HTMLPlugin.logException(ex);
    }
  }

}
