/*
 * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
 *
 * The contents of this file are subject to the CCM Public
 * License (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.redhat.com/licenses/ccmpl.html.
 *
 * Software distributed under the License is distributed on an
 * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
 * or implied. See the License for the specific language
 * governing rights and limitations under the License.
 *
 */
package com.arsdigita.formbuilder;


// This domain object can create a Bebop FormSection
import com.arsdigita.bebop.FormSection;

// Manages associations with child components
import com.arsdigita.formbuilder.util.PersistentContainerHelper;

// For the FormSection constructor

// What we add to the form section
import com.arsdigita.bebop.Component;
import com.arsdigita.bebop.Container;

// For attributes of the form

// Child components are returned in a collection
import java.util.Collection;
import java.util.Iterator;
import java.util.ArrayList;

// Thrown if the underlying DataObject with given id cannot be found
import com.arsdigita.domain.DataObjectNotFoundException;

// We are persisting these via their class name

// For instantiating objects

// All ACSObjects have a unique id
import java.math.BigDecimal;
import com.arsdigita.persistence.OID;
import com.arsdigita.persistence.DataObject;
import com.arsdigita.persistence.metadata.ObjectType;

// ACS 5 uses Log4J for logging
import org.apache.log4j.Logger;
import com.arsdigita.domain.DomainObjectFactory;
import com.arsdigita.persistence.DataAssociationCursor;
import com.arsdigita.persistence.DataAssociation;


/**
 * This domain object manages persistence of a Bebop FormSection.
 *
 * @author Peter Marklund
 * @version $Id: //core-platform/dev/src/com/arsdigita/formbuilder/PersistentFormSection.java#14 $
 *
 */
public class PersistentFormSection extends PersistentComponent
    implements PersistentContainer, CompoundComponent {

    public static final String versionId = "$Id: //core-platform/dev/src/com/arsdigita/formbuilder/PersistentFormSection.java#14 $ by $Author: dennis $, $DateTime: 2004/04/07 16:07:11 $";

    private static final Logger s_log =
        Logger.getLogger(PersistentFormSection.class.getName());

    // We delegate the association with the child components to this
    // object
    private PersistentContainerHelper m_container =
        new PersistentContainerHelper(this);

    private Container m_form_container;

    // Executes before each component is aded to the form section
    ComponentAddObserver m_componentAddObserver;

    /**
     * BASE_DATA_OBJECT_TYPE represents the full name of the
     * underlying DataObject of this class.
     */
    public static final String BASE_DATA_OBJECT_TYPE =
        "com.arsdigita.formbuilder.FormSection";

    ArrayList m_listeners;

    // *** Constructors -------------

    /**
     * Constructor that creates a new form domain object that
     * can be saved to the database later on.
     */
    public PersistentFormSection() {

        this(BASE_DATA_OBJECT_TYPE);
    }

    /**
     * To be used by sub classes only.
     */
    public PersistentFormSection(String objectType) {

        super(objectType);
    }

    public PersistentFormSection(ObjectType type) {
        super(type);
    }

    public PersistentFormSection(DataObject obj) {
        super(obj);
    }

    /**
     * Constructor that retrieves an existing form domain object
     * from the database.
     *
     * @param id The object id of the form domain object to retrieve
     */
    public PersistentFormSection(BigDecimal id)
        throws DataObjectNotFoundException {

        this(new OID(BASE_DATA_OBJECT_TYPE, id));
    }

    /**
     * For sub classes to use.
     */
    protected PersistentFormSection(OID oid)
        throws DataObjectNotFoundException {

        super(oid);
    }


    // *** Public Methods

    /**
     * I am overriding this method to delete all associations with
     * components before the form is deleted. The components themselves are not deleted.
     */
    public void delete() {

        m_container.clearComponents();

        super.delete();
    }

    /**
     * Add a component after the already added components (in the last position).
     * If this domain object has not been saved (with save()) before you invoke this method
     * it will be saved by this method.
     */
    public void addComponent(PersistentComponentFactory componentFactory) {

        // Delegate to the Container helper
        m_container.addComponent(componentFactory);
    }

    /**
     * Add a child component of the form.
     * If this domain object has not been saved (with save()) before you invoke this method
     * it will be saved by this method.
     *
     * @param position The count of this component starting with 1 (i.e. if it's
     *        the third component to be added to the form this
     *        value would be 3)
     */
    public void addComponent(PersistentComponentFactory componentFactory,
                             int position) {

        // Delegate to the Container helper
        m_container.addComponent(componentFactory, position);
    }

    /**
     * Remove a component from the form.
     * If this domain object has not been saved (with save()) before you invoke this method
     * it will be saved by this method.
     */
    public void removeComponent(PersistentComponentFactory componentFactory) {

        // Delegate to the Container helper
        m_container.removeComponent(componentFactory);
    }

    /**
     * Move component to new position.
     *
     * @param toPosition The position to move the component to. Positions start with 1.
     */
    public void moveComponent(PersistentComponentFactory componentFactory,
                              int toPosition) {

        // Delegate to the Container helper
        m_container.moveComponent(componentFactory, toPosition);
    }

    /**
     * Delete all component associations from this container
     */
    public void clearComponents() {
        m_container.clearComponents();
    }

    /**
     * Create a Bebop FormSection using the persistent information in this form section domain object.
     *
     */
    public Component createComponent() {

        FormSection formSection;
        if (m_form_container == null) {
            formSection = new FormSection();
        } else {
            formSection = new FormSection(m_form_container);
        }

        // Add the process listener
        addProcessListeners(formSection);

        // Add the components
        addComponents(formSection);

        return formSection;
    }

    /**
     * Return all children components represented by their PersistentComponentFactory
     * objects.
     */
    public Collection getComponents() {

        // Delegate to the Container helper
        return m_container.getComponents();
    }

    // *** Internal Helper Methods

    /**
     * Instantiate a process listener with the persisted class name and add
     * it to the Bebop Form.
     */
    protected void addProcessListeners(FormSection formSection) {
        Iterator listeners = getProcessListeners();

        while(listeners.hasNext()) {
            PersistentProcessListener l = (PersistentProcessListener)listeners.next();

            formSection.addProcessListener(l.createProcessListener());
        }
    }

    /**
     * Add all child components to the FormSection. Each persisted component has an associated
     * PersistentComponentFactory that can be resurrected with the id of the component.
     * The Bebop components are created with the createComponent() method of their factory.
     */
    protected void addComponents(FormSection formSection) {

        Iterator componentIter = m_container.getComponents().iterator();

        // Loop over the child components
        int componentCounter = 1;
        while (componentIter.hasNext()) {
            // Fetch the next component factory from the list
            PersistentComponentFactory factory =
                (PersistentComponentFactory)componentIter.next();

            // Add the component to the form with the factory

            // Fire the component add observer
            if (m_componentAddObserver != null) {
                m_componentAddObserver.beforeAddingComponent(formSection, factory, componentCounter);
            }

            Component component = factory.createComponent();

            // Give the observer an opportunity to modify the component that we add to the form
            if (m_componentAddObserver != null) {
                m_componentAddObserver.addingComponent(factory, componentCounter, component);
            }

            formSection.add(component);

            // Fire the component add observer
            if (m_componentAddObserver != null) {
                m_componentAddObserver.afterAddingComponent(formSection, factory, componentCounter);
            }

            ++componentCounter;
        }
    }

    public void setComponentAddObserver(ComponentAddObserver observer) {

        m_componentAddObserver = observer;
    }

    private void retrieveListeners() {
        m_listeners = new ArrayList();
        
        DataAssociation listeners = (DataAssociation)get("listeners");
        DataAssociationCursor cursor = listeners.cursor();
        cursor.addOrder("link.position asc");
        
        while (cursor.next()) {
            PersistentProcessListener l = (PersistentProcessListener)DomainObjectFactory.newInstance(cursor.getDataObject());
            m_listeners.add(l);
        }
    }

    // *** Attribute Methods

    public void addProcessListener(PersistentProcessListener listener) {
        if (listener.isNew()) {
            listener.save();
        }

        if (m_listeners == null)
            retrieveListeners();

        int position = m_listeners.size();
        
        DataObject link = add("listeners", listener);
        link.set("position", new BigDecimal(position));
        m_listeners.add(listener);        
    }

    public void removeProcessListener(PersistentProcessListener listener) {
        if (m_listeners == null)
            retrieveListeners();

        int position = m_listeners.indexOf(listener);
        
        remove("listeners", listener);
        m_listeners.remove(position);
        
        DataAssociation listeners = (DataAssociation)get("listeners");
        DataAssociationCursor cursor = listeners.cursor();
        cursor.addOrder("link.position asc");
        
        while (cursor.next()) {
            DataObject link = cursor.getLink();
            BigDecimal current = (BigDecimal)link.get("position");
            if (current.intValue() > position) {
                link.set("position", new BigDecimal(current.intValue() - 1));
            }
        }
    }

    public void clearProcessListeners() {
        if (m_listeners == null)
            retrieveListeners();

        clear("listeners");
        
        Iterator i = m_listeners.iterator();
        while (i.hasNext()) {
            PersistentProcessListener l = (PersistentProcessListener)i.next();
            l.delete();
        }
        m_listeners = null;
    }

    public Iterator getProcessListeners() {
        if (m_listeners == null)
            retrieveListeners();

        return m_listeners.iterator();
    }

    /**
     * Set the container that the persistent form will use
     * (for example a ColumnPanel with a certain number of columns).
     * This property is not yet persisted.
     */
    public void setFormContainer(Container container) {
        m_form_container = container;
    }

    /**
     * Get the container that the persistent form will use
     * (for example a ColumnPanel with a certain number of columns).
     * This property is not yet persisted.
     */
    public Container getFormContainer() {
        return m_form_container;
    }
}
