/*
 * File:     TimeCodedTranscriptionImpl.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
 */

/*
 * Created on Dec 17, 2004
 */
package mpi.eudico.server.corpora.clomimpl.shoebox.interlinear;

import mpi.eudico.client.util.TimeFormatter;

import mpi.eudico.server.corpora.clom.Annotation;
import mpi.eudico.server.corpora.clom.Tier;
import mpi.eudico.server.corpora.clom.Transcription;

import mpi.eudico.server.corpora.clomimpl.abstr.AlignableAnnotation;
import mpi.eudico.server.corpora.clomimpl.abstr.RefAnnotation;
import mpi.eudico.server.corpora.clomimpl.abstr.TierImpl;
import mpi.eudico.server.corpora.clomimpl.abstr.TranscriptionImpl;
import mpi.eudico.server.corpora.clomimpl.type.LinguisticType;
import mpi.eudico.server.corpora.clomimpl.type.SymbolicAssociation;

import mpi.eudico.util.TranscriptionUtil;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;

import java.util.Hashtable;
import java.util.Locale;
import java.util.Vector;


/**
 * This implementation of TimeCodedTranscription will delegate method calls to
 * a wrapped TranscriptionImpl, but overrides methods when necessary, to add
 * time code tiers without modifying the wrapped document.
 *
 * @author hennie
 * @version Aug 2005 Identity removed
 */
public class TimeCodedTranscriptionImpl implements TimeCodedTranscription {
    private TranscriptionImpl wrappedTranscription;
    private Vector timeCodeTiers = null;
    private int tcTierCounter = 0;
    private LinguisticType tcLingType;
    private Hashtable rootTiers;
    private Hashtable tcChildAnnots;

    /**
     * Creates a new TimeCodedTranscriptionImpl instance
     *
     * @param trImpl DOCUMENT ME!
     */
    public TimeCodedTranscriptionImpl(TranscriptionImpl trImpl) {
        wrappedTranscription = trImpl;

        timeCodeTiers = new Vector();
        rootTiers = new Hashtable();
        tcChildAnnots = new Hashtable();

        tcLingType = new LinguisticType(TC_LING_TYPE);
        tcLingType.setTimeAlignable(false);
        tcLingType.setGraphicReferences(false);
        tcLingType.addConstraint(new SymbolicAssociation());
    }

    /**
     * DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public Transcription getTranscription() {
        return wrappedTranscription;
    }

    /**
     * Returns the list of Tiers that are accessible.
     *
     * @return the list of Tiers
     */
    public Vector getTiers() {
        Vector tiers = wrappedTranscription.getTiers();
        Vector allTiers = new Vector(tiers);

        if (timeCodeTiers != null) {
            allTiers.addAll(timeCodeTiers);
        }

        return allTiers;
    }

    /**
     * DOCUMENT ME!
     *
     * @param theAnnot DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public Vector getChildAnnotationsOf(Annotation theAnnot) {
        Vector childAnnots = new Vector(wrappedTranscription.getChildAnnotationsOf(
                    theAnnot));

        Annotation child = (Annotation) tcChildAnnots.get(theAnnot);

        if (child != null) {
            childAnnots.add(child);
        }

        return childAnnots;
    }

    /**
     * DOCUMENT ME!
     *
     * @param theTier DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public TierImpl getParentTier(Tier theTier) {
        TierImpl parentTier = null;

        if (theTier != null) {
            if (timeCodeTiers.contains(theTier)) {
                parentTier = (TierImpl) rootTiers.get(theTier);
            } else {
                parentTier = (TierImpl) ((TierImpl) theTier).getParentTier();
            }
        }

        return parentTier;
    }

    /**
     * DOCUMENT ME!
     *
     * @param forTier DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public TierImpl getRootTier(Tier forTier) {
        TierImpl rootTier = null;

        if (forTier != null) {
            if (timeCodeTiers.contains(forTier)) {
                rootTier = (TierImpl) rootTiers.get(forTier);
            } else {
                rootTier = ((TierImpl) forTier).getRootTier();
            }
        }

        return rootTier;
    }

    /**
     * DOCUMENT ME!
     *
     * @param tier1 DOCUMENT ME!
     * @param tier2 DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public boolean isAncestorOf(Tier tier1, Tier tier2) {
        boolean ancestor = false;
        TierImpl parentTier = getParentTier(tier2);

        if (parentTier != null) { // has ancestor

            if (parentTier == tier1) {
                ancestor = true;
            } else {
                ancestor = isAncestorOf(tier1, parentTier);
            }
        }

        return ancestor;
    }

    /**
     * DOCUMENT ME!
     *
     * @param tier DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public Vector getTierTree(TierImpl tier) {
        Vector tierTree = new Vector();

        Vector children = tier.getChildTiers();

        tierTree.add(tier);

        for (int j = 0; j < children.size(); j++) {
            TierImpl child = (TierImpl) children.elementAt(j);
            tierTree.addAll(getTierTree(child));
        }

        // add potential tc tier
        for (int i = 0; i < timeCodeTiers.size(); i++) {
            TierImpl tcTier = (TierImpl) timeCodeTiers.elementAt(i);

            if (rootTiers.get(tcTier) == tier) {
                tierTree.add(tcTier);
            }
        }

        return tierTree;
    }

    /**
     * DOCUMENT ME!
     *
     * @param timeCodeStyle DOCUMENT ME!
     */
    public void prepareTimeCodeRendering(int timeCodeStyle) {
        cleanupTimeCodeTiers();
        addTimeCodeTiers(timeCodeStyle);
    }

    /**
     * DOCUMENT ME!
     */
    public void cleanupTimeCodeTiers() {
        timeCodeTiers.clear();
        rootTiers.clear();
        tcChildAnnots.clear();
        tcTierCounter = 0; // reset
    }

    /**
     * Time code is shown by adding a symbolic association tier to each root
     * tier, and a RefAnnotation with a time code as value for each root
     * annotation.
     *
     * @param timeCodeStyle DOCUMENT ME!
     */
    private void addTimeCodeTiers(int timeCodeStyle) {
        Vector topTiers = TranscriptionUtil.getTopTiers(wrappedTranscription);

        for (int i = 0; i < topTiers.size(); i++) {
            TierImpl topT = (TierImpl) topTiers.elementAt(i);
            timeCodeTiers.add(addTCTierFor(topT, timeCodeStyle));
        }
    }

    private Tier addTCTierFor(TierImpl tier, int timeCodeStyle) {
        TierImpl newTier = null;
        String newTierName = TC_TIER_PREFIX + tcTierCounter++;

        // set parent tier and transcription to null, to prevent modification
        // of the wrappedTranscription.
        newTier = new TierImpl(null, newTierName, null, null, null);

        newTier.setLinguisticType(tcLingType);

        // add tc annotations for each annot on tier
        Vector annots = tier.getAnnotations();

        for (int i = 0; i < annots.size(); i++) {
            Annotation parentAnn = (Annotation) annots.elementAt(i);

            // manually set referred annotation, to prevent registration of
            // parent annotation listeners.
            // getChildAnnotationsOf should now return tc children
            RefAnnotation newAnnot = new RefAnnotation(null, newTier);
            newAnnot.getReferences().add(parentAnn);

            newTier.addAnnotation(newAnnot);

            long bl = -1;
            long el = -1;

            if (parentAnn instanceof AlignableAnnotation &&
                    ((AlignableAnnotation) parentAnn).getBegin().isTimeAligned()) {
                bl = parentAnn.getBeginTimeBoundary();
            }

            if (parentAnn instanceof AlignableAnnotation &&
                    ((AlignableAnnotation) parentAnn).getEnd().isTimeAligned()) {
                el = parentAnn.getEndTimeBoundary();
            }

            String value = "";

            if (timeCodeStyle == Interlinearizer.HHMMSSMS) {
                String beginStr = Interlinearizer.UNALIGNED_HHMMSSMS;

                if (bl != -1) {
                    beginStr = TimeFormatter.toString(parentAnn.getBeginTimeBoundary());
                }

                String endStr = Interlinearizer.UNALIGNED_HHMMSSMS;

                if (el != -1) {
                    endStr = TimeFormatter.toString(parentAnn.getEndTimeBoundary());
                }

                value = beginStr + " - " + endStr;
            } else {
                double bd = bl / 1000.0;
                double ed = el / 1000.0;

                //DecimalFormat ssmmm = new DecimalFormat("#0.000");
                // HS: 27 apr 05 formatting ('.' or ',')is locale dependent, make sure '.' is used
                DecimalFormat ssmmm = new DecimalFormat("#0.000",
                        new DecimalFormatSymbols(Locale.US));
                String bs = Interlinearizer.UNALIGNED_SSMS;

                if (bl != -1) {
                    bs = ssmmm.format(bd);
                }

                String es = Interlinearizer.UNALIGNED_SSMS;

                if (el != -1) {
                    es = ssmmm.format(ed);
                }

                value = bs + " - " + es;
            }

            newAnnot.setValue(value);

            // store parent and child
            tcChildAnnots.put(parentAnn, newAnnot);
        }

        rootTiers.put(newTier, tier);

        return newTier;
    }

    /**
     * DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public Vector getTimeCodeTiers() {
        return timeCodeTiers;
    }
}
