/*

   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.batik.anim.timing;

import java.util.LinkedList;
import java.util.List;

/**
 * An abstract base class for time container elements.
 *
 * @author <a href="mailto:cam%40mcc%2eid%2eau">Cameron McCormack</a>
 * @version $Id$
 */
public abstract class TimeContainer extends TimedElement {

    /**
     * The child {@link TimedElement}s of this time container.
     */
    protected List children = new LinkedList();

    /**
     * Adds a {@link TimedElement} to this container.
     */
    public void addChild(TimedElement e) {
        if (e == this) {
            throw new IllegalArgumentException("recursive datastructure not allowed here!");
        }
        children.add(e);
        e.parent = this;
        setRoot(e, root);
        root.fireElementAdded(e);
        root.currentIntervalWillUpdate();
    }

    /**
     * Recursively sets the {@link TimedDocumentRoot} of the given
     * {@link TimedElement} and any of its descendants.
     */
    protected void setRoot(TimedElement e, TimedDocumentRoot root) {
        e.root = root;
        if (e instanceof TimeContainer) {
            TimeContainer c = (TimeContainer) e;
            for (Object aChildren : c.children) {
                TimedElement te = (TimedElement) aChildren;
                setRoot(te, root);
            }
        }
    }

    /**
     * Removes a {@link TimedElement} from this container.
     */
    public void removeChild(TimedElement e) {
        children.remove(e);
        e.parent = null;
        setRoot(e, null);
        root.fireElementRemoved(e);
        root.currentIntervalWillUpdate();
    }

    /**
     * Returns an array of the children of this container.
     */
    public TimedElement[] getChildren() {
        return (TimedElement[]) children.toArray(new TimedElement[children.size()]);
    }

    /**
     * Calculates the local simple time.  Currently the hyperlinking parameter
     * is ignored, so DOM timing events are fired during hyperlinking seeks.
     * If we were following SMIL 2.1 rather than SMIL Animation, then these
     * events would have to be surpressed.
     *
     * @return the number of seconds until this element becomes active again
     *         if it currently is not, {@link Float#POSITIVE_INFINITY} if this
     *         element will become active at some undetermined point in the
     *         future (because of unresolved begin times, for example) or
     *         will never become active again, or <code>0f</code> if the
     *         element is currently active.
     */
    protected float sampleAt(float parentSimpleTime, boolean hyperlinking) {
        super.sampleAt(parentSimpleTime, hyperlinking);
        // Maybe check the return value of the previous statement.
        return sampleChildren(parentSimpleTime, hyperlinking);
    }

    /**
     * Samples all the child timed elements.
     */
    protected float sampleChildren(float parentSimpleTime,
                                   boolean hyperlinking) {
        float mint = Float.POSITIVE_INFINITY;
        for (Object aChildren : children) {
            TimedElement e = (TimedElement) aChildren;
            float t = e.sampleAt(parentSimpleTime, hyperlinking);
            if (t < mint) {
                mint = t;
            }
        }
        return mint;
    }

    /**
     * Resets this element.
     */
    protected void reset(boolean clearCurrentBegin) {
        super.reset(clearCurrentBegin);
        for (Object aChildren : children) {
            TimedElement e = (TimedElement) aChildren;
            e.reset(clearCurrentBegin);
        }
    }

    /**
     * Returns whether this timed element is for a constant animation (i.e., a
     * 'set' animation.
     */
    protected boolean isConstantAnimation() {
        return false;
    }

    /**
     * Returns the default begin time for the given child
     * timed element.
     */
    public abstract float getDefaultBegin(TimedElement child);
}
