/*******************************************************************************
 * Copyright (c) 2000, 2004 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.cfeclipse.cfml.editors.decoration;


import org.cfeclipse.cfml.CFMLPlugin;
import org.cfeclipse.cfml.preferences.EditorPreferenceConstants;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IPaintPositionManager;
import org.eclipse.jface.text.IPainter;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewerExtension5;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.source.ICharacterPairMatcher;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.ui.internal.editors.text.EditorsPlugin;

/**
 * Highlights the peer character matching the character near the caret position.
 * This painter can be configured with an
 * {@link org.eclipse.jface.text.source.ICharacterPairMatcher}.
 * <p>
 * Clients instantiate and configure object of this class.
 * 
 * @since 2.1
 */
public final class MatchingCharacterPainter implements IPainter, PaintListener {
	
	/** Indicates whether this painter is active */
	private boolean fIsActive= false;
	/** The source viewer this painter is associated with */
	private ISourceViewer fSourceViewer;
	/** The viewer's widget */
	private StyledText fTextWidget;
	/** The color in which to highlight the peer character */
	private Color fColor;
	/** The paint position manager */
	private IPaintPositionManager fPaintPositionManager;
	/** The strategy for finding matching characters */
	private ICharacterPairMatcher fMatcher;
	/** The position tracking the matching characters */
	private Position fPairPosition= new Position(0, 0);
	/** The anchor indicating whether the character is left or right of the caret */
	private int fAnchor;

	
	/**
	 * Creates a new MatchingCharacterPainter for the given source viewer using
	 * the given character pair matcher. The character matcher is not adopted by
	 * this painter. Thus,  it is not disposed. However, this painter requires
	 * exclusive access to the given pair matcher.
	 * 
	 * @param sourceViewer
	 * @param matcher
	 */
	public MatchingCharacterPainter(ISourceViewer sourceViewer, ICharacterPairMatcher matcher) {
		fSourceViewer= sourceViewer;
		fMatcher= matcher;
		fTextWidget= sourceViewer.getTextWidget();
	}
	
	/**
	 * Sets the color in which to highlight the match character.
	 * 
	 * @param color the color
	 */
	public void setColor(Color color) {
		fColor= color;
	}
				
	/*
	 * @see org.eclipse.jface.text.IPainter#dispose()
	 */
	public void dispose() {
		if (fMatcher != null) {
			fMatcher.clear();
			fMatcher= null;
		}
		
		fColor= null;
		fTextWidget= null;
	}
				
	/*
	 * @see org.eclipse.jface.text.IPainter#deactivate(boolean)
	 */
	public void deactivate(boolean redraw) {
		if (fIsActive) {
			fIsActive= false;
			fTextWidget.removePaintListener(this);
			if (fPaintPositionManager != null)
				fPaintPositionManager.unmanagePosition(fPairPosition);
			if (redraw)
				handleDrawRequest(null);
		}
	}
		
	/*
	 * @see org.eclipse.swt.events.PaintListener#paintControl(org.eclipse.swt.events.PaintEvent)
	 */
	public void paintControl(PaintEvent event) {
		if (fTextWidget != null)
			handleDrawRequest(event.gc);
	}
	
	/**
	 * Handles a redraw request.
	 * 
	 * @param gc the GC to draw into.
	 */
	private void handleDrawRequest(GC gc) {
		
		if (fPairPosition.isDeleted)
			return;
			
		int offset= fPairPosition.getOffset();
		int length= fPairPosition.getLength();
		if (length < 1)
			return;
		
		if (fSourceViewer instanceof ITextViewerExtension5) {
			ITextViewerExtension5 extension= (ITextViewerExtension5) fSourceViewer;
			IRegion widgetRange= extension.modelRange2WidgetRange(new Region(offset, length));
			
			if (widgetRange == null)
				return;
			
			try {
				// don't draw if the pair position is really hidden and widgetRange just
				// marks the coverage around it.
				IDocument doc= fSourceViewer.getDocument();
				int startLine= doc.getLineOfOffset(offset);
				int endLine= doc.getLineOfOffset(offset + length);
				if (extension.modelLine2WidgetLine(startLine) == -1 || extension.modelLine2WidgetLine(endLine) == -1)
					return;
			} catch (BadLocationException e) {
				return;
			}
				
			offset= widgetRange.getOffset();
			length= widgetRange.getLength();
			
		} else {
			IRegion region= fSourceViewer.getVisibleRegion();
			if (region.getOffset() > offset || region.getOffset() + region.getLength() < offset + length)
				return;
			offset -= region.getOffset();
		}
			
		int endOffset = offset + length-1;
		draw(gc, offset, 1);
		draw(gc, endOffset, 1);
		
	}
	
	/**
	 * Highlights the given widget region.
	 * 
	 * @param gc the GC to draw into
	 * @param offset the offset of the widget region
	 * @param length the length of the widget region
	 */
	private void draw(GC gc, int offset, int length) {
		if (gc != null) {
			Point left= fTextWidget.getLocationAtOffset(offset);
			Point right= fTextWidget.getLocationAtOffset(offset + length);
			
			IPreferenceStore store = CFMLPlugin.getDefault().getPreferenceStore();
			int style = store.getInt(EditorPreferenceConstants.P_BRACKET_MATCHING_STYLE);
			
			switch (style) {
				case EditorPreferenceConstants.BRACKET_MATCHING_OUTLINE: 
				{
					//draw box around character
					gc.setForeground(fColor);
					gc.drawRectangle(left.x, left.y, right.x - left.x - 1, fTextWidget.getLineHeight() - 1);
					break;
				}
				case EditorPreferenceConstants.BRACKET_MATCHING_BACKGROUND: 
				{
					//Paint a background on the character
					gc.setBackground(fColor);
					gc.drawText(fTextWidget.getText(offset,offset),left.x,left.y+1);
					break;
				}
				case EditorPreferenceConstants.BRACKET_MATCHING_BOLD: 
				{
				    int caret= fTextWidget.getCaretOffset();
				    int lineIndex = fTextWidget.getLineAtOffset(caret);
				    int lineStart = fTextWidget.getOffsetAtLine(lineIndex);
				    int lineEnd = -1;
				    if (lineIndex == fTextWidget.getLineCount()) {
				        lineEnd = fTextWidget.getText().length()-1;
				    } else {
				        lineEnd = fTextWidget.getText().indexOf(fTextWidget.getLineDelimiter(),lineStart);
				    }
				    if (offset >= lineStart && offset <= lineEnd) {
				        RGB rgb= PreferenceConverter.getColor(store, EditorPreferenceConstants.P_CURRENT_LINE_COLOR);
				        Color c = EditorsPlugin.getDefault().getSharedTextColors().getColor(rgb);
				        gc.setBackground(c);    
				    } else {
				        gc.setBackground(fTextWidget.getBackground());
				    }
				    
					gc.setForeground(fColor);
					Font oldFont = gc.getFont();
					FontData[] data = gc.getFont().getFontData();
					data[0].setStyle(SWT.BOLD);
					
					Font font = new Font(fTextWidget.getDisplay(),data);
					gc.setFont(font);
					gc.drawText(fTextWidget.getText(offset,offset),left.x,left.y+1);
					gc.setFont(oldFont);
					font.dispose();
					break;
				}
			}
			
			
			
							
		} else {
			fTextWidget.redrawRange(offset, length, true);
		}
	}
	
	/*
	 * @see org.eclipse.jface.text.IPainter#paint(int)
	 */
	public void paint(int reason) {

		IDocument document= fSourceViewer.getDocument();
		if (document == null) {
			deactivate(false);
			return;
		}

		Point selection= fSourceViewer.getSelectedRange();
		if (selection.y > 0) {
			deactivate(true);
			return;
		}
		
		IRegion pair= fMatcher.match(document, selection.x);
		if (pair == null) {
			deactivate(true);
			return;
		}
		
		if (fIsActive) {
			
			if (IPainter.CONFIGURATION == reason) {
				
				// redraw current highlighting
				handleDrawRequest(null);
			
			} else if (pair.getOffset() != fPairPosition.getOffset() || 
					pair.getLength() != fPairPosition.getLength() || 
					fMatcher.getAnchor() != fAnchor) {
				
				// otherwise only do something if position is different
				
				// remove old highlighting
				handleDrawRequest(null);
				// update position
				fPairPosition.isDeleted= false;
				fPairPosition.offset= pair.getOffset();
				fPairPosition.length= pair.getLength();
				fAnchor= fMatcher.getAnchor();
				// apply new highlighting
				handleDrawRequest(null);
			
			}
		} else {
			
			fIsActive= true;
			
			fPairPosition.isDeleted= false;
			fPairPosition.offset= pair.getOffset();
			fPairPosition.length= pair.getLength();
			fAnchor= fMatcher.getAnchor();
			
			fTextWidget.addPaintListener(this);
			fPaintPositionManager.managePosition(fPairPosition);
			handleDrawRequest(null);
		}
	}
	
	/*
	 * @see org.eclipse.jface.text.IPainter#setPositionManager(org.eclipse.jface.text.IPaintPositionManager)
	 */
	public void setPositionManager(IPaintPositionManager manager) {
		fPaintPositionManager= manager;
	}
}
