package tk.eclipse.plugin.jseditor.editors;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 
 * @author Naoki Takezoe
 */
public class JavaScriptModel implements JavaScriptContext {
	
	private List<JavaScriptElement> _children = new ArrayList<JavaScriptElement>();
	private List<JavaScriptComment> _comments = new ArrayList<JavaScriptComment>();
	private JavaScriptContext _context;
	private int _endOffset;
	
	public JavaScriptModel(String source){
		update(source);
	}
	
	public int getStartOffset(){
		return 0;
	}
	
	public int getEndOffset(){
		return _endOffset;
	}
	
	public JavaScriptContext getContextFromOffset(int offset){
		return getContextFromOffset(this, offset);
	}
	
	private JavaScriptContext getContextFromOffset(JavaScriptContext context, int offset){
		if(context.getStartOffset() < offset && context.getEndOffset() > offset){
			JavaScriptElement[] children = context.getChildren();
			for(int i=0;i<children.length;i++){
				if(children[i] instanceof JavaScriptContext){
					JavaScriptContext result = getContextFromOffset((JavaScriptContext)children[i], offset);
					if(result!=null){
						return result;
					}
				}
			}
			return context;
		}
		return this;
	}
	
	/**
	 * Updates model structure by the specified source code.
	 * 
	 * @param source JavaScript
	 */
	public void update(String source){
		this._children.clear();
		this._comments.clear();
		this._endOffset = source.length();
		_context = this;
		
		boolean whitespace = true;
		char quote = 0;
		boolean escape = false;
		
		for(int i=0;i<source.length();i++){
			char c = source.charAt(i);
			// String literal
			if(c=='"' || c=='\''){
				if(!escape){
					quote = (quote == c ? 0 : c);
				}
				escape = false;
				continue;
			}
			if(quote != 0){
				escape = (c=='\\');
				continue;
			}
			// skip comment
			if(c=='/' && source.length() > i+1){
				char nc = source.charAt(i+1);
				if(nc == '/'){
					int start = i;
					while(nc!='\r' && nc!='\n' && source.length() > i){
						i++;
						nc = source.charAt(i+1);
					}
					_comments.add(new JavaScriptComment(start, i+1,
							source.substring(start, i+1)));
				}
				if(nc == '*'){
					int start = i;
					i = source.indexOf("*/", i);
					if(i==-1){
						break;
					}
					_comments.add(new JavaScriptComment(start, i+2,
							source.substring(start, i+2)));
				}
			}
			// var
			if(whitespace && c=='v'){
				int result = parseVariable(source, i, _context);
				if(result!=0){
					whitespace = true;
					i += result;
					continue;
				}
			}
			// function
			if(whitespace && c=='f'){
				Object[] result = parseFunction(source, i, _context);
				if(result != null){
					whitespace = true;
					i += ((Integer)result[0]).intValue();
					_context = (JavaScriptFunction)result[1];
					continue;
				}
			}
			// end function
			if(c=='}'){
				if(_context.getParent()!=null){
					if(_context instanceof JavaScriptFunction){
						((JavaScriptFunction)_context).setEndOffset(i);
					}
					_context = _context.getParent();
				}
			}
			// whitespace
			if(c==' ' || c=='\t' || c=='\r' || c=='\n'){
				whitespace = true;
			} else {
				whitespace = false;
			}
		}
	}
	
	private static int parseVariable(String source, int position, JavaScriptContext context){
		Pattern pattern = Pattern.compile("var[\\s\r\n]+(.+?)[\\s\r\n]*?[;=]");
		if(source.indexOf("var", position) == position){
			Matcher matcher = pattern.matcher(source.substring(position));
			if(matcher.find() && matcher.start()==0){
				JavaScriptVariable var = new JavaScriptVariable(matcher.group(1), position);
				context.add(var);
				return matcher.end();
			}
		}
		return 0;
	}
	
	private static Object[] parseFunction(String source, int position, JavaScriptContext context){
		Pattern pattern = Pattern.compile("function[\\s\r\n]+?(.+?)[\\s\r\n]*?\\((.*?)\\)[\\s\r\n]*?\\{", Pattern.DOTALL);
		if(source.indexOf("function", position) == position){
			Matcher matcher = pattern.matcher(source.substring(position));
			if(matcher.find() && matcher.start()==0){
				String args = matcher.group(2).replaceAll("[\\s\r\n]*,[\\s\r\n]*",", ").trim();
				JavaScriptFunction func = new JavaScriptFunction(matcher.group(1), args, position);
				func.setParent(context);
				context.add(func);
				return new Object[]{Integer.valueOf(matcher.end()), func};
			}
		}
		return null;
	}

	
	public void add(JavaScriptFunction func) {
		this._children.add(func);
	}
	
	public void add(JavaScriptVariable var) {
		this._children.add(var);
	}
	
	public JavaScriptElement[] getChildren(){
		return this._children.toArray(new JavaScriptElement[this._children.size()]);
	}
	
	public JavaScriptElement[] getVisibleElements(){
		return getChildren();
	}
	
	public JavaScriptContext getParent(){
		return null;
	}
	
	public JavaScriptComment[] getComments(){
		return this._comments.toArray(new JavaScriptComment[this._comments.size()]);
	}
	
}
