/**
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 *
 * Copyright (C)
 * <a href="http://www.inab.org">Spanish National Institute of Bioinformatics (INB)</a>
 * <a href="http://www.bsc.es">Barcelona Supercomputing Center (BSC)</a>
 * <a href="http://inb.bsc.es">Computational Node 6</a>
 */

package org.inb.biomoby.shared.message;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.XmlRootElement;
import org.w3c.dom.Element;

/**
 * @author Dmitry Repchevsky
 */

@XmlRootElement(name="Object")
@XmlAccessorType(XmlAccessType.NONE)
public class MobyObject extends AbstractMobyObject implements Serializable
{
    protected List<Object> attributes;
            
    public MobyObject()
    {
        this("", "");
    }
    
    public MobyObject(String id, String namespace)
    {
        super(id, namespace);
        
        attributes = new LinkedList<Object>();
    }

    /**
     * This is a non-public method that JAXB uses for marshalling/unmarshalling attributes
     * 
     * @return List of all object attributes (HAS & HASA)
     */
    @XmlAnyElement(lax = true)
    List<Object> getJaxbAttributes()
    {
        return attributes;
    }
    
    /**
     * The method returns all elements (has, hasa as well as inherited from parent elements)
     * Usually there is no need to use this method because datatype generated classes
     * suppose to have apropriate getters and setters for their data.
     * It could be used in case where there is no datatype tree structure available
     * (for example the got an unknown datatype element).
     * @return the list of all elements contained by thes object.
     */
    public List<AbstractMobyObject> getAttributes()
    {
        ArrayList<AbstractMobyObject> list = new ArrayList<AbstractMobyObject>();
        
        for (Object object : attributes)
        {
            AbstractMobyObject mobyObject;
            
            if (object instanceof Element)
            {
                Element element = (Element)object;

                mobyObject = AnyMobyObject.unmarshal(element);
                
                if (mobyObject == null)
                {
                    continue;
                }
            }
            else if (object instanceof CrossReference)
            {
                continue; // ignore CrossReferences
            }
            else
            {
                mobyObject = (AbstractMobyObject)object;
            }
            
            list.add(mobyObject);
        }
        
        return list;
    }
    
    /**
     * The method adds an attribute to the object
     * 
     * @param articleName the name of the attribute to be added.
     * @param object the attribute to be added (any moby object)
     */
    public boolean putAttribute(String articleName, AbstractMobyObject object)
    {
        if (object == null)
        {
            removeAttribute(articleName);
        }
        else
        {
            object.setArticleName(articleName);
            
            if (object instanceof AnyMobyObject)
            {
                AnyMobyObject anyMobyObject = (AnyMobyObject)object;
            
                Element element = anyMobyObject.marshal();
                
                if (element == null)
                {
                    return false;
                }
                
                attributes.add(element);
            }
            else
            {
                attributes.add(object);
            }
        }
        
        return true;
    }

    /**
     * Remove attribute from a list of attributes
     * 
     * @param articleName the name of an attribute to be removed
     */
    public void removeAttribute(String articleName)
    {
        Iterator iter = attributes.iterator();

        while(iter.hasNext())
        {
            AbstractMobyObject mobyObject;

            Object object = iter.next();
            if (object instanceof Element)
            {
                Element element = (Element)object;

                mobyObject = AnyMobyObject.unmarshal(element);

                if (mobyObject == null)
                {
                    continue;
                }
            }
            else
            {
                mobyObject = (AbstractMobyObject)object;
            }

            if (articleName.equals(mobyObject.articleName))
            {
                iter.remove();
            }
        }
    }

    /**
     * Return CrossReference block if present
     *
     * @return CrossReference object or null if there is no one.
     */
    public CrossReference getCrossReference()
    {
        for (Object object : attributes)
        {
            if (object instanceof CrossReference)
            {
                return (CrossReference)object;
            }
        }

        return null;
    }

    public void setCrossReference(CrossReference ref)
    {
        removeCrossReference();

        attributes.add(ref);
    }

    public void removeCrossReference()
    {
        Iterator iter = attributes.iterator();

        while(iter.hasNext())
        {
            Object object = iter.next();
            if (object instanceof CrossReference)
            {
                iter.remove();
            }
        }
    }

    /**
     * Generic method to retrieve the attribute by its name.
     * Method makes autocast to appropriate object, so could possible generate 
     * a ClassCastException (but taking into account that method is protected 
     * should never happen). It saves some typing in derived classes where the 
     * usage is as simple as MobyInteger i = getAttribute("score");
     * 
     * @param articleName the name of retrieved attribute
     * @return the appropriate attribute object
     */
    protected <T extends AbstractMobyObject> T getAttribute(String articleName)
    {
        List<AbstractMobyObject> list = getAttributes();
        
        for (AbstractMobyObject object : list)
        {
            if (articleName.equals(object.getArticleName()))
            {
                return (T)object;
            }
        }
   
        return null;
    }
    
    protected <T extends AbstractMobyObject> List<T> getAttributes(String articleName)
    {
        ArrayList<T> l = new ArrayList<T>();
        
        List<AbstractMobyObject> list = getAttributes();
        for (AbstractMobyObject object : list)
        {
            if (articleName.equals(object.getArticleName()))
            {
                l.add((T)object);
            }
        }
   
        return l;
    }
    
    @Override
    public String toString()
    {
        StringBuilder sb = new StringBuilder();
        
        String articleName = getArticleName();
        sb.append(articleName != null ? articleName : "");
        sb.append('\n');

        List<AbstractMobyObject> list = getAttributes();
        for (AbstractMobyObject object : list)
        {
            sb.append("--> ");
            sb.append(object);
            sb.append('\n');
        }
        
        return sb.toString();
    }
}
