package org.nodeclipse.ui.contentassist;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.CompletionProposal;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.contentassist.IContextInformationValidator;
import org.eclipse.swt.graphics.Image;
//import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.nodeclipse.ui.Activator;
import org.nodeclipse.ui.preferences.PreferenceConstants;
import org.nodeclipse.ui.util.Constants;
import org.nodeclipse.ui.util.NodeclipseConsole;


/**
 * Code completion (Content Assist) that works in TextEditor based Editor
 * proposals are sorted inside Model.</br>
 * TODO make this class JSON independent - remove JSON processing in addCompletionProposalFromCompletionJson()
 * @see method computeCompletionProposals(ITextViewer viewer, int offset)
 * @see JSDTProposalComputer wrapper for JSDT, that calls computeCompletionProposals()
 * 
 * @author Lamb Gao
 * @author Paul Verest
 * 
 * see grepcode.com/file/repository.grepcode.com/java/eclipse.org/4.3/org.eclipse.ui.workbench/texteditor/3.8.100/org/eclipse/ui/texteditor/HippieProposalProcessor.java
 * 
 */
public class NodeContentAssistant implements IContentAssistProcessor {
	
 	private static final ICompletionProposal[] NO_PROPOSALS= new ICompletionProposal[0];
 	private static final IContextInformation[] NO_CONTEXTS= new IContextInformation[0];	

	public static final Image MODULE = Activator.getImageDescriptor(Constants.MODULE_ICON).createImage();
    public static final Image METHOD = Activator.getImageDescriptor(Constants.METHOD_ICON).createImage();
    public static final Image CLASS = Activator.getImageDescriptor(Constants.CLASS_ICON).createImage();
    public static final Image PROPERTY = Activator.getImageDescriptor(Constants.PROPERTY_ICON).createImage();
    public static final Image UNKNOWN_BLUE = Activator.getImageDescriptor(Constants.UNKNOWN_BLUE_ICON).createImage();
	
    private boolean indexFilesInitialized = false;
    
	private IPreferenceStore preferenceStore = Activator.getDefault().getPreferenceStore();
	boolean useNodejsBaseModuleDefinitions = preferenceStore.getBoolean(PreferenceConstants.USE_NODEJS_BASE_MODULE_DEFINITIONS);//@since 0.12
	boolean useOrionIndexFiles = preferenceStore.getBoolean(PreferenceConstants.USE_ORION_INDEX_FILES);//@since 0.12
	boolean useCompletionJson = preferenceStore.getBoolean(PreferenceConstants.USE_COMPLETIONS_JSON);//@since 0.12
    
    private Model model = null ; //new Model();

    public String getInputString(IDocument doc, int offset) {
        StringBuffer buf = new StringBuffer();
        while (true) {
            try {
                char charOffset = doc.getChar(--offset);
                if (Character.isWhitespace(charOffset))
                    break;
                buf.append(charOffset);
            } catch (BadLocationException e) {
                break;
            }
        }
        return buf.reverse().toString();
    }

// the very first way    
//    private void addCompletionProposalFromNodejsSources(
//			List<CompletionProposal> list, String input, int offset) {
//        try {
//            for (int i = 0; i < ContentFromSources.METHODS.length(); i++) {
//                JSONObject method = (JSONObject) ContentFromSources.METHODS.get(i);
//                String trigger = method.getString("textRaw");
//                if (trigger != null && trigger.startsWith(input)) {
//                    int length = input.length();
//                    list.add(new CompletionProposal(trigger, offset - length, length, trigger.length(), 
//                    		METHOD, null, null, method.getString("desc") )); 
//                    		//method.getString("name")
//                }
//            }
//        } catch (JSONException e) {
//            //e.printStackTrace();
//        	NodeclipseConsole.write(e.getLocalizedMessage()+"\n");
//        }		
//	}
//
//    /** old first way, see addCompletionProposalFromModel() below (that most of code moved to ContentFromSources.populateModel() )
//     */
//    @Deprecated
//    private void addCompletionProposalFromNodejsSources(
//			List<CompletionProposal> list, String input, int offset) {
//        int length = input.length();
//        // modules30: timers(m8), module, addons, util(m13), Events(c1), domain(m1)(c1), buffer(c2), stream(c4), crypto(m18)(c7), 
//        // tls_(ssl)(m5)(c4), stringdecoder(c1), fs(m67)(c4), path(m7), net(m10)(c2), dgram(m1)(c1), dns(m10), http(m4)(c4), https(m3)(c2), 
//        // url(m3), querystring(m2), punycode(m4), readline(m1)(c1), repl(m1), vm(m5)(c1), child_process(m4)(c1), assert(m11), tty(m2)(c2), zlib(m14)(c8), os(m13), cluster(m3)(c1)
//        try {
//        	JSONObject NodejsContext = ContentFromSources.defaultInstance.NodejsContext;
//			JSONArray modules = NodejsContext.getJSONArray("modules");
//			log("modules"+modules.length()+" ");
//			for (int i = 0; i < modules.length(); i++) {
//				JSONObject module = (JSONObject) modules.get(i);
//				String moduleName = module.getString("name");
//				debug( ", "+moduleName);
//				
//				if (module.has("methods")) {
//					JSONArray methods = module.getJSONArray("methods");
//					debug("(m"+methods.length()+")");
//					for (int j = 0; j < methods.length(); j++) {
//						JSONObject method = (JSONObject) methods.get(j);
//						// example: "textRaw": "http.createServer([requestListener])","type": "method","name": "createServer",
//						String trigger = method.getString("textRaw");
//						if (trigger != null && trigger.startsWith(input)) {
//							String name = method.getString("name");
//							String desc = formatedName(name,trigger)+method.getString("desc");
//							list.add(new CompletionProposal(trigger, offset - length, length, trigger.length(), 
//									METHOD, trigger, null, desc));
//						}
//					}
//				}
//
//				if (module.has("classes")){
//	                JSONArray classes = module.getJSONArray("classes");
//	                debug("(c"+classes.length()+")");
//	                for (int j = 0; j < classes.length(); j++) {
//	                    JSONObject clazz = (JSONObject) classes.get(j);
//	                    // example: "textRaw": "Class: Domain","type": "class","name": "Domain"
//	                    String trigger = clazz.getString("name");
//	                    if (!trigger.startsWith(moduleName)) {
//	                    	trigger=moduleName+'.'+trigger;
//	                    }
//	                    if (trigger != null && trigger.startsWith(input)) {
//	                    	//String name = clazz.getString("name");
//	                    	String desc = formatedName(trigger,clazz.getString("textRaw"))+clazz.getString("desc");
//	                        list.add(new CompletionProposal(trigger, offset - length, length, trigger.length(), 
//	                        		CLASS, trigger, null, desc )); 
//	                    }
//	                }
//                }
//                
//        	}
//        } catch (JSONException e) {
//        	log(e.getLocalizedMessage()+"\n"+e);
//        }		
//	}
//    private String formatedName(String name) {
//		return "<b>"+name+"</b><br/>";
//	}
//    private String formatedName(String name, String trigger) {
//		return formatedName(name)+"<code>"+trigger+"</code><br/>";
//	}

    
    private void addCompletionProposalFromModel(List<CompletionProposal> list, String input, int offset) {
        int length = input.length();
        //TODO create Model here and pass it as parameter
        if (useNodejsBaseModuleDefinitions){
        	model = ContentFromSources.getDefaultInstances().model;
        }else{
        	model = new Model();
        }
        if (useOrionIndexFiles && !indexFilesInitialized){ //@since 0.12 context from Orion IndexFiles
        	ContentFromOrionIndexFiles.initModel(model);
        	indexFilesInitialized = true;
        }
        if (model==null){
        	log("Model is empty! (There should have been initialization error)");
        }	
        for(Entry entry: model.findMatchingEntries(input)){
        	String trigger = entry.trigger;
        	String desc = entry.desc;
        	//Image image = (entry.type == EntryType.clazz) ? CLASS : METHOD;
        	Image image = null;
        	switch (entry.type){
        		case module:	image = MODULE; break;
        		case method:	image = METHOD; break;
        		case clazz:	image = CLASS; break;        		
        		case property:	image = PROPERTY; break;
        		case unknown:	image = UNKNOWN_BLUE;
        	}        		
			list.add(new CompletionProposal(trigger, offset - length, length, trigger.length(), 
					image, trigger, null, desc));        	
        }
	}

	public void addCompletionProposalFromCompletionJson(
    		List<CompletionProposal> list, String input, int offset) {
        //List<CompletionProposal> list = new ArrayList<CompletionProposal>();
        int length = input.length();
        try {
            for (int i = 0; i < ContentFromCompletionsJson.COMPLETIONS.length(); i++) {
                JSONObject method = (JSONObject) ContentFromCompletionsJson.COMPLETIONS.get(i);
                String trigger = method.getString("trigger");
                if (trigger != null && trigger.startsWith(input)) {
                    list.add(new CompletionProposal(trigger, offset - length, length, trigger.length(), 
                    		//METHOD
                    		null, null, null, null));
                }
            }
        } catch (JSONException e) {
        	log(e.getLocalizedMessage()+"\n");
        }
        //return list;
    }

	// Entry point for JSDT API call
    @Override
    public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
        IDocument doc = viewer.getDocument();
        String inputString = getInputString(doc, offset);
        List<CompletionProposal> list = new ArrayList<CompletionProposal>();
        //list = getCompletionProposalFromCompletionJson(inputString , offset);
        //addCompletionProposalFromNodejsSources(list, inputString , offset);
        addCompletionProposalFromModel(list, inputString , offset);
        if (useCompletionJson && ContentFromCompletionsJson.COMPLETIONS!=null){
        	addCompletionProposalFromCompletionJson(list, inputString , offset);
        }	
        return (CompletionProposal[]) list.toArray(new CompletionProposal[list.size()]);
    }
    
    @Override
    public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) {
        return NO_CONTEXTS;
    }

    @Override
    public char[] getCompletionProposalAutoActivationCharacters() {
        // TODO Preferences
    	// may be null
        return ".abcdefghijklmnopqrstuvwxyz".toCharArray();
    }

    @Override
    public char[] getContextInformationAutoActivationCharacters() {
        return null;
    }

    @Override
    public String getErrorMessage() {
        return null;
    }

    @Override
    public IContextInformationValidator getContextInformationValidator() {
        return null;
    }

	//    private static void debug(String s){
	//    	//NodeclipseConsole.write(s);
	//    }
    private static void log(String s){
    	NodeclipseConsole.write(s);
    }
}
