package railo.runtime.util;

import java.net.MalformedURLException;
import java.net.URL;

import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import railo.commons.lang.StringUtil;
import railo.runtime.exp.PageException;
import railo.runtime.op.Caster;
import railo.runtime.text.xml.XMLUtil;
import railo.transformer.util.CFMLString;

/**
 * Transform a HTML String, set all relative Pathes inside HTML File to absolute
 * TODO Test this  
 *
 */
public final class URLResolver {
	
	private Tag[] tags=new Tag[]{
			new Tag("a","href"),
			new Tag("link","href"),
			new Tag("form","action"),
			new Tag("applet","code"),
			new Tag("script","src"),
			new Tag("body","background"),
			new Tag("frame","src"),
			new Tag("bgsound","src"),
			new Tag("img","src"),
			
			new Tag("embed",new String[]{"src","pluginspace"}),
			new Tag("object",new String[]{"data","classid","codebase","usemap"})
			
	};
	

	public void transform(Node node, URL url) throws MalformedURLException {
		Element el;
		if(node.getNodeType()==Node.DOCUMENT_NODE) {
			transform(XMLUtil.getRootElement(node, true), url);
		}
		else if(node.getNodeType()==Node.ELEMENT_NODE) {
			el=(Element) node;
			String[] attr;
			NamedNodeMap map;
			String attrName,value,value2,nodeName=el.getNodeName();
			int len;
			// translate attribute
			for(int i=0;i<tags.length;i++) {
				if(tags[i].tag.equalsIgnoreCase(nodeName)) {
					
					attr=tags[i].attributes;
					map = el.getAttributes();
					len = map.getLength();
					for(int y=0;y<attr.length;y++) {
						for(int z=0;z<len;z++) {
							attrName=map.item(z).getNodeName();
							if(attrName.equalsIgnoreCase(attr[y])) {
								value=el.getAttribute(attrName);
								value2=add(url, value);
								
								if(value!=value2){
									el.setAttribute(attrName, value2);
								}
								
								break;
							}
						}
					}
				}
			}
			
			// list children
			NodeList nodes = el.getChildNodes();
			len=nodes.getLength();
			for(int i=0;i<len;i++) {
				transform(nodes.item(i), url);
			}
		}
	}
	
	/**
	 * transform the HTML String
	 * @param html HTML String to transform
	 * @param url Absolute URL path to set
	 * @return transformed HTMl String
	 * @throws PageException
	 */
	public String transform(String html, URL url, boolean setBaseTag) throws PageException {
		StringBuffer target=new StringBuffer();
		CFMLString cfml=new CFMLString(html,"UTF-8");
		while(!cfml.isAfterLast()) {
			if(cfml.forwardIfCurrent('<')) {
				target.append('<');
				try {
					for(int i=0;i<tags.length;i++) {
						if(cfml.forwardIfCurrent(tags[i].tag+" ")) {
							target.append(tags[i].tag+" ");
							transformTag(target,cfml,tags[i],url);
						}
					}
				}
				catch(MalformedURLException me) {
					throw Caster.toPageException(me);
				}
			}
			else {
				target.append(cfml.getCurrent());
				cfml.next();
			}
			
		}
		if(!setBaseTag)return target.toString();
		
		html=target.toString();
		String prefix="",postfix="";
		int index = StringUtil.indexOfIgnoreCase(html, "</head>");
		if(index==-1) {
			prefix="<head>";
			postfix="</head>";
			index = StringUtil.indexOfIgnoreCase(html, "</html>");
		}
			
		
		if(index!=-1) {
			StringBuffer sb=new StringBuffer();
			sb.append(html.substring(0,index));
			String port=url.getPort()==-1?"":":"+url.getPort();
			sb.append(prefix+"<base href=\""+(url.getProtocol()+"://"+url.getHost()+port)+"\">"+postfix);
			sb.append(html.substring(index));
			html=sb.toString();
			
		}
		return html;
	}
	
	/**
	 * transform a single tag
	 * @param target target to write to
	 * @param cfml CFMl String Object containing plain HTML
	 * @param tag current tag totransform
	 * @param url absolute URL to Set at tag attribute
	 * @throws MalformedURLException
	 */
	private void transformTag(StringBuffer target, CFMLString cfml, Tag tag,URL url) throws MalformedURLException {
		// TODO attribute inside other attribute
		
		char quote=0;
		boolean inside=false;
		StringBuffer value=new StringBuffer();
		
		while(!cfml.isAfterLast()) {
			if(inside) {
				if(quote!=0 && cfml.forwardIfCurrent(quote)) {
					inside=false;
					target.append(add(url,value.toString()));
					target.append(quote);
				}
				else if(quote==0 && (cfml.isCurrent(' ')||cfml.isCurrent("/>")||cfml.isCurrent('>')||cfml.isCurrent('\t')||cfml.isCurrent('\n'))) {
					
					inside=false;
					target.append(new URL(url,value.toString()));
					target.append(cfml.getCurrent());
					cfml.next();
				}
				else {
					value.append(cfml.getCurrent());
					cfml.next();
				}
			}
			else if(cfml.forwardIfCurrent('>')) {
				target.append('>');
				break;
			}
			else {
				
				for(int i=0;i<tag.attributes.length;i++) {
					if(cfml.forwardIfCurrent(tag.attributes[i])) {
						target.append(tag.attributes[i]);
						cfml.removeSpace();
						// =
						if(cfml.isCurrent('=')) {
							inside=true;
							target.append('=');
							cfml.next();
							cfml.removeSpace();
							
							quote=cfml.getCurrent();
							value=new StringBuffer();
							if(quote!='"' && quote!='\'')quote=0;
							else {
								target.append(quote);
								cfml.next();
							}
						}
					}
				}
				if(!inside) {
					target.append(cfml.getCurrent());
					cfml.next();
				}
			}
		}
	}


	private String add(URL url, String value) {
		value=value.trim();
		String lcValue=value.toLowerCase();
		if(lcValue.startsWith("http://") || lcValue.startsWith("file://") || lcValue.startsWith("news://") || lcValue.startsWith("goopher://") || lcValue.startsWith("javascript:"))
			return (value);
		try {
			return new URL(url,value.toString()).toExternalForm();
		} catch (MalformedURLException e) {
			return value;
		}
	}

	private class Tag {
		private String tag;
		private String[] attributes;
		private Tag(String tag,String[] attributes) {
			this.tag=tag.toLowerCase();
			this.attributes=new String[attributes.length];
			for(int i=0;i<attributes.length;i++) {
				this.attributes[i]=attributes[i].toLowerCase();
			}
			
		}
		private Tag(String tag,String attribute1) {
			this.tag=tag.toLowerCase();
			this.attributes=new String[]{attribute1.toLowerCase()};
		}
	
	}

	public static URLResolver getInstance() {
		return new URLResolver();
	}
	
}