/*
 * File:     NoTimeGapWithinParent.java
 * Project:  MPI Linguistic Application
 * Date:     03 April 2006
 *
 * Copyright (C) 2001-2006  Max Planck Institute for Psycholinguistics
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package mpi.eudico.server.corpora.clomimpl.type;

import mpi.eudico.server.corpora.clom.Annotation;
import mpi.eudico.server.corpora.clom.Tier;
import mpi.eudico.server.corpora.clom.TimeOrder;
import mpi.eudico.server.corpora.clom.TimeSlot;

import mpi.eudico.server.corpora.clomimpl.abstr.AlignableAnnotation;
import mpi.eudico.server.corpora.clomimpl.abstr.SVGAlignableAnnotation;
import mpi.eudico.server.corpora.clomimpl.abstr.TierImpl;
import mpi.eudico.server.corpora.clomimpl.abstr.TimeSlotImpl;
import mpi.eudico.server.corpora.clomimpl.abstr.TranscriptionImpl;

import java.util.Iterator;
import java.util.Vector;


/**
 * DOCUMENT ME! $Id: NoTimeGapWithinParent.java,v 1.9 2005/01/18 12:22:55
 * hasloe Exp $
 *
 * @author $Author: hasloe $
 * @version $Revision: 1.15 $
 */
public class NoTimeGapWithinParent extends ConstraintImpl {
    /**
     * Creates a new NoTimeGapWithinParent instance
     */
    public NoTimeGapWithinParent() {
        super();
    }

    /**
     * DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public int getStereoType() {
        return Constraint.NO_GAP_WITHIN_PARENT;
    }

    /**
     * Creates new or finds existing TimeSlots for a new Annotation on a Tier.
     * New TimeSlots will be added to the Transcription's TimeOrder, existing
     * TimeSlots will be adjusted if needed. The Tier object should then add
     * the Annotation to it's list of Annotations,  correct overlaps and mark
     * Annotations for deletion, if necessary.  Note HS aug 2005: the
     * mechanism applied here sometimes conflicts with the  the graph based
     * mechanism applied in TierImpl to solve overlaps etc. This was the cause
     * of the faulty behavior (bug) when an Annotation was  created on a
     * TimeSubdivision Tier, with a begin time equal to that of the  parent
     * Annotation's begin time while there already were one or more child
     * Annotations on that Tier, on that position (the begin time of parent
     * and  new child would be equal to the end time). Existing annotations
     * are marked for deletion based on their begin and end time  (begin >=
     * end). To acclompish this time values of TimeSlots are altered.  But
     * since the new and the existing annotation share the same begin time
     * slot  (the begin slot of the parent), this can not be applied in such
     * case.  Disconnecting the existing annotation from the graph would make
     * the graph  based iteration over a chain of depending annotations fail.
     * To (temporarely) solve this an exisitng annotation is now sometimes
     * marked for deletion here, which looks odd. This whole mechanism should
     * maybe be redesigned.
     *
     * @param begin time for new begin time slot
     * @param end time for new end time slot
     * @param forTier the tier to which an annotation is going to be created
     *
     * @return a Vector containing the
     */
    public Vector getTimeSlotsForNewAnnotation(long begin, long end,
        Tier forTier) {
        Vector slots = new Vector();

        TimeOrder timeOrder = ((TranscriptionImpl) (forTier.getParent())).getTimeOrder();

        boolean insertAtParentBegin = false;
        TierImpl parentTier = (TierImpl) ((TierImpl) forTier).getParentTier();
        AlignableAnnotation parentAnn = (AlignableAnnotation) (parentTier.getAnnotationAtTime(begin,
                true));

        if (parentAnn == null) {
            return slots;
        }

        if (parentTier != null) {
            if ((parentAnn != null) &&
                    (parentAnn.getBegin().getTime() == begin)) {
                insertAtParentBegin = true;
            }
        }

        // get next existing TimeSlot on tier
        AlignableAnnotation currAnn = (AlignableAnnotation) ((TierImpl) forTier).getAnnotationAtTime(begin,
                true);

        TimeSlot bts = null;
        TimeSlot ets = null;

        if (currAnn == null) { // no enclosed annotation for this parent yet

            // connect to extremes of parent annotation
            bts = parentAnn.getBegin();
            ets = parentAnn.getEnd();
        } else if (!insertAtParentBegin) {
            bts = new TimeSlotImpl(begin, timeOrder);

            //	timeOrder.insertTimeSlot(bts);
            ets = currAnn.getEnd();

            if (ets != parentAnn.getEnd()) {
                ets.setTime(end);
            }

            timeOrder.insertTimeSlot(bts, currAnn.getBegin(), currAnn.getEnd());

            // correct end of current annotation
            //     currAnn.setEnd(bts);               
            Vector endingAtTs = ((TranscriptionImpl) (forTier.getParent())).getAnnotsEndingAtTimeSlot(currAnn.getEnd(),
                    forTier, true);

            if (endingAtTs.contains(parentAnn)) {
                endingAtTs.remove(parentAnn);
            }

            //HS jan 2005 only update annotations on the same tier or on depending tiers
            Vector depTiers = ((TierImpl) forTier).getDependentTiers();

            for (int i = 0; i < endingAtTs.size(); i++) {
                AlignableAnnotation nextAA = (AlignableAnnotation) endingAtTs.elementAt(i);

                if ((nextAA.getTier() == forTier) ||
                        depTiers.contains(nextAA.getTier())) {
                    nextAA.setEnd(bts);
                }
            }
        } else {
            // insert at parent begin, see javadoc comments
            bts = currAnn.getBegin();
            ets = currAnn.getEnd();

            if (end == parentAnn.getEnd().getTime()) {
                // removes any number of existing child annotations
                ets.setTime(end);
                currAnn.markDeleted(true);
            } else {
                AlignableAnnotation currEndAnn = null;
                currEndAnn = (AlignableAnnotation) (((TierImpl) forTier).getAnnotationAtTime(end,
                        true));

                TimeSlotImpl nextEndTs = new TimeSlotImpl(end, timeOrder);

                if ((ets == parentAnn.getEnd()) || (currAnn == currEndAnn)) {
                    // only one child annotation
                    // insert new                	
                    timeOrder.insertTimeSlot(nextEndTs, bts, ets);

                    Vector beginningAtTs = ((TranscriptionImpl) (forTier.getParent())).getAnnotsBeginningAtTimeSlot(bts,
                            forTier, true);

                    if (beginningAtTs.contains(parentAnn)) {
                        beginningAtTs.remove(parentAnn);
                    }

                    // HS jan 2005 only update annotations on the same tier or on depending tiers
                    Vector depTiers = ((TierImpl) forTier).getDependentTiers();

                    for (int i = 0; i < beginningAtTs.size(); i++) {
                        AlignableAnnotation nextAA = (AlignableAnnotation) beginningAtTs.elementAt(i);

                        if ((nextAA.getTier() == forTier) ||
                                depTiers.contains(nextAA.getTier())) {
                            nextAA.setBegin(nextEndTs);
                        }
                    }

                    ets = nextEndTs;
                } else {
                    // more than one child annotations
                    ets.setTime(end);

                    Vector endingAtTs = ((TranscriptionImpl) (forTier.getParent())).getAnnotsEndingAtTimeSlot(ets,
                            forTier, true);

                    if (endingAtTs.contains(parentAnn)) {
                        endingAtTs.remove(parentAnn);
                    }

                    // HS jan 2005 only update annotations on the same tier or on depending tiers
                    Vector depTiers = ((TierImpl) forTier).getDependentTiers();

                    for (int i = 0; i < endingAtTs.size(); i++) {
                        AlignableAnnotation nextAA = (AlignableAnnotation) endingAtTs.elementAt(i);

                        if ((nextAA.getTier() == forTier) ||
                                depTiers.contains(nextAA.getTier())) {
                            nextAA.setBegin(bts);
                        }
                    }

                    currAnn.markDeleted(true); // fix for bug see next line 
                }

                //currAnn.markDeleted(true); //bug introduced in 2.5.1
            }
        }

        slots.add(bts);
        slots.add(ets);

        // System.out.println("end of getTimeSlotsForNewAnnotation, returning " + slots.size() + " slots");
        return slots;
    }

    /**
     * DOCUMENT ME!
     *
     * @param theTier DOCUMENT ME!
     */
    public void enforceOnWholeTier(Tier theTier) {
        // close all gaps within parents by reconnecting time slots
        // iterate over parent's annotations.
        // for each parent, find enclosed annotations
        // iterate over enclosed annotations
        // for each, connect begin timeslot to either parent's begin or previous annots end
        // connect end of last to parent's end
        Tier parentTier = ((TierImpl) theTier).getParentTier();

        if (parentTier != null) {
            Vector parentAnnots = null;

            parentAnnots = ((TierImpl) parentTier).getAnnotations();

            Iterator parentIter = parentAnnots.iterator();

            while (parentIter.hasNext()) {
                AlignableAnnotation parent = (AlignableAnnotation) parentIter.next();

                //Vector enclosedAnnots = ((TierImpl) theTier).getOverlappingAnnotations(
                //	parent.getBegin().getTime(), parent.getEnd().getTime(), true);
                Vector enclosedAnnots = ((TierImpl) theTier).getOverlappingAnnotations(parent.getBegin(),
                        parent.getEnd());

                Iterator annIter = enclosedAnnots.iterator();
                AlignableAnnotation previousAnn = null;

                while (annIter.hasNext()) {
                    AlignableAnnotation a = (AlignableAnnotation) annIter.next();

                    if (previousAnn == null) { // connect to parent's begin
                        a.setBegin(parent.getBegin());
                    } else { // connect end of previous to ann begin
                        previousAnn.setEnd(a.getBegin());
                    }

                    previousAnn = a;
                }

                // connect last ann to end of parent
                if (previousAnn != null) {
                    previousAnn.setEnd(parent.getEnd());
                }
            }

            TimeOrder timeOrder = ((TranscriptionImpl) (theTier.getParent())).getTimeOrder();

            timeOrder.pruneTimeSlots();
        }
    }

    /**
     * DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public boolean supportsInsertion() {
        return true;
    }

    /**
     * DOCUMENT ME!
     *
     * @param beforeAnn DOCUMENT ME!
     * @param theTier DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public Annotation insertBefore(Annotation beforeAnn, Tier theTier) {
        AlignableAnnotation a = null;
        AlignableAnnotation beforeA = (AlignableAnnotation) beforeAnn;

        TimeOrder timeOrder = ((TranscriptionImpl) (theTier.getParent())).getTimeOrder();

        TimeSlot newTs = new TimeSlotImpl(timeOrder);
        timeOrder.insertTimeSlot(newTs, beforeA.getBegin(), beforeA.getEnd());

        // check whether or not the tier supports graphical references
        if (((TierImpl) theTier).getLinguisticType().hasGraphicReferences()) {
            a = new SVGAlignableAnnotation(beforeA.getBegin(), newTs, theTier,
                    null);
        } else {
            a = new AlignableAnnotation(beforeA.getBegin(), newTs, theTier);
        }

        //    beforeA.setBegin(newTs);
        Annotation parentAnn = beforeAnn.getParentAnnotation();

        Vector beginningAtTs = ((TranscriptionImpl) (theTier.getParent())).getAnnotsBeginningAtTimeSlot(beforeA.getBegin(),
                theTier, true);

        if (beginningAtTs.contains(parentAnn)) {
            beginningAtTs.remove(parentAnn);
        }

        // HS jan 2005 only update annotations on the same tier or on depending tiers
        Vector depTiers = ((TierImpl) theTier).getDependentTiers();

        for (int i = 0; i < beginningAtTs.size(); i++) {
            AlignableAnnotation other = (AlignableAnnotation) beginningAtTs.elementAt(i);

            if ((other.getTier() == theTier) ||
                    depTiers.contains(other.getTier())) {
                other.setBegin(newTs);
            }
        }

        ((TierImpl) theTier).addAnnotation(a);

        return a;
    }

    /**
     * DOCUMENT ME!
     *
     * @param afterAnn DOCUMENT ME!
     * @param theTier DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public Annotation insertAfter(Annotation afterAnn, Tier theTier) {
        AlignableAnnotation a = null;
        AlignableAnnotation afterA = (AlignableAnnotation) afterAnn;

        TimeOrder timeOrder = ((TranscriptionImpl) (theTier.getParent())).getTimeOrder();

        TimeSlot newTs = new TimeSlotImpl(timeOrder);
        timeOrder.insertTimeSlot(newTs, afterA.getBegin(), afterA.getEnd());

        // check whether or not the tier supports graphical references
        if (((TierImpl) theTier).getLinguisticType().hasGraphicReferences()) {
            a = new SVGAlignableAnnotation(newTs, afterA.getEnd(), theTier, null);
        } else {
            a = new AlignableAnnotation(newTs, afterA.getEnd(), theTier);
        }

        //      afterA.setEnd(newTs);
        Annotation parentAnn = afterAnn.getParentAnnotation();

        Vector endingAtTs = ((TranscriptionImpl) (theTier.getParent())).getAnnotsEndingAtTimeSlot(afterA.getEnd(),
                theTier, true);

        if (endingAtTs.contains(parentAnn)) {
            endingAtTs.remove(parentAnn);
        }

        // HS jan 2005 only update annotations on the same tier or on depending tiers
        Vector depTiers = ((TierImpl) theTier).getDependentTiers();

        for (int i = 0; i < endingAtTs.size(); i++) {
            AlignableAnnotation other = (AlignableAnnotation) endingAtTs.elementAt(i);

            if ((other.getTier() == theTier) ||
                    depTiers.contains(other.getTier())) {
                other.setEnd(newTs);
            }
        }

        ((TierImpl) theTier).addAnnotation(a);
        a.registerWithParent(); //HB, 18-5-04, only works after addAnnotation, newTS not used on any tier during construction

        return a;
    }

    /**
     * Detach annotation theAnn from tier theTier by reconnecting remaining
     * Annotations on the tier. Assumes that all references and
     * ParentAnnotationListener registrations are already cleaned up.
     *
     * @param theAnn DOCUMENT ME!
     * @param theTier DOCUMENT ME!
     */
    public void detachAnnotation(Annotation theAnn, Tier theTier) {
        // find all dependents on this tier for theAnn's parent.
        // within this set, find theAnn's previous and next annotion, if they exist.
        // finally, reconnect
        AlignableAnnotation a = (AlignableAnnotation) theAnn; // cast is safe for case of NoTimeGapWithinParent
        Annotation parent = a.getParentAnnotation();

        //System.out.println("\ndetach: " + a.getValue());
        Vector enclosedAnnots = new Vector();
        Vector childAnnots = new Vector();

        if (parent != null) {
            //	enclosedAnnots = parent.getChildrenOnTier(theTier);
            childAnnots = ((TranscriptionImpl) (theTier.getParent())).getChildAnnotationsOf(parent);
        }

        Iterator childIter = childAnnots.iterator();

        while (childIter.hasNext()) {
            Annotation ann = (Annotation) childIter.next();

            if (ann.getTier() == theTier) {
                enclosedAnnots.add(ann);
            }
        }

        if (enclosedAnnots.size() > 0) {
            AlignableAnnotation prev = null;
            AlignableAnnotation next = null;

            int index = enclosedAnnots.indexOf(a);

            if (index > 0) {
                prev = (AlignableAnnotation) (enclosedAnnots.get(index - 1));
            }

            if (index < (enclosedAnnots.size() - 1)) {
                next = (AlignableAnnotation) (enclosedAnnots.get(index + 1));
            }

            // reconnect
            if (prev != null) {
                //    prev.setEnd(a.getEnd());
                if (prev.isMarkedDeleted()) {
                    // don't bother to reconnect an annotation that has been marked deleted
                    return;
                }

                Vector endingAtTs = ((TranscriptionImpl) (theTier.getParent())).getAnnotsEndingAtTimeSlot(prev.getEnd(),
                        theTier, true);

                for (int i = 0; i < endingAtTs.size(); i++) {
                    ((AlignableAnnotation) endingAtTs.elementAt(i)).setEnd(a.getEnd());
                }
            } else if (next != null) {
                //    next.setBegin(a.getBegin());
                if (next.isMarkedDeleted()) {
                    //don't bother to reconnect an annotation that has been marked deleted
                    return;
                }

                Vector beginningAtTs = ((TranscriptionImpl) (theTier.getParent())).getAnnotsBeginningAtTimeSlot(next.getBegin(),
                        theTier, true);

                for (int i = 0; i < beginningAtTs.size(); i++) {
                    ((AlignableAnnotation) beginningAtTs.elementAt(i)).setBegin(a.getBegin());
                }
            }
        }
    }
}
