/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.empire.commons;

import java.io.Serializable;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Iterator;

import org.apache.empire.exceptions.InvalidArgumentException;
import org.w3c.dom.Element;

/**
 * This class holds a map of objects which are identified by a case insensitive key string.
 * 
 */
public class Attributes extends AbstractSet<Attributes.Attribute> implements Cloneable, Serializable 
{
	public static final class Attribute implements Serializable 
	{
        private static final long serialVersionUID = 1L;
	    private String name;
        private String namespace;
	    private Object value;
	    
	    public Attribute(String name, Object value, String namespace)
        {   // check name
	        if (name==null || (this.name=name.trim()).length()==0)
	            throw new InvalidArgumentException("name", name);
	        this.namespace = namespace;
            this.value = value;
        }
        public Attribute(String name, Object value)
        {   // check name
            this(name, value, null);
        }
        public String getName()
        {
            return name;
        }
        public String getNamespace()
        {
            return namespace;
        }
        public Object getValue()
        {
            return value;
        }
        public void setValue(Object value)
        {
            this.value = value;
        }
        @Override
        public boolean equals(Object other)
        {   // check namespace
            if (namespace!=null)
            {
                String otherNs = (other instanceof Attribute) ? ((Attribute)other).getNamespace() : null;
                if (!namespace.equalsIgnoreCase(otherNs))
                    return false; // Namespace don't match
            }
            // check name
            String otherName = (other instanceof Attribute) ? ((Attribute)other).getName() : other.toString();
            return name.equalsIgnoreCase(otherName);
        }
	}
	
	private static final class EmptyIterator implements Iterator<Attribute>
	{
        @Override
        public boolean hasNext()  {
            return false;
        }
        @Override
        public Attribute next() {
            return null;
        }
        @Override
        public void remove() {
        }
	}

    private static final long serialVersionUID = 1L;
    
	private static final EmptyIterator emptyIterator = new EmptyIterator(); 
	
	protected ArrayList<Attributes.Attribute> attributes = null;
	
	protected ArrayList<Attributes.Attribute> list()
	{
	    if (attributes==null)
	        attributes = new ArrayList<Attributes.Attribute>(2);
	    return attributes;
	}
    
    public Attributes()
    {
    }
    
    public Attributes(int size)
    {
        if (size>0)
            attributes = new ArrayList<Attributes.Attribute>(size);
    }

    @Override
    public Attributes clone()
    {
         Attributes clone = new Attributes();
         if (attributes!=null)
             clone.attributes = new ArrayList<Attributes.Attribute>(attributes);
         return clone;
    }

    @Override
    public Iterator<Attribute> iterator()
    {
        return (attributes!=null ? list().iterator() : emptyIterator);
    }

    @Override
    public int size()
    {
        return (attributes!=null ? attributes.size() : 0);
    }
    
    @Override
    public void clear()
    {
        if (attributes!=null)
            attributes.clear();
    }
    
    @Override
    public boolean contains(Object item)
    {
        if (item==null)
            return false;
        // find
        String name = (item instanceof Attribute) ? ((Attribute)item).getName() : item.toString();
        return (indexOf(name)>=0);
    }

    public final boolean contains(String name)
    {
        return contains((Object)name); 
    }
    
    @Override
    public boolean add(Attribute a)
    {
        set(a.getName(), a.getValue());
        return true;
    }

    @Override
    public boolean remove(Object item)
    {
        if (item==null)
            return false;
        // find
        String name = (item instanceof Attribute) ? ((Attribute)item).getName() : item.toString();
        int i = indexOf(name);
        if (i<0)
            return false;
        // remove
        list().remove(i);
        return true;
    }
    
    public final boolean remove(String name)
    {
        return remove((Object)name);
    }

    /**
     * @param name the attribute name
     * @return the attribute value
     */
    public int indexOf(String name)
    {   // Find an Entry
        if (attributes==null)
            return -1;
        // Find it now
        int size = attributes.size();
        for (int i = 0; i < size; i++)
        { // Search List for Index
            Attribute a = attributes.get(i);
            if (a.getName().equalsIgnoreCase(name))
                return i;
        }
        return -1;
    }

	/**
     * @param name the attribute name
     * @return the attribute value
     */
    public Object get(String name)
    {   // check name
        if (attributes==null || name==null || name.length()==0)
            return null;
        // find
        int i = indexOf(name);
        if (i<0)
            return null; // Not set
        // found
        return list().get(i).getValue();
    }

    /**
     * @param name the attribute
     * @param value The attribute to set
     */
    public Attribute set(String name, Object value)
    {
        // check name
        if (name==null || name.length()==0)
            return null;
        // Find
        int i = indexOf(name);
        if (i<0)
        {   // new attribute
            Attribute a = new Attribute(name, value); 
            list().add(a);
            return a;
        }
        else
        {   // existing attribute
            Attribute a = list().get(i);
            a.setValue(value);
            return a;
        }
    }

    @Override
    public Object[] toArray()
    {
        return (isEmpty() ? new Object[0] : list().toArray());
    }

    @Override
    public String toString()
    {
        if (isEmpty())
            return "{ /*empty*/ }";
        // create string
        StringBuilder b = new StringBuilder();
        b.append("{");
        boolean first = true;
        for (Attribute a : list())
        {
            b.append((first) ? "\"" : ",\r\n \"");
            String ns = a.getNamespace();
            if (StringUtils.isNotEmpty(ns))
            {   // append namespace
                b.append(ns);
                b.append(":");
            }
            b.append(a.getName());
            b.append("\":\"");
            b.append(a.getValue());
            b.append("\"");
            first = false;
        }    
        b.append("}");
        return b.toString();
    }
    
    /**
     * @param element the XMLElement to which to append the options
     * @param flags options (currently unused)
     */
    public void addXml(Element element, long flags)
    {
        if (isEmpty())
            return;
        // add All Options
    	for (Attribute a : list())
    	{
    	    Object value = a.getValue();
    	    if (value==null)
    	        continue;
    	    // set xml attribute
            String ns = a.getNamespace();
            if (StringUtils.isNotEmpty(ns))
                element.setAttributeNS(ns, a.getName(), String.valueOf(value));
            else
                element.setAttribute(a.getName(), String.valueOf(value));
    	}
    }
}
