package tk.eclipse.plugin.jspeditor.editors;

import java.util.HashMap;
import java.util.List;

import jp.aonir.fuzzyxml.FuzzyXMLAttribute;
import jp.aonir.fuzzyxml.FuzzyXMLDocument;
import jp.aonir.fuzzyxml.FuzzyXMLElement;
import jp.aonir.fuzzyxml.FuzzyXMLNode;
import jp.aonir.fuzzyxml.XPath;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;

import tk.eclipse.plugin.htmleditor.HTMLPlugin;
import tk.eclipse.plugin.htmleditor.HTMLUtil;
import tk.eclipse.plugin.htmleditor.ICustomTagValidator;
import tk.eclipse.plugin.htmleditor.ICustomTagValidatorContributer;
import tk.eclipse.plugin.htmleditor.assist.AttributeInfo;
import tk.eclipse.plugin.htmleditor.assist.TagInfo;
import tk.eclipse.plugin.htmleditor.editors.HTMLValidator;

/**
 * The JSP Validator that is called by <code>JSPSourceEditor</code>.
 * <p>
 * This validator validates the JSP document using FuzzyXML API,
 * and reports following errors.
 * <ul>
 *   <li>TLDs are not extst</li>
 *   <li>taglibs are not defined</li>
 *   <li>required attributes of taglibs are not specified</li>
 * </ul>
 * 
 * @author Naoki Takezoe
 * @author Tom Wickham-Jones
 */
public class JSPValidator extends HTMLValidator implements IJSPValidationMarkerCreator {
	
	private JSPInfo info;
	
	/**
	 * The constructor.
	 * 
	 * @param file
	 */
	public JSPValidator(IFile file){
		super(file);
	}
	
	/**
	 * The constructor.
	 * 
	 * @param file
	 * @param info
	 */
	public JSPValidator(IFile file, JSPInfo info){
		super(file);
		this.info = info;
	}
	
	@Override
  protected void validateDocument(FuzzyXMLDocument doc){
		if(doc!=null){
			if(this.info==null){
				this.info = JSPInfo.getJSPInfo(getFile(), getContent());
			}
			validateElement((FuzzyXMLElement)XPath.selectSingleNode(doc.getDocumentElement(),"*"));
		}
	}
	
	@Override
  protected boolean validateUsingFuzzyXML() {
		return true;
	}
	
	@Override
  protected boolean validateUsingTidy() {
		return false;
	}
	
	private void validateElement(FuzzyXMLElement element){
		if(element==null){
			return;
		}
		if(element.getName().indexOf(":") > 0){
			String[] dim = element.getName().split(":");
			if(dim.length==1){
				// I can't judge that this case is marked an error...
			} else {
				String prefix  = dim[0];
//				String tagName = dim[1];
				String uri = info.getTaglibUri(prefix);
				ICustomTagValidator validator = null;
				if(uri!=null){
					ICustomTagValidatorContributer contributer = HTMLPlugin.getDefault().getCustomTagValidatorContributer(uri);
					if(contributer!=null){
						validator = contributer.getConverter(dim[1]);
					}
					
					if(validator!=null){
						HashMap<String, String> attrMap = new HashMap<String, String>();
						FuzzyXMLAttribute[] attrs = element.getAttributes();
						for(int i=0;i<attrs.length;i++){
							attrMap.put(attrs[i].getName(),attrs[i].getValue());
						}
						validator.validate(this, attrMap,element,info);
						return;
					}
				}
				if(!prefix.equals("jsp")){
					TLDInfo tld = getTLDInfo(prefix);
					if(tld==null){
						// tld does not exist.
						addElementMarker(element,"Validation.NoTLD",prefix);
					} else if(tld.getUri()!=null || tld.getTagdir()!=null){
						TagInfo tagInfo = getTagInfo(tld,element.getName());
						if(tagInfo==null){
							// taglib does not exist.
							addElementMarker(element,"Validation.NoCustomTag",element.getName());
						} else {
							AttributeInfo[] attrs = tagInfo.getAttributeInfo();
							for(int i=0;i<attrs.length;i++){
								if(attrs[i].isRequired() && !element.hasAttribute(attrs[i].getAttributeName())){
									// check jsp:attribute
									boolean findAttribute = false;
									FuzzyXMLNode[] nodes = element.getChildren();
									for(int j=0;j<nodes.length;j++){
										if(nodes[j] instanceof FuzzyXMLElement 
												&& ((FuzzyXMLElement)nodes[j]).getName().equals("jsp:attribute")){
											FuzzyXMLAttribute attr = ((FuzzyXMLElement)nodes[j]).getAttributeNode("name");
											if(attr!=null && attr.getValue().equals(attrs[i].getAttributeName())){
												findAttribute = true;
												break;
											}
										}
									}
									// required attributes wasn't specified.
									if(!findAttribute){
										addElementMarker(element,"Validation.RequiredAttr",attrs[i].getAttributeName());
									}
								}
							}
						}
					}
				}
			}
		}
		
		FuzzyXMLNode[] nodes = element.getChildren();
		for(int i=0;i<nodes.length;i++){
			if(nodes[i] instanceof FuzzyXMLElement){
				validateElement((FuzzyXMLElement)nodes[i]);
			}
		}
	}
	
	private TLDInfo getTLDInfo(String prefix){
		TLDInfo[] tlds = info.getTLDInfo();
		for(int i=0;i<tlds.length;i++){
			if(tlds[i].getPrefix().equals(prefix)){
				return tlds[i];
			}
		}
		return null;
	}
	
	private TagInfo getTagInfo(TLDInfo tld,String tagName){
		List list = tld.getTagInfo();
//		boolean tagFound = false;
		for(int j=0;j<list.size();j++){
			TagInfo tagInfo = (TagInfo)list.get(j);
			if(tagInfo.getTagName().equals(tagName)){
				return tagInfo;
			}
		}
		return null;
	}
	
	private void addElementMarker(FuzzyXMLElement element,String key,String value){
		// create message
		String message = HTMLPlugin.createMessage(
				HTMLPlugin.getResourceString(key),new String[]{value});
		
		int offset = element.getOffset()+1;
		HTMLUtil.addMarker(getFile(), IMarker.SEVERITY_ERROR, getLineAtOffset(offset), offset ,element.getName().length(), message);
	}
	
	public void addMarker( int severity, int offset, int length, String message) {
		HTMLUtil.addMarker(getFile(), severity, getLineAtOffset(offset), offset , length, message);
	}
	
	/*
	 * Call any contributed JSP filter extensions
	 * @since 2.0.5
	 */
	@Override
  protected String filterContents( String jspContents, IFile file) {
		IJSPFilter[] jspFilters = HTMLPlugin.getDefault().getJSPFilters();
		for ( int i = 0; i < jspFilters.length; i++) {
			jspContents = jspFilters[i].filterJSP(jspContents, file);
		}
		return jspContents;
	}
}
