/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ant.internal.ui.editor.text;

import java.util.Iterator;
import java.util.StringTokenizer;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.AbstractFileSet;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.PatternSet;
import org.eclipse.ant.internal.ui.AntUIPlugin;
import org.eclipse.ant.internal.ui.debug.model.AntProperty;
import org.eclipse.ant.internal.ui.debug.model.AntStackFrame;
import org.eclipse.ant.internal.ui.debug.model.AntValue;
import org.eclipse.ant.internal.ui.editor.AntEditor;
import org.eclipse.ant.internal.ui.editor.derived.HTMLPrinter;
import org.eclipse.ant.internal.ui.editor.derived.HTMLTextPresenter;
import org.eclipse.ant.internal.ui.model.AntElementNode;
import org.eclipse.ant.internal.ui.model.AntModel;
import org.eclipse.ant.internal.ui.model.AntPropertyNode;
import org.eclipse.ant.internal.ui.model.IAntModel;
import org.eclipse.ant.internal.ui.preferences.AntEditorPreferenceConstants;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.debug.ui.DebugUITools;
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.ITextHover;
import org.eclipse.jface.text.ITextHoverExtension;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
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.SWT;
import org.eclipse.swt.widgets.Shell;


public class XMLTextHover implements ITextHover, ITextHoverExtension {

	private AntEditor fEditor;
	
	public XMLTextHover(AntEditor editor) {
		super();
		fEditor = editor;
	}
	
	/*
	 * Formats a message as HTML text.
	 */
	private String formatMessage(String message) {
		StringBuffer buffer= new StringBuffer();
		HTMLPrinter.addPageProlog(buffer);
		HTMLPrinter.addParagraph(buffer, HTMLPrinter.convertToHTMLContent(message));
		HTMLPrinter.addPageEpilog(buffer);
		return buffer.toString();
	}
	
	/*
	 * Formats a message as HTML text.
	 */
	private String formatPathMessage(String[] list) {
		StringBuffer buffer= new StringBuffer();
		HTMLPrinter.addPageProlog(buffer);
		HTMLPrinter.addSmallHeader(buffer, AntEditorTextMessages.XMLTextHover_4);
		HTMLPrinter.startBulletList(buffer);
		for (int i = 0; i < list.length; i++) {
			HTMLPrinter.addBullet(buffer, list[i]);
		}
		HTMLPrinter.endBulletList(buffer);
		HTMLPrinter.addPageEpilog(buffer);
		return buffer.toString();
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.jface.text.ITextHover#getHoverInfo(org.eclipse.jface.text.ITextViewer, org.eclipse.jface.text.IRegion)
	 */
	public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) {
		
		if (!(textViewer instanceof ISourceViewer)) {
			return null;
		}
		
		ISourceViewer sourceViewer= (ISourceViewer) textViewer;
		IAnnotationModel model= sourceViewer.getAnnotationModel();
		
		if (model != null) {
			String message= getAnnotationModelHoverMessage(model, hoverRegion);
			if (message != null) {
				return message;
			}
		}
        
		AntModel antModel= fEditor.getAntModel();
		if (antModel == null) { //the ant model has not been created yet
			return null;
		}
		
		return getAntModelHoverMessage(antModel, hoverRegion, textViewer);
		
	}
	
	private String getAntModelHoverMessage(AntModel antModel, IRegion hoverRegion, ITextViewer textViewer){
		try {
			IDocument document= textViewer.getDocument();
			int offset= hoverRegion.getOffset();
			int length= hoverRegion.getLength();
			String text= document.get(offset, length);
			String value;
			AntElementNode node= antModel.getNode(offset, false);
			if (document.get(offset - 2, 2).equals("${") || node instanceof AntPropertyNode) { //$NON-NLS-1$
                AntStackFrame frame= getFrame();
                if (frame != null) {//active Ant debug session
                    AntProperty property= frame.findProperty(text);
                    if (property != null) {
                        return ((AntValue)property.getValue()).getValueString();
                    }
                }
				value= antModel.getPropertyValue(text);
				if (value != null) {
					return formatMessage(value);
				}
			}
			value= antModel.getTargetDescription(text);
			if (value != null) {
				return formatMessage(value);
			}
			Object referencedObject= antModel.getReferenceObject(text);
			if (referencedObject != null) {
				if (referencedObject instanceof Path) {
					return formatPathMessage(((Path)referencedObject).list());
				} else if (referencedObject instanceof PatternSet) {
					return formatPatternSetMessage((PatternSet) referencedObject);
				} else if (referencedObject instanceof AbstractFileSet) {
					return formatFileSetMessage((AbstractFileSet)referencedObject);
				}
			}
		} catch (BadLocationException e) {
		} catch (BuildException be) {
			return be.getMessage();
		}
		
		return null;
	}
	
	private String getAnnotationModelHoverMessage(IAnnotationModel model, IRegion hoverRegion) {
		Iterator e= model.getAnnotationIterator();
		while (e.hasNext()) {
			Annotation a= (Annotation) e.next();
			if (a instanceof XMLProblemAnnotation) {
				Position p= model.getPosition(a);
				if (p.overlapsWith(hoverRegion.getOffset(), hoverRegion.getLength())) {
					String msg= a.getText();
					if (msg != null && msg.trim().length() > 0) {
						return formatMessage(msg);
					}
				}
			}
		}
		return null;
	}
	
	private String formatFileSetMessage(AbstractFileSet set) {
		FileScanner fileScanner= new FileScanner();
		IAntModel antModel= fEditor.getAntModel();
		Project project= antModel.getProjectNode().getProject();
		set.setupDirectoryScanner(fileScanner, project);
		String[] excludedPatterns= fileScanner.getExcludesPatterns();
		String[] includesPatterns= fileScanner.getIncludePatterns();
		return formatSetMessage(includesPatterns, excludedPatterns);
	}

	private String formatPatternSetMessage(PatternSet set) {
		IAntModel antModel= fEditor.getAntModel();
		Project project= antModel.getProjectNode().getProject();
		String[] includes= set.getIncludePatterns(project);
		String[] excludes= set.getExcludePatterns(project);
		return formatSetMessage(includes, excludes);
	}

	private String formatSetMessage(String[] includes, String[] excludes) {
		StringBuffer buffer= new StringBuffer();
		HTMLPrinter.addPageProlog(buffer);
		if (includes != null && includes.length > 0) {
			HTMLPrinter.addSmallHeader(buffer, AntEditorTextMessages.XMLTextHover_5);
			for (int i = 0; i < includes.length; i++) {
				HTMLPrinter.addBullet(buffer, includes[i]);
			}
		}
		HTMLPrinter.addParagraph(buffer, ""); //$NON-NLS-1$
		HTMLPrinter.addParagraph(buffer, ""); //$NON-NLS-1$
		if (excludes != null && excludes.length > 0) {
			HTMLPrinter.addSmallHeader(buffer, AntEditorTextMessages.XMLTextHover_6);
			for (int i = 0; i < excludes.length; i++) {
				HTMLPrinter.addBullet(buffer, excludes[i]);
			}
		}
		HTMLPrinter.addPageEpilog(buffer);
		return buffer.toString();
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.text.ITextHover#getHoverRegion(org.eclipse.jface.text.ITextViewer, int)
	 */
	public IRegion getHoverRegion(ITextViewer textViewer, int offset) {
		if (textViewer != null) {
			return getRegion(textViewer, offset);
		}
		return null;	
	}

	public static IRegion getRegion(ITextViewer textViewer, int offset) {
		IDocument document= textViewer.getDocument();
		
		int start= -1;
		int end= -1;
	    IRegion region= null;
		try {	
			int pos= offset;
			char c;
            
            if (document.getChar(pos) == '"') {
                pos--;
            }
			while (pos >= 0) {
				c= document.getChar(pos);
				if (c != '.' && c != '-' && c != '/' &&  c != '\\' && c != ' ' && c != ')' && c != '('&& c != ':' && !Character.isJavaIdentifierPart(c) && pos != offset)
					break;
				--pos;
			}
			
			start= pos;
			
			pos= offset;
			int length= document.getLength();
			
			while (pos < length) {
				c= document.getChar(pos);
				if (c != '.' && c != '-' && c != '/' &&  c != '\\' && c != ' ' && c != ')' && c != '('&& c != ':' && !Character.isJavaIdentifierPart(c))
					break;
                if (c == '/' && (document.getLength() - 1) > (pos + 1) && document.getChar(pos + 1) == '>') {
                   //e.g. <name/>
                    break;
                }
				++pos;
			}
			
			end= pos;
			
		} catch (BadLocationException x) {
		}
		
		if (start > -1 && end > -1) {
			if (start == offset && end == offset) {
				return new Region(offset, 0);
			} else if (start == offset) {
				return new Region(start, end - start);
			} else {
                try { //correct for spaces at beginning or end
                    while(document.getChar(start + 1) == ' ') {
                        start++;
                    }
                    while(document.getChar(end - 1) == ' ') {
                        end--;
                    }
                } catch (BadLocationException e) {
                }
                region= new Region(start + 1, end - start - 1);
            }
        }
        
        if (region != null) {
            try {
                char c= document.getChar(region.getOffset() - 1);
				if (c == '"') {
					if (document.get(offset, region.getLength()).indexOf(',') != -1) {
						region = cleanRegionForNonProperty(offset, document, region);
					}
				} else if (c != '{') {
                	region = cleanRegionForNonProperty(offset, document, region);
                }
            } catch (BadLocationException e) {
            }
        }
            
		return region;
	}

	private static IRegion cleanRegionForNonProperty(int offset, IDocument document, IRegion region) throws BadLocationException {
		//do not allow spaces in region that is not a property
		String text= document.get(region.getOffset(), region.getLength());
		if (text.startsWith("/")) { //$NON-NLS-1$
			text= text.substring(1);
			region= new Region(region.getOffset() + 1, region.getLength() - 1);
		}
		StringTokenizer tokenizer= new StringTokenizer(text, " "); //$NON-NLS-1$
		if (tokenizer.countTokens() != 1) {
		    while(tokenizer.hasMoreTokens()) {
		        String token= tokenizer.nextToken();
		        int index= text.indexOf(token);
		        if (region.getOffset() + index <= offset && region.getOffset() + index + token.length() >= offset) {
		            region= new Region(region.getOffset() + index, token.length());
		            break;
		        }
		    }
		}
		
		return region;
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.jface.text.ITextHoverExtension#getHoverControlCreator()
	 */
	public IInformationControlCreator getHoverControlCreator() {
		if (AntUIPlugin.getDefault().getPreferenceStore().getBoolean(AntEditorPreferenceConstants.EDITOR_SHOW_TEXT_HOVER_AFFORDANCE)) {
			return new IInformationControlCreator() {
				public IInformationControl createInformationControl(Shell parent) {
	  				return new DefaultInformationControl(parent, SWT.NONE, 
	  					new HTMLTextPresenter(true),
				   		AntEditorTextMessages.XMLTextHover_7);
			 	}
  			};
		}
		return null;
	}
    
    /**
     * Returns the stack frame in which to search for properties, or <code>null</code>
     * if none.
     * 
     * @return the stack frame in which to search for properties, or <code>null</code>
     * if none
     */
    private AntStackFrame getFrame() {
        IAdaptable adaptable = DebugUITools.getDebugContext();
        if (adaptable != null) {
            return (AntStackFrame)adaptable.getAdapter(AntStackFrame.class); 
        }
        return null;
    }
}
