package tk.eclipse.plugin.htmleditor.editors;

import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import jp.aonir.fuzzyxml.FuzzyXMLDocument;
import jp.aonir.fuzzyxml.FuzzyXMLParser;
import jp.aonir.fuzzyxml.event.FuzzyXMLErrorEvent;
import jp.aonir.fuzzyxml.event.FuzzyXMLErrorListener;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.jface.preference.IPreferenceStore;
import org.w3c.tidy.Tidy;

import tk.eclipse.plugin.htmleditor.HTMLPlugin;
import tk.eclipse.plugin.htmleditor.HTMLUtil;

/**
 * The HTML Validator that is called by HTMLSourceEditor.
 * <p>
 * This validator validates the HTML document using JTidy,
 * and provides some common features for validators.
 * 
 * @author Naoki Takezoe
 * @author Tom Wickham-Jones
 */
public class HTMLValidator implements FuzzyXMLErrorListener {
	
	private String _original;
	private String _contents;
	private IFile _file;
	
	private Pattern TIDY_ERROR = Pattern.compile("^line ([0-9]+?) column ([0-9]+?) - (.+?)$",Pattern.DOTALL);
	
	private boolean _showXMLErrors;
	
	/**
	 * Constructor.
	 * 
	 * @param file IFile that is validated by this class.
	 */
	public HTMLValidator(IFile file){
		this._file = file;
    _showXMLErrors = showXMLErrors();
	}
  
  public boolean showXMLErrors() {
    IPreferenceStore store = HTMLPlugin.getDefault().getPreferenceStore();
    return store.getBoolean( HTMLPlugin.PREF_SHOW_XML_ERRORS);
  }
	
	/**
	 * Validates the HTML document.
	 */
	public void doValidate(){
		try {
			// Validates using JTidy
			if(validateUsingTidy()){
				ByteArrayOutputStream out = null;
				try {
					out = new ByteArrayOutputStream();
					Tidy tidy = new Tidy();
					tidy.setXHTML(false);
					tidy.setCharEncoding(org.w3c.tidy.Configuration.RAW);
					tidy.setErrout(new PrintWriter(out, true));
					tidy.parse(_file.getContents(), null);
					
					String errors = new String(out.toByteArray());
					
					errors = errors.replaceAll("\r\n","\n");
					errors = errors.replaceAll("\r"  ,"\n");
					
					String[] dim = errors.split("\n");
					for(int i=0;i<dim.length;i++){
						if(dim[i].startsWith("line")){
							Matcher matcher = TIDY_ERROR.matcher(dim[i]);
							if(matcher.matches()){
								String message = matcher.group(3);
								if(message.startsWith("Warning")){
									HTMLUtil.addMarker(_file, IMarker.SEVERITY_WARNING, 
											Integer.parseInt(matcher.group(1)),
											matcher.group(3));
								} else {
									HTMLUtil.addMarker(_file, IMarker.SEVERITY_ERROR, 
											Integer.parseInt(matcher.group(1)),
											matcher.group(3));
								}
							}
						}
					}
					
				} finally {
					if(out!=null){
						out.close();
					}
				}
			}
			
			// Validates using FuzzyXML
			if(validateUsingFuzzyXML()){
				this._original = new String(HTMLUtil.readStream(_file.getContents()), _file.getCharset());
				String contents = filterContents(this._original, _file);
				contents = HTMLUtil.scriptlet2space(contents,false);
				
				this._contents = contents;
				this._contents = this._contents.replaceAll("\r\n"," \n");
				this._contents = this._contents.replaceAll("\r"  ,"\n");
				
				FuzzyXMLParser parser = new FuzzyXMLParser(false);
				parser.addErrorListener( this);
//				parser.addErrorListener(new HTMLParseErrorListener());
				FuzzyXMLDocument doc = parser.parse(contents);
				validateDocument(doc);
			}
		} catch(Exception ex){
			HTMLPlugin.logException(ex);
		}
	}
	
	/**
	 * Returns true if this validator validates using JTidy.
	 * <p>
	 * <strong>Notice:</strong> Even if this method returns true, JTidy based validation isn't executed,
	 * when &quot;HTML Validation&quot; isn't checked in the project property page.
	 * 
	 * @return
	 *   <ul>
	 *     <li>true - validate using JTidy</li>
	 *     <li>false - not validate using JTidy</li>
	 *   </ul>
	 */
	protected boolean validateUsingTidy(){
		return true;
	}
	
	/**
	 * Returns true if this validator validates using FuzzyXML.
	 * Validation logic is written in validateDocument().
	 * 
	 * @return
	 *   <ul>
	 *     <li>true - validate using FuzzyXML</li>
	 *     <li>false - not validate using FuzzyXML</li>
	 *   </ul>
	 */
	protected boolean validateUsingFuzzyXML(){
		return false;
	}
	
	/**
	 * Validates using FuzzyXML.
	 * This method is called only when validateUsingFuzzyXML() returns true.
	 * 
	 * @param doc FuzzyXMLDocument
	 */
	protected void validateDocument(FuzzyXMLDocument doc){
		// Nothing to do in default.
	}
	
	/**
	 * Returns the targeted IFile object.
	 * 
	 * @return the IFile object, target of this validator
	 */
	protected IFile getFile(){
		return this._file;
	}
	
	/**
	 * Returns the original source code.
	 * 
	 * @return the original source code
	 */
	protected String getContent(){
		return this._original;
	}
	
	/**
	 * Returns the line number from the offset.
	 * 
	 * @param offset the offset
	 * @return the line number.
	 * 
	 * Needs to be offset+1 in the substring to make sure 
	 * that the text is included in the substring.  Otherwise 
	 * an offset at the start of the line is not included.
	 */
	protected int getLineAtOffset(int offset){
		String text = _contents.substring(0,offset+1);
		return text.split("\n").length;
	}

	/**
	 * Filters the contents of the page.
	 * <p>
	 * This validator will validate returned contents.
	 * 
	 * @param contents
	 * @return filtered contents
	 * @since 2.0.5
	 */
	protected String filterContents( String contents, IFile file) {
		return contents;
	}
	
  protected boolean showXMLError(FuzzyXMLErrorEvent event) {
    return _showXMLErrors;
  }
  
	public void error(FuzzyXMLErrorEvent event) {
		if ( !showXMLError(event)) {
			return;
		}
		int offset = event.getOffset();
		int length = event.getLength();
		String message = event.getMessage();
		HTMLUtil.addMarker(getFile(), IMarker.SEVERITY_ERROR, getLineAtOffset(offset), offset , length, message);
	}
	
	
}
