/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.uima.ruta.ide.ui.text;

import java.util.ArrayList;
import java.util.Iterator;

import org.apache.uima.ruta.ide.RutaIdeUIPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.dltk.ui.DLTKUIPlugin;
import org.eclipse.dltk.ui.PreferenceConstants;
import org.eclipse.dltk.ui.text.IColorManager;
import org.eclipse.jface.internal.text.html.HTMLTextPresenter;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DefaultInformationControl;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IInformationControl;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.quickassist.IQuickAssistAssistant;
import org.eclipse.jface.text.quickassist.QuickAssistAssistant;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.ITextEditor;

public class RutaCorrectionAssistant extends QuickAssistAssistant {

  private ITextViewer fViewer;

  private ITextEditor fEditor;

  private Position fPosition;

  private Annotation[] fCurrentAnnotations;

  // private RutaQuickAssistLightBulbUpdater fLightBulbUpdater;

  /**
   * Constructor for JavaCorrectionAssistant.
   * 
   * @param editor
   *          the editor
   */
  public RutaCorrectionAssistant(ITextEditor editor) {
    super();
    Assert.isNotNull(editor);
    fEditor = editor;

    RutaCorrectionProcessor processor = new RutaCorrectionProcessor(this);

    setQuickAssistProcessor(processor);

    setInformationControlCreator(getInformationControlCreator());

    RutaTextTools textTools = RutaIdeUIPlugin.getDefault().getTextTools();
    IColorManager manager = textTools.getColorManager();

    IPreferenceStore store = RutaIdeUIPlugin.getDefault().getPreferenceStore();

    Color c = getColor(store, PreferenceConstants.CODEASSIST_PROPOSALS_FOREGROUND, manager);
    setProposalSelectorForeground(c);

    c = getColor(store, PreferenceConstants.CODEASSIST_PROPOSALS_BACKGROUND, manager);
    setProposalSelectorBackground(c);
  }

  public IEditorPart getEditor() {
    return fEditor;
  }

  private IInformationControlCreator getInformationControlCreator() {
    return new IInformationControlCreator() {
      public IInformationControl createInformationControl(Shell parent) {
        return new DefaultInformationControl(parent, new HTMLTextPresenter());
      }
    };
  }

  private static Color getColor(IPreferenceStore store, String key, IColorManager manager) {
    RGB rgb = PreferenceConverter.getColor(store, key);
    return manager.getColor(rgb);
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.eclipse.jface.text.contentassist.IContentAssistant#install(org.eclipse
   * .jface.text.ITextViewer)
   */
  @Override
  public void install(ISourceViewer sourceViewer) {
    super.install(sourceViewer);
    fViewer = sourceViewer;

    // fLightBulbUpdater = new
    // RutaQuickAssistLightBulbUpdater(fEditor,
    // sourceViewer);
    // fLightBulbUpdater.install();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.eclipse.jface.text.contentassist.ContentAssistant#uninstall()
   */
  @Override
  public void uninstall() {
    // if (fLightBulbUpdater != null) {
    // fLightBulbUpdater.uninstall();
    // fLightBulbUpdater = null;
    // }
    super.uninstall();
  }

  /*
   * @seeorg.eclipse.jface.text.quickassist.QuickAssistAssistant# showPossibleQuickAssists()
   * 
   * @since 3.2
   */

  /**
   * Show completions at caret position. If current position does not contain quick fixes look for
   * next quick fix on same line by moving from left to right and restarting at end of line if the
   * beginning of the line is reached.
   * 
   * @see IQuickAssistAssistant#showPossibleQuickAssists()
   */
  @Override
  public String showPossibleQuickAssists() {
    fPosition = null;
    fCurrentAnnotations = null;

    if (fViewer == null || fViewer.getDocument() == null)
      // Let superclass deal with this
      return super.showPossibleQuickAssists();

    ArrayList resultingAnnotations = new ArrayList(20);
    try {
      Point selectedRange = fViewer.getSelectedRange();
      int currOffset = selectedRange.x;
      int currLength = selectedRange.y;
      boolean goToClosest = (currLength == 0);

      int newOffset = collectQuickFixableAnnotations(fEditor, currOffset, goToClosest,
              resultingAnnotations);
      if (newOffset != currOffset) {
        storePosition(currOffset, currLength);
        fViewer.setSelectedRange(newOffset, 0);
        fViewer.revealRange(newOffset, 0);
      }
    } catch (BadLocationException e) {
      // JavaPlugin.log(e);
    }
    fCurrentAnnotations = (Annotation[]) resultingAnnotations
            .toArray(new Annotation[resultingAnnotations.size()]);

    return super.showPossibleQuickAssists();
  }

  private static IRegion getRegionOfInterest(ITextEditor editor, int invocationLocation)
          throws BadLocationException {
    IDocumentProvider documentProvider = editor.getDocumentProvider();
    if (documentProvider == null) {
      return null;
    }
    IDocument document = documentProvider.getDocument(editor.getEditorInput());
    if (document == null) {
      return null;
    }
    return document.getLineInformationOfOffset(invocationLocation);
  }

  public static int collectQuickFixableAnnotations(ITextEditor editor, int invocationLocation,
          boolean goToClosest, ArrayList resultingAnnotations) throws BadLocationException {
    IAnnotationModel model = DLTKUIPlugin.getDocumentProvider().getAnnotationModel(
            editor.getEditorInput());
    if (model == null) {
      return invocationLocation;
    }

    ensureUpdatedAnnotations(editor);

    Iterator iter = model.getAnnotationIterator();
    if (goToClosest) {
      IRegion lineInfo = getRegionOfInterest(editor, invocationLocation);
      if (lineInfo == null) {
        return invocationLocation;
      }
      int rangeStart = lineInfo.getOffset();
      int rangeEnd = rangeStart + lineInfo.getLength();

      ArrayList allAnnotations = new ArrayList();
      ArrayList allPositions = new ArrayList();
      int bestOffset = Integer.MAX_VALUE;
      while (iter.hasNext()) {
        Annotation annot = (Annotation) iter.next();
        if (RutaCorrectionProcessor.isQuickFixableType(annot)) {
          Position pos = model.getPosition(annot);
          if (pos != null && isInside(pos.offset, rangeStart, rangeEnd)) { // inside
            // our
            // range?
            allAnnotations.add(annot);
            allPositions.add(pos);
            bestOffset = processAnnotation(annot, pos, invocationLocation, bestOffset);
          }
        }
      }
      if (bestOffset == Integer.MAX_VALUE) {
        return invocationLocation;
      }
      for (int i = 0; i < allPositions.size(); i++) {
        Position pos = (Position) allPositions.get(i);
        if (isInside(bestOffset, pos.offset, pos.offset + pos.length)) {
          resultingAnnotations.add(allAnnotations.get(i));
        }
      }
      return bestOffset;
    } else {
      while (iter.hasNext()) {
        Annotation annot = (Annotation) iter.next();
        if (RutaCorrectionProcessor.isQuickFixableType(annot)) {
          Position pos = model.getPosition(annot);
          if (pos != null && isInside(invocationLocation, pos.offset, pos.offset + pos.length)) {
            resultingAnnotations.add(annot);
          }
        }
      }
      return invocationLocation;
    }
  }

  private static void ensureUpdatedAnnotations(ITextEditor editor) {
    // Object inputElement = editor.getEditorInput().getAdapter(
    // IModelElement.class);

  }

  private static int processAnnotation(Annotation annot, Position pos, int invocationLocation,
          int bestOffset) {
    int posBegin = pos.offset;
    int posEnd = posBegin + pos.length;
    if (isInside(invocationLocation, posBegin, posEnd)) { // covers
      // invocation
      // location?
      return invocationLocation;
    } else if (bestOffset != invocationLocation) {
      int newClosestPosition = computeBestOffset(posBegin, invocationLocation, bestOffset);
      if (newClosestPosition != -1) {
        if (newClosestPosition != bestOffset) { // new best
          // if (JavaCorrectionProcessor.hasCorrections(annot)) { //
          // only
          // // jump
          // // to
          // // it
          // // if
          // // there
          // // are
          // // proposals
          // return newClosestPosition;
          // }
        }
      }
    }
    return bestOffset;
  }

  private static boolean isInside(int offset, int start, int end) {
    return offset == start || offset == end || (offset > start && offset < end); // make sure to
    // handle
    // 0-length ranges
  }

  /**
   * Computes and returns the invocation offset given a new position, the initial offset and the
   * best invocation offset found so far.
   * <p>
   * The closest offset to the left of the initial offset is the best. If there is no offset on the
   * left, the closest on the right is the best.
   * </p>
   * 
   * @param newOffset
   *          the offset to llok at
   * @param invocationLocation
   *          the invocation location
   * @param bestOffset
   *          the current best offset
   * @return -1 is returned if the given offset is not closer or the new best offset
   */
  private static int computeBestOffset(int newOffset, int invocationLocation, int bestOffset) {
    if (newOffset <= invocationLocation) {
      if (bestOffset > invocationLocation) {
        return newOffset; // closest was on the right, prefer on the
        // left
      } else if (bestOffset <= newOffset) {
        return newOffset; // we are closer or equal
      }
      return -1; // further away
    }

    if (newOffset <= bestOffset)
      return newOffset; // we are closer or equal

    return -1; // further away
  }

  /*
   * @seeorg.eclipse.jface.text.contentassist.ContentAssistant# possibleCompletionsClosed()
   */
  @Override
  protected void possibleCompletionsClosed() {
    super.possibleCompletionsClosed();
    restorePosition();
  }

  private void storePosition(int currOffset, int currLength) {
    fPosition = new Position(currOffset, currLength);
  }

  private void restorePosition() {
    if (fPosition != null && !fPosition.isDeleted() && fViewer.getDocument() != null) {
      fViewer.setSelectedRange(fPosition.offset, fPosition.length);
      fViewer.revealRange(fPosition.offset, fPosition.length);
    }
    fPosition = null;
  }

  /**
   * Returns true if the last invoked completion was called with an updated offset.
   * 
   * @return <code> true</code> if the last invoked completion was called with an updated offset.
   */
  public boolean isUpdatedOffset() {
    return fPosition != null;
  }

  /**
   * Returns the annotations at the current offset
   * 
   * @return the annotations at the offset
   */
  public Annotation[] getAnnotationsAtOffset() {
    return fCurrentAnnotations;
  }
}
