/*
 * File:     ExportSelectionAsEAF.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.client.annotator.export;

import mpi.eudico.client.annotator.ElanLocale;
import mpi.eudico.client.annotator.Preferences;

import mpi.eudico.client.annotator.commands.ELANCommandFactory;

import mpi.eudico.client.annotator.util.AnnotationRecreator;
import mpi.eudico.client.annotator.util.ClientLogger;
import mpi.eudico.client.annotator.util.ElanFileFilter;
import mpi.eudico.client.annotator.util.FileExtension;

import mpi.eudico.client.util.TierTree;

import mpi.eudico.server.corpora.clom.TranscriptionStore;

import mpi.eudico.server.corpora.clomimpl.abstr.AlignableAnnotation;
import mpi.eudico.server.corpora.clomimpl.abstr.MediaDescriptor;
import mpi.eudico.server.corpora.clomimpl.abstr.TierImpl;
import mpi.eudico.server.corpora.clomimpl.abstr.TranscriptionImpl;
import mpi.eudico.server.corpora.clomimpl.dobes.ACM23TranscriptionStore;
import mpi.eudico.server.corpora.clomimpl.type.CVEntry;
import mpi.eudico.server.corpora.clomimpl.type.Constraint;
import mpi.eudico.server.corpora.clomimpl.type.ControlledVocabulary;
import mpi.eudico.server.corpora.clomimpl.type.IncludedIn;
import mpi.eudico.server.corpora.clomimpl.type.LinguisticType;
import mpi.eudico.server.corpora.clomimpl.type.SymbolicAssociation;
import mpi.eudico.server.corpora.clomimpl.type.SymbolicSubdivision;
import mpi.eudico.server.corpora.clomimpl.type.TimeSubdivision;

import java.io.File;

import java.util.Enumeration;
import java.util.Vector;

import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.ProgressMonitor;
import javax.swing.filechooser.FileFilter;
import javax.swing.tree.DefaultMutableTreeNode;


/**
 * Creates a new transcription document with the annotations that are found
 * within  a selected time interval in the source document.<br>
 * First all MediaDescriptors, Controlled Vocabularies, Linguistic Types and
 * Tiers  are copied to a new Transcription. Next all annotations that overlap
 * the selected time  interval are copied. The copied annotations are forced
 * into the interval (i.e. if  the begintime is smaller than the interval
 * begin time, the annotation's begin time  is updated to the interval begin
 * time. Same if the annotation's end time is greater than the interval end
 * time. <br>
 * Finally all annotations are shifted in time by a value of -(selection begin
 * time),  making the original selection begin time the time origin (point 0)
 * of the new  transcription.
 *
 * @author Han Sloetjes
 * @version 1.0 Oct 2005
 */
public class ExportSelectionAsEAF implements ClientLogger {
    private TranscriptionImpl transcription;
    private TranscriptionImpl nextTrans;
    private String path;
    private long beginTime;
    private long endTime;
    private ProgressMonitor monitor;

    /**
     * Creates a new ExportSelectionAsEAF instance
     *
     * @param transcription the source transcription
     * @param beginTime interval begin time
     * @param endTime interval end time
     */
    public ExportSelectionAsEAF(TranscriptionImpl transcription,
        long beginTime, long endTime) {
        this.transcription = transcription;
        this.beginTime = beginTime;
        this.endTime = endTime;

        if (transcription != null) {
            startExport();
        }
    }

    /**
     * Prompts for a file name, creates a progress monitor and spawns a new
     * thread for the actual work.
     */
    private void startExport() {
        // get a file path to save to
        path = promptForFileName();

        if (path == null) {
            return;
        }

        nextTrans = new TranscriptionImpl();
        monitor = new ProgressMonitor(null,
                ElanLocale.getString("SaveDialog.Message.Title"), "", 0, 100);
        monitor.setMillisToDecideToPopup(10);
        monitor.setMillisToPopup(10);

        new Thread() {
                public void run() {
                    try {
                        Thread.sleep(50);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                    ExportSelectionAsEAF.this.startCopy();
                }
            }.start();

        //copy(transcription, nextTrans, beginTime, endTime);
    }

    /**
     * Starts the copy process.
     */
    private void startCopy() {
        if ((transcription == null) || (nextTrans == null)) {
            progressUpdate(null, 100);

            return;
        }

        progressUpdate(null, 10);
        copy(transcription, nextTrans, beginTime, endTime);
        progressUpdate(null, 100);
    }

    /**
     * Copies MediaDescriptors, Controlled Vocabularies, Linguistic Types,
     * Tiers and  the relevant Annotations to the new Transcription. The
     * annotations are forced  into the selection interval. Finally the
     * annotations are shifted with a value of -begin and the transcription is
     * written to file.  This method could be split into separate parts for
     * copying each group of  objects, when there is a need for this. Maybe in
     * its own helper class.
     *
     * @param sourceTrans the source transcription
     * @param copyTrans the copy transcription
     * @param begin the selection begin time
     * @param end the selection end time
     */
    private void copy(TranscriptionImpl sourceTrans,
        TranscriptionImpl copyTrans, long begin, long end) {
        copyTrans.setNotifying(false);

        // copy/clone media descriptors
        Vector mds = sourceTrans.getMediaDescriptors();
        Vector cmds = new Vector(mds.size());
        MediaDescriptor md;

        for (int i = 0; i < mds.size(); i++) {
            md = (MediaDescriptor) mds.get(i);
            cmds.add((MediaDescriptor) md.clone());
        }

        copyTrans.setMediaDescriptors(cmds);
        progressUpdate(null, 20);

        // cv's
        Vector cvs = sourceTrans.getControlledVocabularies();
        Vector cvc = new Vector(cvs.size());
        ControlledVocabulary cv1;
        ControlledVocabulary cv2;
        CVEntry[] entries;
        CVEntry ent1;
        CVEntry ent2;

        for (int i = 0; i < cvs.size(); i++) {
            cv1 = (ControlledVocabulary) cvs.get(i);
            cv2 = new ControlledVocabulary(cv1.getName(), cv1.getDescription());
            entries = cv1.getEntries();

            for (int j = 0; j < entries.length; j++) {
                ent1 = entries[j];
                ent2 = new CVEntry(ent1.getValue(), ent1.getDescription());
                cv2.addEntry(ent2);
            }

            cvc.add(cv2);
        }

        copyTrans.setControlledVocabularies(cvc);
        progressUpdate(null, 30);

        // linguistic types
        Vector types = sourceTrans.getLinguisticTypes();
        Vector typc = new Vector(types.size());
        LinguisticType lt1;
        LinguisticType lt2;
        Constraint con1;
        Constraint con2 = null;

        for (int i = 0; i < types.size(); i++) {
            lt1 = (LinguisticType) types.get(i);
            lt2 = new LinguisticType(lt1.getLinguisticTypeName());
            lt2.setTimeAlignable(lt1.isTimeAlignable());
            lt2.setGraphicReferences(lt1.hasGraphicReferences());
            lt2.setControlledVocabularyName(lt1.getControlledVocabylaryName());
            con1 = lt1.getConstraints();

            if (con1 != null) {
                switch (con1.getStereoType()) {
                case Constraint.TIME_SUBDIVISION:
                    con2 = new TimeSubdivision();

                    break;

                case Constraint.SYMBOLIC_SUBDIVISION:
                    con2 = new SymbolicSubdivision();

                    break;

                case Constraint.SYMBOLIC_ASSOCIATION:
                    con2 = new SymbolicAssociation();

                    break;

                case Constraint.INCLUDED_IN:
                    con2 = new IncludedIn();
                }

                lt2.addConstraint(con2);
            }

            typc.add(lt2);
        }

        copyTrans.setLinguisticTypes(typc);
        progressUpdate(null, 40);

        TierTree tree = new TierTree(sourceTrans);
        DefaultMutableTreeNode root = tree.getTree();
        DefaultMutableTreeNode node;
        TierImpl t1;
        TierImpl t2;
        TierImpl copyTier;
        String name;
        String parentName;

        Enumeration en = root.breadthFirstEnumeration();
        en.nextElement();

        while (en.hasMoreElements()) {
            copyTier = null; // reset
            node = (DefaultMutableTreeNode) en.nextElement();
            name = (String) node.getUserObject();
            t1 = (TierImpl) sourceTrans.getTierWithId(name);

            if (t1 != null) {
                lt1 = t1.getLinguisticType();
                lt2 = copyTrans.getLinguisticTypeByName(lt1.getLinguisticTypeName());

                if (lt2 != null) {
                    if (t1.hasParentTier()) {
                        parentName = t1.getParentTier().getName();
                        t2 = (TierImpl) copyTrans.getTierWithId(parentName);

                        if (t2 != null) {
                            copyTier = new TierImpl(t2, name,
                                    t1.getParticipant(), copyTrans, lt2);
                        } else {
                            LOG.warning("The parent tier: " + parentName +
                                " for tier: " + name +
                                " was not found in the destination transcription");
                        }
                    } else {
                        copyTier = new TierImpl(name, t1.getParticipant(),
                                copyTrans, lt2);
                    }
                } else {
                    LOG.warning("Could not add tier: " + name +
                        " because the Linguistic Type was not found in the destination transcription.");
                }
            }

            if (copyTier != null) {
                copyTrans.addTier(copyTier);
            }
        }

        progressUpdate(null, 60);

        // copy annotations within the interval, loop over toplevel tiers
        Vector srcAnnos;
        AlignableAnnotation ann;
        AlignableAnnotation copyAnn;
        DefaultMutableTreeNode record;

        for (int i = 0; i < root.getChildCount(); i++) {
            node = (DefaultMutableTreeNode) root.getChildAt(i);
            name = (String) node.getUserObject();
            t1 = (TierImpl) sourceTrans.getTierWithId(name);
            srcAnnos = t1.getAnnotations();

            for (int j = 0; j < srcAnnos.size(); j++) {
                ann = (AlignableAnnotation) srcAnnos.get(j);

                if (ann.getEndTimeBoundary() <= begin) {
                    continue;
                }

                if (ann.getBeginTimeBoundary() >= end) {
                    break;
                }

                record = AnnotationRecreator.createTreeForAnnotation(ann);
                copyAnn = (AlignableAnnotation) AnnotationRecreator.createAnnotationFromTree(copyTrans,
                        record);

                // force in interval
                if (copyAnn.getBeginTimeBoundary() < begin) {
                    copyAnn.updateTimeInterval(begin,
                        copyAnn.getEndTimeBoundary());
                }

                if (copyAnn.getEndTimeBoundary() > end) {
                    copyAnn.updateTimeInterval(copyAnn.getBeginTimeBoundary(),
                        end);
                }
            }
        }

        progressUpdate(null, 80);

        copyTrans.shiftAllAnnotations(-begin);

        ACM23TranscriptionStore store = new ACM23TranscriptionStore();
        store.storeTranscription(copyTrans, null, new Vector(), path,
            TranscriptionStore.EAF);
        LOG.info("Selection saved as new .eaf");
    }

    /**
     * Prompt for a filename and location.
     *
     * @return a path as a string
     */
    private String promptForFileName() {
        String exportDir = (String) Preferences.get("LastUsedEAFDir", null);

        if (exportDir == null) {
            exportDir = System.getProperty("user.dir");
        }

        JFileChooser chooser = new JFileChooser();
        chooser.setCurrentDirectory(new File(exportDir));
        chooser.setDialogTitle(ElanLocale.getString("SaveDialog.Title"));

        File exportFile = null;
        FileFilter filter = ElanFileFilter.createFileFilter(ElanFileFilter.EAF_TYPE);
        chooser.setFileFilter(filter);

        if (chooser.showSaveDialog(ELANCommandFactory.getRootFrame(
                        transcription)) == JFileChooser.APPROVE_OPTION) {
            File curDir = chooser.getCurrentDirectory();

            if (curDir != null) {
                Preferences.set("LastUsedEAFDir", curDir.getAbsolutePath(), null);
            }

            exportFile = chooser.getSelectedFile();

            if (exportFile != null) {
                String name = exportFile.getAbsolutePath();
                String lowerPathName = name.toLowerCase();

                String[] exts = FileExtension.EAF_EXT;
                boolean validExt = false;

                for (int i = 0; i < exts.length; i++) {
                    if (lowerPathName.endsWith("." + exts[i])) {
                        validExt = true;

                        break;
                    }
                }

                if (!validExt) {
                    name += ("." + exts[0]);
                    exportFile = new File(name);
                }

                if (exportFile.exists()) {
                    int answer = JOptionPane.showConfirmDialog(null,
                            ElanLocale.getString("Message.Overwrite"),
                            ElanLocale.getString("SaveDialog.Message.Title"),
                            JOptionPane.YES_NO_OPTION);

                    if (answer == JOptionPane.NO_OPTION) {
                        return promptForFileName();
                    } else {
                        return name;
                    }
                } else {
                    return name;
                }
            } else {
                return null;
            }
        } else {
            // save dialog canceled
            return null;
        }
    }

    private void progressUpdate(String note, int progress) {
        if (monitor != null) {
            if (note != null) {
                monitor.setNote(note);
            }

            monitor.setProgress(progress);
        }
    }
}
