package tk.eclipse.plugin.jseditor.editors;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.hyperlink.IHyperlink;
import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.ide.IGotoMarker;

import tk.eclipse.plugin.htmleditor.HTMLPlugin;
import tk.eclipse.plugin.htmleditor.HTMLProjectParams;
import tk.eclipse.plugin.htmleditor.HTMLUtil;
import tk.eclipse.plugin.jseditor.launch.JavaScriptLibraryTable;

/**
 * <code>IHyperlinkDetector</code> for JavaScript.
 * <p>
 * This detector detects available functions and variables 
 * at the caret position.
 * 
 * @author Naoki Takezoe
 */
public class JavaScriptHyperlinkDetector implements IHyperlinkDetector {

	/** Functions which are defined in the library files */
	private List<FunctionInfo> functions = new ArrayList<FunctionInfo>();
	
	/**
	 * Returns hyperlinks or null.
	 */
	public IHyperlink[] detectHyperlinks(ITextViewer textViewer,
			IRegion region, boolean canShowMultipleHyperlinks) {
		
		String source = textViewer.getDocument().get();
		int offset = region.getOffset();
		
		// extracts the word at the caret position
		String word = String.valueOf(source.charAt(offset));
		int index = offset -1;
		while(index >= 0){
			char c = source.charAt(index);
			if(Character.isJavaIdentifierPart(c)){
				word = c + word;
				index--;
			} else {
				break;
			}
		}
		index = offset + 1;
		offset = offset - word.length() + 1;
		while(source.length()>index){
			char c = source.charAt(index);
			if(Character.isJavaIdentifierPart(c)){
				word = word + c;
				index++;
			} else if(c=='('){
				break;
			} else {
				return null;
			}
		}
		
		// itselfs
		JavaScriptModel model = new JavaScriptModel(source);
		JavaScriptContext context = model.getContextFromOffset(offset);
		if(context!=null){
			JavaScriptElement[] children = context.getVisibleElements();
			for(int i=0;i<children.length;i++){
				if(children[i] instanceof JavaScriptFunction){
					JavaScriptFunction function = (JavaScriptFunction)children[i];
					if(children[i].getName().equals(word)){
						IRegion hyperlinkRegion = new Region(offset, word.length());
						return new IHyperlink[]{
								new JavaScriptHyperlink(hyperlinkRegion, textViewer, 
										function.getOffset())
						};
					}
				}
			}
		}
		
		// libraries
		for(int i=0;i<functions.size();i++){
			FunctionInfo info = functions.get(i);
			if(info.function.getName().equals(word)){
				IRegion hyperlinkRegion = new Region(offset, word.length());
				return new IHyperlink[]{
						new JavaScriptHyperlink(hyperlinkRegion, info.resource, 
								info.function.getOffset())
				};
			}
		}
		
		return null;
	}
	
	/**
	 * Updates internal informations.
	 * 
	 * @param file the editing file
	 */
	public void update(IFile file){
		try {
			functions.clear();
			
			HTMLProjectParams params = new HTMLProjectParams(file.getProject());
			String[] javaScripts = params.getJavaScripts();
			IWorkspaceRoot wsroot = ResourcesPlugin.getWorkspace().getRoot();
			
			for(int i=0;i<javaScripts.length;i++){
				InputStream in = null;
				Object obj = null;
				if(javaScripts[i].startsWith(JavaScriptLibraryTable.PREFIX)){
					IResource resource = wsroot.findMember(javaScripts[i].substring(JavaScriptLibraryTable.PREFIX.length()));
					if(resource!=null && resource instanceof IFile && resource.exists()){
						in = ((IFile)resource).getContents();
						obj = resource;
					}
				} else {
					obj = new File(javaScripts[i]);
					in = new FileInputStream(javaScripts[i]);
				}
				String source = new String(HTMLUtil.readStream(in));
				JavaScriptModel model = new JavaScriptModel(source);
				JavaScriptElement[] elements = model.getChildren();
				
				for(int j=0;j<elements.length;j++){
					if(elements[j] instanceof JavaScriptFunction){
						FunctionInfo info = new FunctionInfo();
						info.resource = obj;
						info.function = (JavaScriptFunction)elements[j];
						functions.add(info);
					}
				}
			}
		} catch(Exception ex){
			HTMLPlugin.logException(ex);
		}
	}
	
	/**
	 * An internal object to stores <code>JavaScriptFunction</code> 
	 * and resource which defines them.
	 */
	private class FunctionInfo {
		private Object resource;
		private JavaScriptFunction function;
	}
	
	/**
	 * <code>IHyperlink</code> implementation to jump the target resource.
	 */
	private class JavaScriptHyperlink implements IHyperlink {
		
		private IRegion region;
		private Object resource;
		private int beginOffset;
		
		/**
		 * The constructor.
		 * 
		 * @param region the hyperlink region
		 * @param resource the target resource 
		 *   (<code>IFile</code>, <code>ITextViewer</code> or <code>java.io.File</code>)
		 * @param beginOffset the begin offset
		 */
		public JavaScriptHyperlink(IRegion region, Object resource, int beginOffset){
			this.region = region;
			this.resource = resource;
			this.beginOffset = beginOffset;
		}
		
		public IRegion getHyperlinkRegion() {
			return region;
		}

		public String getTypeLabel() {
			return null;
		}

		public String getHyperlinkText() {
			return null;
		}
		
		/**
		 * TODO <code>java.io.File</code> is unsupported.
		 */
		public void open() {
			// IFile
			if(resource instanceof IFile){
				IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
				try {
					IEditorPart editor = IDE.openEditor(page, (IFile)resource, true);
					IGotoMarker gotoMarker = (IGotoMarker)editor.getAdapter(IGotoMarker.class);
					if(gotoMarker!=null){
						IMarker marker= ((IFile)resource).createMarker(IMarker.TEXT);
						marker.setAttribute(IMarker.CHAR_START, beginOffset);
						marker.setAttribute(IMarker.CHAR_END, beginOffset);
						gotoMarker.gotoMarker(marker);
					}
				} catch (Exception ex) {
					HTMLPlugin.logException(ex);
				}
			}
			if(resource instanceof ITextViewer){
				((ITextViewer)resource).setSelectedRange(beginOffset, 0);
			}
			// java.io.File (external file)
			if(resource instanceof File){
				
			}
		}
	}

}
