/**
 * 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.central.gui.model;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.swing.SwingUtilities;
import org.inb.biomoby.central.model.AbstractModel;
import org.inb.biomoby.central.model.ModelListener;
import org.inb.biomoby.shared.registry.ObjectType;
import org.inb.biomoby.shared.registry.Relationship;
import org.inb.biomoby.shared.registry.Relationship.RELATIONSHIP_TYPE;

/**
 * @author Dmitry Repchevsky
 */

public class MobyObjectPropertiesTableModel extends MobyPropertiesTableModel
        implements ModelListener<ObjectType>
{
    private ObjectType objectType;
    private AbstractModel<ObjectType> model;

    public MobyObjectPropertiesTableModel(AbstractModel<ObjectType> model)
    {
        this.model = model;

        model.addListener(this);
    }

    public AbstractModel<ObjectType> getModel()
    {
        return model;
    }

    public void setClazz(ObjectType objectType)
    {
        clear();

        this.objectType = objectType.clone();

        setProperties(objectType);
    }

    public ObjectType getClazz()
    {
        return objectType;
    }

    @Override
    public void clear()
    {
        objectType = null;
        super.clear();
    }

    /**
     * Method to add a property into the table model
     * @param property
     * @return
     */
    @Override
    public Relationship<ObjectType> addProperty(ObjectType property)
    {
        Relationship<ObjectType> relationship = super.addProperty(property);

        if (objectType != null)
        {
            addProperty(property, relationship.getRelationshipType());
        }

        return relationship;
    }

    @Override
    public void addProperties(ObjectType clazz)
    {
        super.addProperties(clazz);

        if (objectType != null && objectType.equals(clazz))
        {
            List<Relationship<ObjectType>> relationships = clazz.getRelationships();

            for (Relationship<ObjectType> relationship : relationships)
            {
                Relationship.RELATIONSHIP_TYPE relationshipType = relationship.getRelationshipType();

                if (relationshipType == Relationship.RELATIONSHIP_TYPE.HAS ||
                    relationshipType == Relationship.RELATIONSHIP_TYPE.HASA)
                {
                    for (ObjectType property : relationship.getEntityTypeList())
                    {
                       addProperty(property, relationshipType);
                    }
                }
            }
        }
    }

    private void addProperty(ObjectType property, Relationship.RELATIONSHIP_TYPE relationshipType)
    {
        Relationship<ObjectType> relationship = objectType.getRelationship(relationshipType);

        if (relationship == null)
        { // we didn't find relationship type, so create it
            relationship = new Relationship<ObjectType>();
            relationship.setRelationshipType(relationshipType);
            objectType.getRelationships().add(relationship);
        }

        relationship.addEntityType(property);
    }

    /**
     * Method allows to edit only those properties that belong to the editing object.
     * This means that all the properties in a table model that, possibly, inhereted from
     * a parent object are not editable.
     */
    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex)
    {
        if (objectType != null)
        {
            Relationship<ObjectType> rowRelationship = super.get(rowIndex);
            ObjectType checkedProperty = rowRelationship.getEntityTypeList().get(0);
            String propertyName = checkedProperty.getArticleName();

            if (propertyName == null || propertyName.isEmpty())
            { // empty propetry name is always editable...
                return true;
            }

            List<Relationship<ObjectType>> relationships = objectType.getRelationships();
            for (Relationship<ObjectType> relationship : relationships)
            {
                if (relationship.getRelationshipType() == rowRelationship.getRelationshipType())
                {
                    for (ObjectType property : relationship.getEntityTypeList())
                    {
                        if (propertyName.equals(property.getArticleName()))
                        {
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        return true;
    }

    /**
     * 
     */
    @Override
    public void setValueAt(Object value, int rowIndex, int columnIndex)
    {
        if (objectType == null)
        {
            super.setValueAt(value, rowIndex, columnIndex);
        }
        else
        { // modify objectType definition
            Relationship<ObjectType> oldRelationship = get(rowIndex);
            ObjectType oldProperty = oldRelationship.getEntityTypeList().get(0);

            List<Relationship<ObjectType>> relationships = objectType.getRelationships();

            for (Relationship<ObjectType> relationship : relationships)
            {
                Relationship.RELATIONSHIP_TYPE relationshipType = relationship.getRelationshipType();

                if (relationshipType == Relationship.RELATIONSHIP_TYPE.HAS ||
                    relationshipType == Relationship.RELATIONSHIP_TYPE.HASA)
                {
                    Iterator<ObjectType> iterator = relationship.getEntityTypeList().iterator();
                    while (iterator.hasNext())
                    {
                        ObjectType property = iterator.next();

                        if (property.getName().equals(oldProperty.getName()) &&
                            property.getArticleName().equals(oldProperty.getArticleName()))
                        { // we found a property in the editing class

                            switch(columnIndex)
                            {
                                case 0: {
                                            ObjectType newObjectType = (ObjectType)value;
                                            property.setName(newObjectType.getName());
                                            break;
                                        }
                                case 1: {
                                            ObjectType newObjectType = (ObjectType)value;
                                            property.setArticleName(newObjectType.getArticleName());
                                            break;
                                        }
                                case 2: {
                                            RELATIONSHIP_TYPE newRelationshipType = (RELATIONSHIP_TYPE)value;

                                            if (relationshipType == newRelationshipType)
                                            { // no need to update a model
                                                return;
                                            }
                                            else
                                            { // the relationship type has been changed - remove the old one
                                                iterator.remove();
                                                addProperty(oldProperty, newRelationshipType);
                                            }
                                        }
                            }

                            model.updateElement(objectType, objectType);
                            return;
                        }
                    }
                }
            }

            if (columnIndex == 2)
            {
                Relationship<ObjectType> relationship = null;

                for (Relationship<ObjectType> r : relationships)
                {
                    Relationship.RELATIONSHIP_TYPE relationshipType = r.getRelationshipType();

                    if (relationshipType == value)
                    { // add the property
                        relationship = r;
                        break;
                    }
                }

                if (relationship == null)
                { // we didn't find relationship type, so create it
                    relationship = new Relationship<ObjectType>();
                    relationship.setRelationshipType((RELATIONSHIP_TYPE)value);
                    relationships.add(relationship);
                }

                relationship.addEntityType(oldProperty);
            }

            model.updateElement(objectType, objectType);
        }
    }

    @Override
    public Relationship<ObjectType> remove(int row)
    {
        Relationship<ObjectType> removedRelationship = super.remove(row);

        if (objectType != null)
        {
            ObjectType removedProperty = removedRelationship.getEntityTypeList().get(0); // we store only one ObjectType per relationship in the model
            String removedPropertyName = removedProperty.getArticleName();

            if (removedPropertyName != null)
            {
                List<Relationship<ObjectType>> relationships = objectType.getRelationships();

                label:
                for (Relationship<ObjectType> relationship : relationships)
                {
                    if (relationship.getRelationshipType() == removedRelationship.getRelationshipType())
                    {
                        Iterator<ObjectType> iterator = relationship.getEntityTypeList().iterator();

                        while(iterator.hasNext())
                        {
                            ObjectType property = iterator.next();

                            // remove property if articleNames match
                            if (property.getArticleName().equals(removedPropertyName))
                            {
                                iterator.remove();

                                if (removedPropertyName.length() > 0)
                                {
                                   model.updateElement(objectType, objectType);
                                }
                                break label;
                            }
                        }
                    }
                }
            }
        }
        
        return removedRelationship;
    }

    public void modelCleared()
    {
        clear();
    }

    public void modelObjectInserted(ObjectType objectType) {}

    public void modelObjectRemoved(final ObjectType objectType)
    {
        if (SwingUtilities.isEventDispatchThread())
        {
            checkPropertyRemoved(objectType);
        }
        else
        {
            SwingUtilities.invokeLater(new Runnable()
            {
                public void run()
                {
                    checkPropertyRemoved(objectType);
                }
            });
        }
    }

    public void modelObjectChanged(final ObjectType objectType1, final ObjectType objectType2)
    {
        if (SwingUtilities.isEventDispatchThread())
        {
            checkPropertyChange(objectType1, objectType2);
        }
        else
        {
            SwingUtilities.invokeLater(new Runnable()
            {
                public void run()
                {
                    checkPropertyChange(objectType1, objectType2);
                }
            });
        }
    }

    private void checkPropertyRemoved(ObjectType objectType)
    {
        for (int i = getRowCount() - 1; i >= 0; i--)
        {
            Relationship<ObjectType> relationship = get(i);

            ObjectType property = relationship.getEntityTypeList().get(0);

            if (objectType.equals(property))
            {
                remove(i);
            }
        }
    }

    private void checkPropertyChange(ObjectType objectType1, ObjectType objectType2)
    {
        if (objectType != null)
        {
            if (objectType1.equals(objectType))
            { // Object itself has been changed
                super.clear();

                objectType.setName(objectType2.getName());
                objectType.setAuthURI(objectType2.getAuthURI());
                objectType.setTitle(objectType2.getTitle());
                objectType.setLsid(objectType2.getLsid());
                objectType.setContactEmail(objectType2.getContactEmail());
                objectType.setDescription(objectType2.getDescription());

                objectType.getRelationships().clear();

                List<Relationship<ObjectType>> relationships = objectType2.getRelationships();
                for (Relationship<ObjectType> relationship : relationships)
                {
                    objectType.addRelationship(relationship.clone());
                }

                setProperties(objectType);
                return;
            }
        }

        for (int i = 0, n = getRowCount(); i < n; i++)
        {
            Relationship<ObjectType> relationship = get(i);

            List<ObjectType> properties = relationship.getEntityTypeList();
            ObjectType property = properties.get(0);

            if (objectType1.equals(property))
            {
                ObjectType clone = objectType2.clone();
                clone.setArticleName(property.getArticleName());
                properties.set(0, clone);

                fireTableRowsUpdated(i, i);
            }
        }
    }
    
    /**
     * Method inserts all the properties from a given type including all the ancestors' properties.
     * Properties are inserted in reversed order (parents first)
     * 
     * @param objectType
     */
    private void setProperties(ObjectType objectType)
    {
        List<ObjectType> ancestors = new ArrayList<ObjectType>();

        ObjectType parent = objectType;
        while((parent = getParent(parent)) != null)
        {
            ancestors.add(parent);
        }

        for (int i = ancestors.size() - 1; i >= 0; i--)
        {
            addProperties(ancestors.get(i));
        }

        super.addProperties(objectType);
    }

    private ObjectType getParent(ObjectType child)
    {
        Relationship<ObjectType> relationship = child.getRelationship(RELATIONSHIP_TYPE.ISA);

        if (relationship != null)
        {
            List<ObjectType> parents = relationship.getEntityTypeList();

            if (!parents.isEmpty())
            {
                ObjectType parent = parents.get(0);

                return model.getElement(parent);
            }
        }

        return null;
    }
}
