package railo.runtime.text.xml;

import java.util.ArrayList;
import java.util.Iterator;

import org.w3c.dom.Attr;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

import railo.runtime.PageContext;
import railo.runtime.dump.DumpData;
import railo.runtime.dump.DumpProperties;
import railo.runtime.dump.DumpTable;
import railo.runtime.dump.DumpUtil;
import railo.runtime.dump.SimpleDumpData;
import railo.runtime.exp.ExpressionException;
import railo.runtime.exp.PageException;
import railo.runtime.exp.XMLException;
import railo.runtime.op.Caster;
import railo.runtime.type.Collection;
import railo.runtime.type.KeyImpl;
import railo.runtime.type.Struct;
import railo.runtime.type.dt.DateTime;
import railo.runtime.type.it.EntryIterator;
import railo.runtime.type.it.KeyIterator;
import railo.runtime.type.it.StringIterator;
import railo.runtime.type.it.ValueIterator;
import railo.runtime.type.util.ListUtil;
import railo.runtime.type.util.StructSupport;

/**
 * represent a Struct and a NamedNodeMap
 */
public final class XMLAttributes extends StructSupport implements Struct,NamedNodeMap {
	

	private final NamedNodeMap nodeMap;
	private final Document owner;
	private final Node parent;
	private final boolean caseSensitive;

	/**
	 * constructor of the class (readonly)
	 * @param nodeMap
	 */
	public XMLAttributes(Node parent, boolean caseSensitive) {
		this.owner=parent.getOwnerDocument();
		this.parent=parent;
		this.nodeMap=parent.getAttributes();
		this.caseSensitive=caseSensitive;
	}

	@Override
	public DumpData toDumpData(PageContext pageContext, int maxlevel, DumpProperties dp) {
		Collection.Key[] keys=keys();
		maxlevel--;
		DumpTable table = new DumpTable("xml","#999966","#cccc99","#000000");
		table.setTitle("Struct (XML Attributes)");

		int maxkeys=dp.getMaxKeys();
		int index=0;
		Collection.Key k;
		for(int i=0;i<keys.length;i++) {
			k=keys[i];
			
			if(DumpUtil.keyValid(dp,maxlevel, k)){
				if(maxkeys<=index++)break;
				table.appendRow(1,new SimpleDumpData(k.getString()),DumpUtil.toDumpData(get(k.getString(),null), pageContext,maxlevel,dp));
			}
		}
		return table;
	}
	

	@Override
	public int size() {
		return nodeMap.getLength();
	}
	
	@Override
	public Collection.Key[] keys() {
		int len=nodeMap.getLength();
		ArrayList<Collection.Key> list =new ArrayList<Collection.Key>();
		for(int i=0;i<len;i++) {
			Node item = nodeMap.item(i);
			if(item instanceof Attr)
				list.add(KeyImpl.init(((Attr)item).getName()));
		}
		return list.toArray(new Collection.Key[list.size()]);
	}
    
	@Override
	public Object remove(Collection.Key k) throws PageException {
		String key=k.getString();
		Node rtn=null;
		if(!caseSensitive){
			int len = nodeMap.getLength();
			String nn;
			for(int i=len-1;i>=0;i--) {
				nn=nodeMap.item(i).getNodeName();
				if(key.equalsIgnoreCase(nn)) rtn=nodeMap.removeNamedItem(nn);
			}
		}
		else rtn=nodeMap.removeNamedItem(toName(key));
		
		if(rtn!=null) return rtn.getNodeValue();
		throw new ExpressionException("can't remove element with name ["+key+"], element doesn't exist");
	}	
    
    @Override
    public Object removeEL(Collection.Key k) {
    	String key=k.getString();
		Node rtn=null;
		if(!caseSensitive){
			int len = nodeMap.getLength();
			String nn;
			for(int i=len-1;i>=0;i--) {
				nn=nodeMap.item(i).getNodeName();
				if(key.equalsIgnoreCase(nn)) rtn=nodeMap.removeNamedItem(nn);
			}
		}
		else rtn=nodeMap.removeNamedItem(toName(key));
		
		if(rtn!=null) return rtn.getNodeValue();
		return null;
    }
    
	@Override
	public void clear() {
		Collection.Key[] keys=keys();
		for(int i=0;i<keys.length;i++) {
			nodeMap.removeNamedItem(keys[i].getString());
		}
	}

	@Override
	public Object get(Collection.Key key) throws ExpressionException {
		Node rtn = nodeMap.getNamedItem(key.getString());
		if(rtn!=null) return rtn.getNodeValue();
		
		Collection.Key[] keys=keys();
		for(int i=0;i<keys.length;i++) {
			if(key.equalsIgnoreCase(keys[i]))
				return nodeMap.getNamedItem(keys[i].getString()).getNodeValue();
		}
		throw new ExpressionException("No Attribute "+key.getString()+" defined for tag","attributes are ["+ListUtil.arrayToList(keys,", ")+"]");
	}

	@Override
	public Object get(Collection.Key key, Object defaultValue) {
		try {
			return get(key);
		} catch (PageException e) {
			return defaultValue;
		}
	}

	@Override
	public Object set(Collection.Key key, Object value) throws PageException {
		if(owner==null) return value;
		
		try {
			Attr attr=owner.createAttribute(toName(key.getString()));
			attr.setValue(Caster.toString(value));
			nodeMap.setNamedItem(attr);
			
		}
		catch(DOMException de) {
			throw new XMLException(de);
		}
		
		
		
		
		return value;
	}

	private String toName(String name) {
		return toName(name,name);
	}
	private String toName(String name, String defaultValue) {
		if(caseSensitive) return name;
		
		Node n = nodeMap.getNamedItem(name);
		if(n!=null) return n.getNodeName();
		
		int len = nodeMap.getLength();
		String nn;
		for(int i=0;i<len;i++) {
			nn=nodeMap.item(i).getNodeName();
			if(name.equalsIgnoreCase(nn)) return nn;
		}
		return defaultValue;
	}
	
	@Override
	public Object setEL(Collection.Key key, Object value) {
		if(owner==null) return value;
		try {
			Attr attr=owner.createAttribute(toName(key.getString()));
			attr.setValue(Caster.toString(value));
			nodeMap.setNamedItem(attr);
		}
		catch(Exception e) {
			return null;
		}
		return value;
	}
	
	@Override
	public Iterator<Collection.Key> keyIterator() {
		return new KeyIterator(keys());
	}
    
    @Override
	public Iterator<String> keysAsStringIterator() {
    	return new StringIterator(keys());
    }
	
	@Override
	public Iterator<Entry<Key, Object>> entryIterator() {
		return new EntryIterator(this,keys());
	}
	
	@Override
	public Iterator<Object> valueIterator() {
		return new ValueIterator(this,keys());
	}

	@Override
	public int getLength() {
		return nodeMap.getLength();
	}

	@Override
	public Node item(int index) {
		return nodeMap.item(index);
	}

	@Override
	public Node getNamedItem(String name) {
		return nodeMap.getNamedItem(name);
	}

	@Override
	public Node removeNamedItem(String name) throws DOMException {
		return nodeMap.removeNamedItem(name);
	}

	@Override
	public Node setNamedItem(Node arg) throws DOMException {
		return nodeMap.setNamedItem(arg);
	}

	@Override
	public Node setNamedItemNS(Node arg) throws DOMException {
		return nodeMap.setNamedItemNS(arg);
	}

	@Override
	public Node getNamedItemNS(String namespaceURI, String localName) {
		return nodeMap.getNamedItemNS(namespaceURI,localName);
	}

	@Override
	public Node removeNamedItemNS(String namespaceURI, String localName) throws DOMException {
		return nodeMap.removeNamedItemNS(namespaceURI, localName);
	}

	@Override
	public Collection duplicate(boolean deepCopy) {
		return new XMLAttributes(parent.cloneNode(deepCopy),caseSensitive);
	}
	
	
	/**
	 * @return returns named Node map
	 */
	public NamedNodeMap toNamedNodeMap() {
		return nodeMap;
	}

	@Override
	public boolean containsKey(Collection.Key key) {
        return get(key,null)!=null;
	}
    
    @Override
    public String castToString() throws ExpressionException {
        throw new ExpressionException("Can't cast XML NamedNodeMap to String");
    }
    
	@Override
	public String castToString(String defaultValue) {
		return defaultValue;
	}

    @Override
    public boolean castToBooleanValue() throws ExpressionException {
        throw new ExpressionException("Can't cast XML NamedNodeMap to a boolean value");
    }
    
    @Override
    public Boolean castToBoolean(Boolean defaultValue) {
        return defaultValue;
    }


    @Override
    public double castToDoubleValue() throws ExpressionException {
        throw new ExpressionException("Can't cast XML NamedNodeMap to a number value");
    }
    
    @Override
    public double castToDoubleValue(double defaultValue) {
        return defaultValue;
    }


    @Override
    public DateTime castToDateTime() throws ExpressionException {
    	throw new ExpressionException("Can't cast XML NamedNodeMap to a date value");
    }
    
    @Override
    public DateTime castToDateTime(DateTime defaultValue) {
        return defaultValue;
    }

	@Override
	public int compareTo(boolean b) throws ExpressionException {
		throw new ExpressionException("can't compare XML NamedNodeMap with a boolean value");
	}

	@Override
	public int compareTo(DateTime dt) throws PageException {
		throw new ExpressionException("can't compare XML NamedNodeMap with a DateTime Object");
	}

	@Override
	public int compareTo(double d) throws PageException {
		throw new ExpressionException("can't compare XML NamedNodeMap with a numeric value");
	}

	@Override
	public int compareTo(String str) throws PageException {
		throw new ExpressionException("can't compare XML NamedNodeMap with a String");
	}
}