package com.intellij.openapi.editor.impl;

import com.intellij.Patches;
import com.intellij.codeInsight.hint.HintManager;
import com.intellij.codeInsight.hint.TooltipController;
import com.intellij.codeInsight.hint.TooltipGroup;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.DataConstants;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.util.concurrency.ThreadingAssertions;
import com.intellij.util.concurrency.ThreadingAssertions;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.actionSystem.EditorAction;
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
import com.intellij.openapi.editor.actionSystem.EditorActionManager;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.editor.ex.util.EmptyEditorHighlighter;
import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
import com.intellij.openapi.editor.impl.event.MarkupModelListener;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.JScrollPane2;
import com.intellij.util.Alarm;
import com.intellij.util.IJSwingUtilities;
import gnu.trove.THashMap;
import gnu.trove.TIntArrayList;
import org.jdom.Element;

import javax.swing.plaf.ScrollBarUI;
import javax.swing.plaf.basic.BasicScrollBarUI;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.font.TextHitInfo;
import java.awt.im.InputMethodRequests;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.text.CharacterIterator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.TooManyListenersException;

public class EditorImpl implements EditorEx {
  private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
  private static final Key DND_COMMAND_KEY = Key.create("DndCommand");
  private final DocumentImpl myDocument;

  private JPanel myPanel;
  private JScrollPane myScrollPane;
  private EditorComponentImpl myEditorComponent;
  private EditorGutterComponentImpl myGutterComponent;


  private CommandProcessor myCommandProcessor;
  private MyScrollBar myVerticalScrollBar;
  private MyScrollBar myHorizontalScrollBar;

  private ArrayList<EditorMouseListener> myMouseListeners = new ArrayList<EditorMouseListener>();
  private ArrayList<EditorMouseMotionListener> myMouseMotionListeners = new ArrayList<EditorMouseMotionListener>();

  private int myCharHeight = -1;
  private int myLineHeight = -1;
  private int myDescent = -1;

  private boolean myIsInsertMode = true;

  private final CaretCursor myCaretCursor;
  private final ScrollingTimer myScrollingTimer = new ScrollingTimer();

  private final Object MOUSE_DRAGGED_GROUP = Key.create("MouseDraggedGroup");
  private final THashMap<Object, Object> myUserDataMap = new THashMap<Object, Object>();

  private final DocumentListener myEditorDocumentAdapter;

  private SettingsImpl mySettings;

  private boolean isReleased = false;

  private MouseEvent myMousePressedEvent = null;

  private int mySavedSelectionStart = -1;
  private int mySavedSelectionEnd = -1;
  private int myLastColumnNumber = 0;

  private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
  private MyEditable myEditable;

  private EditorColorsScheme myScheme;
  private final boolean myIsViewer;
  private final SelectionModelImpl mySelectionModel;
  private final EditorMarkupModelImpl myMarkupModel;
  private final FoldingModelImpl myFoldingModel;
  private final ScrollingModelImpl myScrollingModel;
  private final CaretModelImpl myCaretModel;

  private static final RepaintCursorThread ourCaretThread;

//  private final BorderEffect myBorderEffect = new BorderEffect();

  private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
  private FoldRegion myMouseSelectedRegion = null;

  private static final int MOUSE_SELECTION_STATE_NONE = 0;
  private static final int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
  private static final int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;

  private final MarkupModelListener myMarkupModelListener;

  private EditorHighlighter myHighlighter;

  private int myScrollbarOrientation;
  private boolean myMousePressedInsideSelection;
  private FontMetrics myPlainFontMetrics;
  private FontMetrics myBoldFontMetrics;
  private FontMetrics myItalicFontMetrics;
  private FontMetrics myBoldItalicFontMetrics;

  private static final int CACHED_CHARS_BUFFER_SIZE = 300;

  private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
  private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
  private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
  private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);

  private int myCurrentFontType = Font.PLAIN;

  private boolean myIsBlockSelectionMode;
  private EditorSizeContainer mySizeContainer = new EditorSizeContainer();

  private Runnable myCursorUpdater;
  private int myCaretUpdateVShift;
  Project myProject;
  private long myMouseSelectionChangeTimestamp;
  private int mySavedCaretOffsetForDNDUndoHack;
  private ArrayList<FocusChangeListener> myFocusListeners = new ArrayList<FocusChangeListener>();

  private MyInputMethodHandler myInputMethodRequestsHandler;
  private InputMethodRequests myInputMethodRequestsSwingWrapper;
  private boolean myIsOneLineMode;
  private boolean myIsRendererMode;
  private VirtualFile myVirtualFile;
  private boolean myIsColumnMode = false;
  private Color myForcedBackground = null;
  private Dimension myPreferredSize;
  private Runnable myGutterSizeUpdater = null;
  private Alarm myAppleRepaintAlarm;

  static {
    ourCaretThread = new RepaintCursorThread();
    ourCaretThread.start();
  }


  public EditorImpl(Document document, boolean viewer, Project project) {
    myProject = project;
    myDocument = (DocumentImpl)document;
    myScheme = new MyColorSchemeDelegate();
    myIsViewer = viewer;
    mySettings = new SettingsImpl(this);

    mySelectionModel = new SelectionModelImpl(this);
    myMarkupModel = new EditorMarkupModelImpl(this);
    myFoldingModel = new FoldingModelImpl(this);
    myCaretModel = new CaretModelImpl(this);
    myIsBlockSelectionMode = false;
    mySizeContainer.reset();

    myCommandProcessor = CommandProcessor.getInstance();

    myEditorDocumentAdapter = new EditorDocumentAdapter();

    myMarkupModelListener = new MarkupModelListener() {
      public void rangeHighlighterChanged(MarkupModelEvent event) {
        RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl)event.getHighlighter();
        if (rangeHighlighter.isValid()) {
          int start = rangeHighlighter.getAffectedAreaStartOffset();
          int end = rangeHighlighter.getAffectedAreaEndOffset();
          int startLine = myDocument.getLineNumber(start);
          int endLine = myDocument.getLineNumber(end);
          repaintLines(Math.max(0, startLine - 1), Math.min(endLine + 1, getDocument().getLineCount()));
        }
        else {
          repaint(0, getDocument().getTextLength());
        }
        ((EditorMarkupModelImpl)getMarkupModel()).repaint();
        ((EditorMarkupModelImpl)getMarkupModel()).markDirtied();
        GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
        if (renderer != null) {
          updateGutterSize();
        }
        updateCaretCursor();
      }
    };

    ((MarkupModelImpl)myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
    ((MarkupModelImpl)getMarkupModel()).addMarkupModelListener(myMarkupModelListener);

    myDocument.addDocumentListener(myFoldingModel);
    myDocument.addDocumentListener(myCaretModel);
    myDocument.addDocumentListener(mySelectionModel);
    myDocument.addDocumentListener(myEditorDocumentAdapter);

    myCaretCursor = new CaretCursor();

    myFoldingModel.flushCaretShift();
    myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;

    EditorHighlighter highlighter = new EmptyEditorHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
    setHighlighter(highlighter);

    initComponent();

    myScrollingModel = new ScrollingModelImpl(this);

    myGutterComponent.updateSize();
    myEditorComponent.setSize(getContentSize());

    Dimension preferredSize = getPreferredSize();
    myScrollPane.getViewport().setViewSize(preferredSize);

    if (Patches.APPLE_BUG_ID_3716835) {
      myScrollingModel.addVisibleAreaListener(new VisibleAreaListener() {
        public void visibleAreaChanged(VisibleAreaEvent e) {
          if (myAppleRepaintAlarm == null) {
            myAppleRepaintAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
          }
          myAppleRepaintAlarm.cancelAllRequests();
          myAppleRepaintAlarm.addRequest(new Runnable() {
            public void run() {
              repaint(0, myDocument.getTextLength());
            }
          }, 50, ModalityState.stateForComponent(myEditorComponent));
        }
      });
    }

    updateCaretCursor();
  }

  public boolean isViewer() {
    return myIsViewer || myIsRendererMode;
  }

  public boolean isRendererMode() {
    return myIsRendererMode;
  }

  public void setRendererMode(boolean isRendererMode) {
    myIsRendererMode = isRendererMode;
  }

  public void setFile(VirtualFile vFile) {
    myVirtualFile = vFile;
    reinitSettings();
  }

  public VirtualFile getVirtualFile() {
    return myVirtualFile;
  }

  public SelectionModel getSelectionModel() {
    return mySelectionModel;
  }

  public MarkupModel getMarkupModel() {
    return myMarkupModel;
  }

  public FoldingModel getFoldingModel() {
    return myFoldingModel;
  }

  public CaretModel getCaretModel() {
    return myCaretModel;
  }

  public ScrollingModel getScrollingModel() {
    return myScrollingModel;
  }

  public EditorSettings getSettings() {
    ThreadingAssertions.assertEventDispatchThread();
    return mySettings;
  }

  public void reinitSettings() {
    ThreadingAssertions.assertEventDispatchThread();
    myCharHeight = -1;
    myLineHeight = -1;
    myDescent = -1;
    myPlainFontMetrics = null;

    myPlainCache.reinitSettings();
    myBoldCache.reinitSettings();
    myItalicCache.reinitSettings();
    myBoldItalicCache.reinitSettings();

    myCaretModel.reinitSettings();
    mySelectionModel.reinitSettings();
    mySettings.reinitSettings();
    ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
    ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
    mySizeContainer.reset();
    myFoldingModel.refreshSettings();
    myFoldingModel.rebuild();

    myHighlighter.setColorScheme(myScheme);

    myGutterComponent.revalidate();
    myEditorComponent.repaint();

    updateCaretCursor();
  }

  public void release() {
    isReleased = true;
    myDocument.removeDocumentListener(myHighlighter);
    myDocument.removeDocumentListener(myEditorDocumentAdapter);
    myDocument.removeDocumentListener(myFoldingModel);
    myDocument.removeDocumentListener(myCaretModel);
    myDocument.removeDocumentListener(mySelectionModel);
    MarkupModelImpl markupModel = (MarkupModelImpl)myDocument.getMarkupModel(myProject, false);
    if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
    myMarkupModel.dispose();
    myLineHeight = -1;
    myCharHeight = -1;
    myDescent = -1;
    myPlainFontMetrics = null;
    myScrollingModel.dispose();
    myGutterComponent.dispose();
  }

  public <T> void putUserData(Key<T> key, T value) {
    if (value != null) {
      myUserDataMap.put(key, value);
    }
    else {
      myUserDataMap.remove(key);
    }
  }

  public <T> T getUserData(Key<T> key) {
    return (T)myUserDataMap.get(key);
  }

  private void initComponent() {
    myEditorComponent = new EditorComponentImpl(this);
//    myStatusBar = new EditorStatusBarImpl();

    myScrollPane = new JScrollPane2() {
      protected void processMouseWheelEvent(MouseWheelEvent e) {
        if (mySettings.isWheelFontChangeEnabled()) {
          boolean changeFontSize = SystemInfo.isMac
                                   ? !e.isControlDown() && e.isMetaDown() && !e.isAltDown() && !e.isShiftDown()
                                   : e.isControlDown() && !e.isMetaDown() && !e.isAltDown() && !e.isShiftDown();
          if (changeFontSize) {
            setFontSize(myScheme.getEditorFontSize() + e.getWheelRotation());
            return;
          }
        }

        super.processMouseWheelEvent(e);
      }
    };
    myPanel = new JPanel() {
      public void addNotify() {
        super.addNotify();
        if (((JComponent)getParent()).getBorder() != null) myScrollPane.setBorder(null);
      }
    };

    //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
    myPanel.setLayout(new BorderLayout());

    myVerticalScrollBar = new MyScrollBar(JScrollBar.VERTICAL);
    myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);

    myGutterComponent = new EditorGutterComponentImpl(this);
    myGutterComponent.setOpaque(true);

    myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
    myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
    myScrollPane.setViewportView(myEditorComponent);
    //myScrollPane.setBorder(null);
    myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
    myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);


    myScrollPane.setRowHeaderView(myGutterComponent);
    stopOptimizedScrolling();

    myEditorComponent.setTransferHandler(new MyTransferHandler());
    myEditorComponent.setAutoscrolls(true);

/*  Default mode till 1.4.0
 *   myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
 */
    myPanel.add(myScrollPane);

    myEditorComponent.addKeyListener(new KeyAdapter() {
      public void keyTyped(KeyEvent event) {
        if (Patches.APPLE_BUG_ID_3337563) return; // Everything is going through InputMethods under MacOS X in JDK releases earlier than 1.4.2_03-117.1
        if (event.isConsumed()) {
          return;
        }
        if (processKeyTyped(event)) {
          event.consume();
        }
      }
    });

    MyMouseAdapter mouseAdapter = new MyMouseAdapter();
    myEditorComponent.addMouseListener(mouseAdapter);
    myGutterComponent.addMouseListener(mouseAdapter);

    MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
    myEditorComponent.addMouseMotionListener(mouseMotionListener);
    myGutterComponent.addMouseMotionListener(mouseMotionListener);

    myEditorComponent.addFocusListener(new FocusAdapter() {
      public void focusGained(FocusEvent e) {
        myCaretCursor.activate();
        int caretLine = getCaretModel().getLogicalPosition().line;
        repaintLines(caretLine, caretLine);
        fireFocusGained();
      }

      public void focusLost(FocusEvent e) {
        synchronized (ourCaretThread) {
          if (ourCaretThread.myEditor == EditorImpl.this) {
            ourCaretThread.myEditor = null;
          }
        }
        int caretLine = getCaretModel().getLogicalPosition().line;
        repaintLines(caretLine, caretLine);
        fireFocusLost();
      }
    });

//    myBorderEffect.reset();

    try {
      myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
        public void drop(DropTargetDropEvent dtde) {
        }

        public void dragOver(DropTargetDragEvent dtde) {
          Point location = dtde.getLocation();

          moveCaretToScreenPos(location.x, location.y);
          getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
        }
      });
    }
    catch (TooManyListenersException e) {
    }
  }

  public void setFontSize(final int fontSize) {
    int oldFontSize = myScheme.getEditorFontSize();
    myScheme.setEditorFontSize(fontSize);
    myPropertyChangeSupport.firePropertyChange(EditorEx.PROP_FONT_SIZE, oldFontSize, fontSize);
  }

  private void processKeyTyped(char c) {
    // [vova] This is patch for Mac OS X. Under Mac "input methods"
    // is handled before our EventQueue consume upcoming KeyEvents.
    IdeEventQueue queue = IdeEventQueue.getInstance();
    if (queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()) {
      return;
    }
    ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
    DataContext dataContext = getDataContext();
    actionManager.fireBeforeEditorTyping(c, dataContext);
    EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
  }

  private void fireFocusLost() {
    FocusChangeListener[] listeners = getFocusListeners();
    for (int i = 0; i < listeners.length; i++) {
      FocusChangeListener listener = listeners[i];
      listener.focusLost(this);
    }
  }

  private FocusChangeListener[] getFocusListeners() {
    return myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
  }

  private void fireFocusGained() {
    FocusChangeListener[] listeners = getFocusListeners();
    for (int i = 0; i < listeners.length; i++) {
      FocusChangeListener listener = listeners[i];
      listener.focusGained(this);
    }
  }

  public void setHighlighter(EditorHighlighter highlighter) {
    ThreadingAssertions.assertEventDispatchThread();
    if (myHighlighter != null) {
      getDocument().removeDocumentListener(myHighlighter);
    }

    getDocument().addDocumentListener(highlighter);
    highlighter.setText(getDocument().getCharsSequence());
    myHighlighter = highlighter;
    myHighlighter.setEditor(this);
    if (myPanel != null) {
      reinitSettings();
    }
  }

  public EditorHighlighter getHighlighter() {
    ThreadingAssertions.assertEventDispatchThread();
    return myHighlighter;
  }

  public JComponent getContentComponent() {
    return myEditorComponent;
  }

  public EditorGutterComponentEx getGutterComponentEx() {
    return myGutterComponent;
  }

  public void addPropertyChangeListener(PropertyChangeListener listener) {
    myPropertyChangeSupport.addPropertyChangeListener(listener);
  }

  public void removePropertyChangeListener(PropertyChangeListener listener) {
    myPropertyChangeSupport.removePropertyChangeListener(listener);
  }

  public void setInsertMode(boolean mode) {
    ThreadingAssertions.assertEventDispatchThread();
    boolean oldValue = myIsInsertMode;
    myIsInsertMode = mode;
    myPropertyChangeSupport.firePropertyChange(EditorEx.PROP_INSERT_MODE, oldValue, mode);
    //Repaint the caret line by moving caret to the same place
    LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
    getCaretModel().moveToLogicalPosition(caretPosition);
  }

  public boolean isInsertMode() {
    return myIsInsertMode;
  }

  public void setColumnMode(boolean mode) {
    ThreadingAssertions.assertEventDispatchThread();
    boolean oldValue = myIsColumnMode;
    myIsColumnMode = mode;
    myPropertyChangeSupport.firePropertyChange(PROP_COLUMN_MODE, oldValue, mode);
  }

  public boolean isColumnMode() {
    return myIsColumnMode;
  }

  public void setBlockSelectionMode(boolean isBlockSelectionMode) {
    myIsBlockSelectionMode = isBlockSelectionMode;
  }

  public boolean isBlockSelectionMode() {
    return myIsBlockSelectionMode;
  }

  private int yPositionToVisibleLineNumber(int y) {
    return y / getLineHeight();
  }

  public int getSpaceWidth(int  fontType) {
    int width = charWidth(fontType, ' ');
    return width > 0 ? width : 1;
  }

  public VisualPosition xyToVisualPosition(Point p) {
    int line = yPositionToVisibleLineNumber(p.y);

    int x = 0;
    int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
    int textLength = myDocument.getTextLength();

    if (offset >= textLength) return new VisualPosition(line, 0);

    int column = 0;
    int prevX = 0;
    CharSequence text = myDocument.getCharsNoThreadCheck();
    char c = ' ';
    IterationState state = new IterationState(this, offset, false);

    int fontType = state.getMergedAttributes().getFontType();
    int spaceSize = getSpaceWidth(fontType);

    outer:
        while (true) {
          if (offset >= textLength) break;

          if (offset >= state.getEndOffset()) {
            state.advance();
            fontType = state.getMergedAttributes().getFontType();
          }

          FoldRegion region = state.getCurrentFold();
          if (region != null) {
            char[] placeholder = region.getPlaceholderText().toCharArray();
            for (int j = 0; j < placeholder.length; j++) {
              c = placeholder[j];
              x += charWidth(fontType, c);
              if (x >= p.x) break outer;
              column++;
            }
            offset = region.getEndOffset();
          }
          else {
            prevX = x;
            c = text.charAt(offset);
            if (c == '\n') {
              break;
            }
            if (c == '\t') {
              x = nextTabStop(x);
            }
            else {
              x += charWidth(fontType, c);
            }

            if (x >= p.x) break;

            if (c == '\t') {
              column += (x - prevX) / spaceSize;
            }
            else {
              column++;
            }

            offset++;
          }
        }

    int charWidth = charWidth(fontType, c);

    if (x >= p.x && c == '\t') {
      if (mySettings.isCaretInsideTabs()) {
        column += (p.x - prevX) / spaceSize;
        if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
      }
      else {
        if ((x - p.x) * 2 < x - prevX) {
          column += (x - prevX) / spaceSize;
        }
      }
    }
    else {
      if (x >= p.x) {
        if ((x - p.x) * 2 < charWidth) column++;
      }
      else {
        column += (p.x - x) / getSpaceWidth(fontType);
      }
    }

    return new VisualPosition(line, column);
  }

  public VisualPosition offsetToVisualPosition(int offset) {
    return logicalToVisualPosition(offsetToLogicalPosition(offset));
  }

  public LogicalPosition offsetToLogicalPosition(int offset) {
    int line = calcLogicalLineNumber(offset);
    int column = calcColumnNumber(offset, line);
    return new LogicalPosition(line, column);
  }

  public LogicalPosition xyToLogicalPosition(Point p) {
    final Point pp;
    if (p.x >= 0 && p.y >= 0) {
      pp = p;
    }
    else {
      pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
    }

    return visualToLogicalPosition(xyToVisualPosition(pp));
  }

  public Point logicalPositionToXY(LogicalPosition pos) {
    VisualPosition visible = logicalToVisualPosition(pos);
    int y = visibleLineNumberToYPosition(visible.line);

    int lineStartOffset;

    if (pos.line == 0) {
      lineStartOffset = 0;
    }
    else {
      if (pos.line >= myDocument.getLineCount()) {
        lineStartOffset = myDocument.getTextLength();
      }
      else {
        lineStartOffset = myDocument.getLineStartOffset(pos.line);
      }
    }

    int x = getTabbedTextWidth(lineStartOffset, visible);
    return new Point(x, y);
  }

  public Point visualPositionToXY(VisualPosition visible) {
    int y = visibleLineNumberToYPosition(visible.line);
    int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;

    int lineStartOffset;

    if (logLine == 0) {
      lineStartOffset = 0;
    }
    else {
      if (logLine >= myDocument.getLineCount()) {
        lineStartOffset = myDocument.getTextLength();
      }
      else {
        lineStartOffset = myDocument.getLineStartOffset(logLine);
      }
    }

    int x = getTabbedTextWidth(lineStartOffset, visible);
    return new Point(x, y);
  }

  private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
    if (pos.column == 0) return 0;

    int x = 0;
    int offset = lineStartOffset;
    CharSequence text = myDocument.getCharsNoThreadCheck();
    int textLength = myDocument.getTextLength();
    IterationState state = new IterationState(this, offset, false);
    int fontType = state.getMergedAttributes().getFontType();
    int spaceSize = getSpaceWidth(fontType);

    int column = 0;
    outer:
        while (column < pos.column) {
          if (offset >= textLength) break;

          if (offset >= state.getEndOffset()) {
            state.advance();
            fontType = state.getMergedAttributes().getFontType();
          }

          FoldRegion region = state.getCurrentFold();

          if (region != null) {
            char[] placeholder = region.getPlaceholderText().toCharArray();
            for (int j = 0; j < placeholder.length; j++) {
              x += charWidth(fontType, placeholder[j]);
              column++;
              if (column >= pos.column) break outer;
            }
            offset = region.getEndOffset();
          }
          else {
            char c = text.charAt(offset);
            if (c == '\n') {
              break;
            }
            if (c == '\t') {
              int prevX = x;
              x = nextTabStop(x);
              column += (x - prevX) / spaceSize;
            }
            else {
              x += charWidth(fontType, c);
              column++;
            }
            offset++;
          }
        }

    if (column != pos.column) {
      x += getSpaceWidth(fontType) * (pos.column - column);
    }

    return x;
  }

  public int nextTabStop(int x) {
    int tabSize = mySettings.getTabSize(myProject);
    if (tabSize <= 0) {
      tabSize = 1;
    }

    tabSize *= getSpaceWidth(Font.PLAIN);

    int nTabs = x / tabSize;
    return (nTabs + 1) * tabSize;
  }

  public int visibleLineNumberToYPosition(int lineNum) {
    if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
    return lineNum * getLineHeight();
  }

  public void repaint(int startOffset, int endOffset) {
    ThreadingAssertions.assertEventDispatchThread();
    if (myScrollPane == null) {
      return;
    }
    if (endOffset > myDocument.getTextLength()) {
      endOffset = myDocument.getTextLength();
    }
    if (startOffset < endOffset) {
      int startLine = myDocument.getLineNumber(startOffset);
      int endLine = myDocument.getLineNumber(endOffset);
      repaintLines(startLine, endLine);
    }
  }

  public void repaintLines(int startLine, int endLine) {
    Rectangle visibleRect = getScrollingModel().getVisibleArea();
    int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
    int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;

    myEditorComponent.repaintEditorComponent(visibleRect.x,
                                             yStartLine,
                                             visibleRect.x + visibleRect.width,
                                             yEndLine - yStartLine);
    myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
  }

  private void beforeChangedUpdate(DocumentEvent e) {
    Rectangle viewRect = getScrollingModel().getVisibleArea();
    Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
    myCaretUpdateVShift = pos.y - viewRect.y;
    mySizeContainer.beforeChange(e);
  }

  private void changedUpdate(DocumentEvent e) {
    if (myScrollPane == null) return;

    stopOptimizedScrolling();
    mySelectionModel.removeBlockSelection();

    mySizeContainer.changedUpdate(e);
    int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
    int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;

    if (myDocument.getTextLength() > 0) {
      int startDocLine = myDocument.getLineNumber(e.getOffset());
      int endDocLine = myDocument.getLineNumber(e.getOffset() + e.getNewLength());
      if (e.getOldLength() > e.getNewLength() || startDocLine != endDocLine) {
        updateGutterSize();
      }
    }

    updateCaretCursor();
    repaintLines(startVisualLine, endVisualLine);

    Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
    int scrollOffset = caretLocation.y - myCaretUpdateVShift;
    getScrollingModel().scrollVertically(scrollOffset);
  }

  private void updateGutterSize() {
    if (myGutterSizeUpdater != null) return;
    myGutterSizeUpdater = new Runnable() {
      public void run() {
        myGutterComponent.updateSize();
        myGutterSizeUpdater = null;
      }
    };
  }

  void validateSize() {
    if (myGutterSizeUpdater != null) {
      SwingUtilities.invokeLater(myGutterSizeUpdater);
    }

    Dimension dim = getPreferredSize();

    if (!dim.equals(myPreferredSize)) {
      myPreferredSize = dim;

      stopOptimizedScrolling();
      myScrollPane.getViewport().setViewSize(myPreferredSize);
      int lineNum = Math.max(1, getDocument().getLineCount());

      myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth(Integer.toString((lineNum + 2))) + 6);
      final JViewport rowHeader = myScrollPane.getRowHeader();
      if (rowHeader != null) {
        rowHeader.setViewSize(myGutterComponent.getPreferredSize());
      }

      getScrollPane().revalidate();

      myEditorComponent.repaint();
      myScrollPane.repaint();
      myMarkupModel.repaint();
    }
  }

  void recalcSizeAndRepaint() {
    mySizeContainer.reset();

    validateSize();

    Dimension size = getPreferredSize();

    myScrollPane.getVerticalScrollBar().setMaximum(size.height);
    myScrollPane.getHorizontalScrollBar().setMaximum(size.width);

    getScrollPane().revalidate();
    myMarkupModel.repaint();

    stopOptimizedScrolling();
    myEditorComponent.repaintEditorComponent();

    myGutterComponent.repaint();
  }

  public Document getDocument() {
    return myDocument;
  }

  public JComponent getComponent() {
    return myPanel;
  }

  public void addEditorMouseListener(EditorMouseListener listener) {
    myMouseListeners.add(listener);
  }

  public void removeEditorMouseListener(EditorMouseListener listener) {
    boolean success = myMouseListeners.remove(listener);
    LOG.assertTrue(success);
  }

  public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
    myMouseMotionListeners.add(listener);
  }

  public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
    boolean success = myMouseMotionListeners.remove(listener);
    LOG.assertTrue(success);
  }

  public boolean isDisposed() {
    return isReleased;
  }

  public void paint(Graphics g) {
    validateSize();
    startOptimizedScrolling();

    if (myCursorUpdater != null) {
      myCursorUpdater.run();
      myCursorUpdater = null;
    }

    Rectangle clip = getClipBounds(g);

    if (clip == null) {
      return;
    }

    Rectangle viewRect = getScrollingModel().getVisibleArea();
    if (viewRect == null) {
      return;
    }

    if (isReleased) {
      g.setColor(new Color(128, 255, 128));
      g.fillRect(clip.x, clip.y, clip.width, clip.height);

      return;
    }

    Color background = getBackroundColor();
    g.setColor(background);
    g.fillRect(clip.x, clip.y, clip.width, clip.height);

    paintBackgrounds(g, clip);
    paintRectangularSelection(g);
    paintRightMargin(g, clip);
    final MarkupModel docMarkup = myDocument.getMarkupModel(myProject);
    if (docMarkup != null) {
      paintLineMarkersSeparators(g, clip, docMarkup);
    }
    paintLineMarkersSeparators(g, clip, myMarkupModel);
    paintText(g, clip);
    paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
    BorderEffect borderEffect = new BorderEffect(this, g);
    borderEffect.paintHighlighters(getHighlighter());
    if (docMarkup != null) {
      borderEffect.paintHighlighters(docMarkup.getAllHighlighters());
    }
    borderEffect.paintHighlighters((getMarkupModel()).getAllHighlighters());
    paintCaretCursor(g);

    paintComposedTextDecoration((Graphics2D)g);
  }

  public void setBackgroundColor(Color color) {
    myForcedBackground = color;
  }

  public void resetBackgourndColor() {
