/*
 * File:     ExportTradTranscript.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.Constants;
import mpi.eudico.client.annotator.ElanLocale;
import mpi.eudico.client.annotator.Preferences;
import mpi.eudico.client.annotator.Selection;

import mpi.eudico.client.annotator.gui.TextExportFileChooser;

import mpi.eudico.client.annotator.tier.TierExportTableModel;

import mpi.eudico.client.annotator.util.AnnotationDataRecord;
import mpi.eudico.client.annotator.util.ElanFileFilter;
import mpi.eudico.client.annotator.util.FileExtension;

import mpi.eudico.client.util.CheckBoxTableCellRenderer;
import mpi.eudico.client.util.TimeFormatter;

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

import mpi.eudico.server.corpora.clomimpl.abstr.TierImpl;
import mpi.eudico.server.corpora.clomimpl.abstr.TranscriptionImpl;

import mpi.eudico.util.TimeRelation;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;

import java.nio.charset.UnsupportedCharsetException;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.TreeSet;
import java.util.Vector;

import javax.swing.DefaultCellEditor;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.WindowConstants;
import javax.swing.border.TitledBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.filechooser.FileFilter;


/**
 * An export dialog for exporting tiers in a 'traditional' transcription style.
 * This class will probably be obsolete by the time the full-featured text
 * export  function is fully implemented.
 *
 * @author Han Sloetjes
 */
public class ExportTradTranscript extends JDialog implements ActionListener,
    ItemListener, ListSelectionListener {
    private TranscriptionImpl transcription;
    private Selection selection;

    /** ui elements */
    private JLabel titleLabel;
    private JPanel titlePanel;
    private JPanel tierSelectionPanel;
    private JCheckBox rootTiersCB;
    private JTable tierTable;
    private TierExportTableModel model;
    private JPanel optionsPanel;
    private JLabel charPerLineLabel;
    private JCheckBox wrapLinesCB;
    private JTextField numCharTF;
    private JCheckBox timeCodeCB;
    private JCheckBox labelsCB;
    private JCheckBox selectionCB;
    private JPanel buttonPanel;
    private JButton startButton;
    private JButton closeButton;
    private JButton upButton;
    private JButton downButton;
    private String encoding = "UTF-8";

    // some strings
    // not visible in the table header

    /** column id for the include in export checkbox column, invisible */
    private final String EXPORT_COLUMN = "export";

    /** column id for the tier name column, invisible */
    private final String TIER_NAME_COLUMN = "tier";

    /** white space string */
    private final String SPACE = " ";

    /** white space char */
    private final char SPACE_CHAR = ' ';

    /** new line string */
    private final String NEW_LINE = "\n";

    /** new line char */
    private final char NL_CHAR = '\n';

    /** string to separate time codes */
    private final String TIME_SEP = " - ";

    /** default line width */
    private final int NUM_CHARS = 80;

    /** space between label and contents */
    private final int LABEL_VALUE_MARGIN = 3;

    /**
     * Constructor.
     *
     * @param parent parent frame
     * @param modal the modal/blocking attribute
     * @param transcription the transcription to export from
     * @param selection the current selection
     */
    public ExportTradTranscript(Frame parent, boolean modal,
        TranscriptionImpl transcription, Selection selection) {
        super(parent, modal);
        this.transcription = transcription;
        this.selection = selection;
        initComponents();
        extractTiers();
        postInit();
    }

    /**
     * Initializes UI elements.
     */
    protected void initComponents() {
        titleLabel = new JLabel();
        titlePanel = new JPanel();
        tierSelectionPanel = new JPanel();
        rootTiersCB = new JCheckBox();
        optionsPanel = new JPanel();
        charPerLineLabel = new JLabel();
        wrapLinesCB = new JCheckBox();
        numCharTF = new JTextField(4);
        timeCodeCB = new JCheckBox();
        labelsCB = new JCheckBox();
        selectionCB = new JCheckBox();
        buttonPanel = new JPanel();
        startButton = new JButton();
        closeButton = new JButton();
        upButton = new JButton();
        downButton = new JButton();

        try {
            ImageIcon upIcon = new ImageIcon(this.getClass().getResource("/toolbarButtonGraphics/navigation/Up16.gif"));
            ImageIcon downIcon = new ImageIcon(this.getClass().getResource("/toolbarButtonGraphics/navigation/Down16.gif"));
            upButton.setIcon(upIcon);
            downButton.setIcon(downIcon);
        } catch (Exception ex) {
            upButton.setText("Up");
            downButton.setText("Down");
        }

        model = new TierExportTableModel();
        model.setColumnIdentifiers(new String[] { EXPORT_COLUMN, TIER_NAME_COLUMN });
        tierTable = new JTable(model);

        getContentPane().setLayout(new GridBagLayout());
        setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);

        addWindowListener(new WindowAdapter() {
                public void windowClosing(WindowEvent evt) {
                    closeDialog(evt);
                }
            });

        Insets insets = new Insets(2, 6, 2, 6);
        GridBagConstraints gridBagConstraints;

        titlePanel.setLayout(new BorderLayout(0, 4));
        titleLabel.setFont(titleLabel.getFont().deriveFont((float) 16));
        titleLabel.setHorizontalAlignment(SwingConstants.CENTER);

        JPanel titleLabelPanel = new JPanel();
        titleLabelPanel.add(titleLabel);
        titlePanel.add(titleLabelPanel, BorderLayout.NORTH);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = GridBagConstraints.NORTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.insets = insets;
        getContentPane().add(titlePanel, gridBagConstraints);

        tierSelectionPanel.setLayout(new GridBagLayout());

        rootTiersCB.setSelected(true);
        rootTiersCB.addItemListener(this);
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.insets = insets;
        tierSelectionPanel.add(rootTiersCB, gridBagConstraints);

        Dimension tableDim = new Dimension(50, 100);

        tierTable.getColumn(EXPORT_COLUMN).setCellEditor(new DefaultCellEditor(
                new JCheckBox()));
        tierTable.getColumn(EXPORT_COLUMN).setCellRenderer(new CheckBoxTableCellRenderer());
        tierTable.getColumn(EXPORT_COLUMN).setMaxWidth(30);
        tierTable.setShowVerticalLines(false);
        tierTable.setTableHeader(null);
        tierTable.getSelectionModel().addListSelectionListener(this);

        JScrollPane tierScrollPane = new JScrollPane(tierTable);
        tierScrollPane.setPreferredSize(tableDim);
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = insets;
        gridBagConstraints.fill = GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        tierSelectionPanel.add(tierScrollPane, gridBagConstraints);

        upButton.addActionListener(this);
        downButton.addActionListener(this);
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.insets = insets;
        tierSelectionPanel.add(upButton, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.insets = insets;
        tierSelectionPanel.add(downButton, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        gridBagConstraints.insets = insets;
        getContentPane().add(tierSelectionPanel, gridBagConstraints);

        optionsPanel.setLayout(new GridBagLayout());

        wrapLinesCB.addItemListener(this);
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.gridwidth = 3;
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.insets = insets;
        optionsPanel.add(wrapLinesCB, gridBagConstraints);

        JPanel fill = new JPanel();
        Dimension fillDim = new Dimension(30, 10);
        fill.setPreferredSize(fillDim);
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = GridBagConstraints.NONE;
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.insets = insets;
        optionsPanel.add(fill, gridBagConstraints);

        numCharTF.setEnabled(false);
        numCharTF.setBackground(Constants.DEFAULTBACKGROUNDCOLOR);
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = GridBagConstraints.NONE;
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.insets = insets;
        optionsPanel.add(numCharTF, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = GridBagConstraints.NONE;
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.insets = insets;
        optionsPanel.add(charPerLineLabel, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.gridwidth = 3;
        gridBagConstraints.fill = GridBagConstraints.NONE;
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.insets = insets;
        optionsPanel.add(labelsCB, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.gridwidth = 3;
        gridBagConstraints.fill = GridBagConstraints.NONE;
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.insets = insets;
        optionsPanel.add(timeCodeCB, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 4;
        gridBagConstraints.gridwidth = 3;
        gridBagConstraints.fill = GridBagConstraints.NONE;
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.insets = insets;
        optionsPanel.add(labelsCB, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 5;
        gridBagConstraints.gridwidth = 3;
        gridBagConstraints.fill = GridBagConstraints.NONE;
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.insets = insets;
        optionsPanel.add(selectionCB, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.insets = insets;
        getContentPane().add(optionsPanel, gridBagConstraints);

        buttonPanel.setLayout(new GridLayout(1, 2, 6, 0));

        startButton.addActionListener(this);
        buttonPanel.add(startButton);

        closeButton.addActionListener(this);
        buttonPanel.add(closeButton);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.insets = insets;
        getContentPane().add(buttonPanel, gridBagConstraints);

        updateLocale();
    }

    /**
     * Extract candidate tiers for export.
     */
    protected void extractTiers() {
        if (model != null) {
            for (int i = model.getRowCount() - 1; i >= 0; i--) {
                model.removeRow(i);
            }

            if (transcription != null) {
                Vector v = transcription.getTiers();
                TierImpl t;
                boolean rootsOnly = rootTiersCB.isSelected();

                for (int i = 0; i < v.size(); i++) {
                    t = (TierImpl) v.get(i);

                    if (rootsOnly) {
                        // add only root tiers
                        if (t.getParentTier() == null) {
                            model.addRow(new Object[] { Boolean.TRUE, t.getName() });
                        }
                    } else {
                        // add all
                        if (t.getParentTier() == null) {
                            model.addRow(new Object[] { Boolean.TRUE, t.getName() });
                        } else {
                            model.addRow(new Object[] { Boolean.FALSE, t.getName() });
                        }
                    }
                }
            }

            if (model.getRowCount() > 1) {
                upButton.setEnabled(true);
                downButton.setEnabled(true);
            } else {
                upButton.setEnabled(false);
                downButton.setEnabled(false);
            }
        } else {
            upButton.setEnabled(false);
            downButton.setEnabled(false);
        }
    }

    /**
     * Pack, size and set location.
     */
    protected void postInit() {
        pack();

        int w = 550;
        int h = 400;
        setSize((getSize().width < w) ? w : getSize().width,
            (getSize().height < h) ? h : getSize().height);
        setLocationRelativeTo(getParent());

        //setResizable(false);
    }

    /**
     * Closes the dialog
     *
     * @param evt the window closing event
     */
    protected void closeDialog(WindowEvent evt) {
        setVisible(false);
        dispose();
    }

    /**
     * Applies localized strings to the ui elements. For historic reasons the
     * string identifiers start with "TokenizeDialog"
     */
    protected void updateLocale() {
        setTitle(ElanLocale.getString("ExportTradTranscript.Title"));
        titleLabel.setText(ElanLocale.getString("ExportTradTranscript.Title"));
        tierSelectionPanel.setBorder(new TitledBorder(ElanLocale.getString(
                    "ExportDialog.Label.SelectTiers")));
        rootTiersCB.setText(ElanLocale.getString(
                "ExportTradTranscript.Label.RootTiers"));
        optionsPanel.setBorder(new TitledBorder(ElanLocale.getString(
                    "ExportDialog.Label.Options")));
        wrapLinesCB.setText(ElanLocale.getString(
                "ExportTradTranscript.Label.WrapLines"));
        charPerLineLabel.setText(ElanLocale.getString(
                "ExportTradTranscript.Label.NumberChars"));
        timeCodeCB.setText(ElanLocale.getString(
                "ExportTradTranscript.Label.IncludeTimeCode"));
        labelsCB.setText(ElanLocale.getString(
                "ExportTradTranscript.Label.IncludeTierLabels"));
        selectionCB.setText(ElanLocale.getString("ExportDialog.Restrict"));
        startButton.setText(ElanLocale.getString("Button.OK"));
        closeButton.setText(ElanLocale.getString("Button.Close"));
    }

    /**
     * Moves selected tiers up in the list of tiers.
     */
    private void moveUp() {
        if ((tierTable == null) || (model == null) ||
                (model.getRowCount() < 2)) {
            return;
        }

        int[] selected = tierTable.getSelectedRows();

        for (int i = 0; i < selected.length; i++) {
            int row = selected[i];

            if ((row > 0) && !tierTable.isRowSelected(row - 1)) {
                model.moveRow(row, row, row - 1);
                tierTable.changeSelection(row, 0, true, false);
                tierTable.changeSelection(row - 1, 0, true, false);
            }
        }
    }

    /**
     * Moves selected tiers up in the list of tiers.
     */
    private void moveDown() {
        if ((tierTable == null) || (model == null) ||
                (model.getRowCount() < 2)) {
            return;
        }

        int[] selected = tierTable.getSelectedRows();

        for (int i = selected.length - 1; i >= 0; i--) {
            int row = selected[i];

            if ((row < (model.getRowCount() - 1)) &&
                    !tierTable.isRowSelected(row + 1)) {
                model.moveRow(row, row, row + 1);
                tierTable.changeSelection(row, 0, true, false);
                tierTable.changeSelection(row + 1, 0, true, false);
            }
        }
    }

    /**
     * Starts the actual export after performing some checks.
     *
     * @return true if export succeeded, false oherwise
     */
    private boolean startExport() {
        if ((model == null) || (tierTable == null)) {
            return false;
        }

        int includeCol = model.findColumn(EXPORT_COLUMN);
        int nameCol = model.findColumn(TIER_NAME_COLUMN);

        // check if there is anything to export
        boolean atLeastOneTier = false;

        for (int i = 0; i < model.getRowCount(); i++) {
            Boolean include = (Boolean) model.getValueAt(i, includeCol);

            if (include.booleanValue()) {
                atLeastOneTier = true;

                break;
            }
        }

        if (!atLeastOneTier) {
            showWarningDialog(ElanLocale.getString(
                    "ExportTradTranscript.Message.NoTiers"));

            return false;
        }

        // check the chars per line value
        int charsPerLine = Integer.MAX_VALUE;

        if (wrapLinesCB.isSelected()) {
            String textValue = numCharTF.getText().trim();

            try {
                charsPerLine = Integer.parseInt(textValue);
            } catch (NumberFormatException nfe) {
                showWarningDialog(ElanLocale.getString(
                        "ExportTradTranscript.Message.InvalidNumber"));
                numCharTF.selectAll();
                numCharTF.requestFocus();

                return false;
            }
        }

        // prompt for file name and location
        String fileName = promptForFileName();

        if (fileName == null) {
            return false;
        }

        // export....
        ArrayList exportTiers = new ArrayList();

        // add selected tiers in the right order
        for (int i = 0; i < model.getRowCount(); i++) {
            Boolean include = (Boolean) model.getValueAt(i, includeCol);

            if (include.booleanValue()) {
                exportTiers.add(model.getValueAt(i, nameCol));
            }
        }

        return doExport(fileName, exportTiers, charsPerLine);
    }

    /**
     * Prompts the user for a file name and location.
     *
     * @return a file (unique) path
     */
    private String promptForFileName() {
        String exportDir = (String) Preferences.get("LastUsedExportDir", null);

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

        TextExportFileChooser chooser = new TextExportFileChooser();
        chooser.setCurrentDirectory(new File(exportDir));
        chooser.setDialogTitle(ElanLocale.getString(
                "ExportTradTranscript.Title"));

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

        if (chooser.showSaveDialog(null, null) == JFileChooser.APPROVE_OPTION) {
            File curDir = chooser.getCurrentDirectory();

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

            exportFile = chooser.getSelectedFile();

            if (exportFile != null) {
                encoding = chooser.getSelectedEncoding();

                String name = exportFile.getAbsolutePath();
                String lowerPathName = name.toLowerCase();

                String[] exts = FileExtension.TEXT_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;
        }
    }

    /**
     * The action performed event handling.
     *
     * @param ae the action event
     */
    public void actionPerformed(ActionEvent ae) {
        Object source = ae.getSource();

        if (source == startButton) {
            boolean success = startExport();

            if (success) {
                closeDialog(null);
            } else {
                // show message
            }
        } else if (source == closeButton) {
            closeDialog(null);
        } else if (source == upButton) {
            moveUp();
        } else if (source == downButton) {
            moveDown();
        }
    }

    /**
     * The item state changed handling.
     *
     * @param ie the ItemEvent
     */
    public void itemStateChanged(ItemEvent ie) {
        if (ie.getSource() == wrapLinesCB) {
            if (wrapLinesCB.isSelected()) {
                numCharTF.setEnabled(true);
                numCharTF.setBackground(Constants.SHAREDCOLOR4);

                if ((numCharTF.getText() != null) ||
                        (numCharTF.getText().length() == 0)) {
                    numCharTF.setText("" + NUM_CHARS);
                }

                numCharTF.requestFocus();
            } else {
                numCharTF.setEnabled(false);
                numCharTF.setBackground(Constants.DEFAULTBACKGROUNDCOLOR);
            }
        } else if (ie.getSource() == rootTiersCB) {
            extractTiers();
        }
    }

    /**
     * Updates the checked state of the export checkboxes.
     *
     * @param lse the list selection event
     */
    public void valueChanged(ListSelectionEvent lse) {
        if ((model != null) && lse.getValueIsAdjusting()) {
            int b = lse.getFirstIndex();
            int e = lse.getLastIndex();
            int col = model.findColumn(EXPORT_COLUMN);

            for (int i = b; i <= e; i++) {
                if (tierTable.isRowSelected(i)) {
                    model.setValueAt(Boolean.TRUE, i, col);
                }
            }
        }
    }

    /**
     * Shows a warning/error dialog with the specified message string.
     *
     * @param message the message to display
     */
    protected void showWarningDialog(String message) {
        JOptionPane.showMessageDialog(this, message,
            ElanLocale.getString("Message.Warning"), JOptionPane.WARNING_MESSAGE);
    }

    //******************************
    // actual export methods from here, for the time being
    //******************************

    /**
     * The actual writing. If this class is to survive alot of the export stuff
     * should go to another  class.
     *
     * @param fileName path to the file, not null
     * @param orderedTiers tier names, ordered by the user, min size 1
     * @param charsPerLine num of chars per line if linewrap is selected
     *
     * @return true if all went well, false otherwise
     */
    private boolean doExport(final String fileName,
        final ArrayList orderedTiers, final int charsPerLine) {
        boolean selectionOnly = selectionCB.isSelected();
        boolean wrapLines = wrapLinesCB.isSelected();
        boolean includeTimeCodes = timeCodeCB.isSelected();
        boolean includeLabels = labelsCB.isSelected();

        int labelMargin = 0;
        Hashtable marginStrings = null;

        String tcLabel = "TC";
        String emptyLabel = "empty";
        int maxAnnotationLength = charsPerLine;

        if (includeLabels) {
            marginStrings = new Hashtable();

            String name;

            for (int i = 0; i < orderedTiers.size(); i++) {
                name = (String) orderedTiers.get(i);

                if (name.length() > labelMargin) {
                    labelMargin = name.length();
                }
            }

            labelMargin += LABEL_VALUE_MARGIN;

            for (int i = 0; i < orderedTiers.size(); i++) {
                name = (String) orderedTiers.get(i);
                marginStrings.put(name, getMarginString(name, labelMargin));
            }

            // add timecode label
            if (includeTimeCodes) {
                if (!marginStrings.containsKey(tcLabel)) {
                    marginStrings.put(tcLabel,
                        getMarginString(tcLabel, labelMargin));
                } else {
                    String tcl;

                    for (int count = 1; count < 100; count++) {
                        tcl = tcLabel + "-" + count;

                        if (!marginStrings.containsKey(tcl)) {
                            tcLabel = tcl;
                            marginStrings.put(tcLabel,
                                getMarginString(tcLabel, labelMargin));

                            break;
                        }
                    }
                }
            }

            // add empty string
            if (!marginStrings.containsKey(emptyLabel)) {
                marginStrings.put(emptyLabel, getMarginString("", labelMargin));
            } else {
                String tcl;

                for (int count = 1; count < 100; count++) {
                    tcl = emptyLabel + "-" + count;

                    if (!marginStrings.containsKey(tcl)) {
                        emptyLabel = tcl;
                        marginStrings.put(emptyLabel,
                            getMarginString("", labelMargin));

                        break;
                    }
                }
            }
        }

        if (wrapLines && includeLabels) {
            maxAnnotationLength = charsPerLine - labelMargin;
        }

        long bb = 0;
        long eb = Long.MAX_VALUE;

        if (selectionOnly && (selection != null)) {
            bb = selection.getBeginTime();
            eb = selection.getEndTime();
        }

        // the parameters are set, create an ordered set of Annotation records
        TreeSet records = new TreeSet();
        String tierName;
        TierImpl t;
        Annotation ann;

        for (int i = 0; i < orderedTiers.size(); i++) {
            tierName = (String) orderedTiers.get(i);

            t = (TierImpl) transcription.getTierWithId(tierName);

            if (t == null) {
                continue;
            }

            Vector v = t.getAnnotations();

            for (int j = 0; j < v.size(); j++) {
                ann = (Annotation) v.get(j);

                if (TimeRelation.overlaps(ann, bb, eb)) {
                    records.add(new IndexedExportRecord(ann, i));
                }

                if (ann.getBeginTimeBoundary() > eb) {
                    break;
                }
            }
        }

        // create output stream
        BufferedWriter writer = null;

        try {
            FileOutputStream out = new FileOutputStream(fileName);
            OutputStreamWriter osw = null;

            try {
                osw = new OutputStreamWriter(out, encoding);
            } catch (UnsupportedCharsetException uce) {
                osw = new OutputStreamWriter(out, "UTF-8");
            }

            writer = new BufferedWriter(osw);

            // do the writing
            Iterator recIter = records.iterator();
            IndexedExportRecord record;
            String val;
            String[] valLines;

            while (recIter.hasNext()) {
                record = (IndexedExportRecord) recIter.next();
                val = record.getValue().replace(NL_CHAR, SPACE_CHAR);

                if (includeLabels) {
                    writer.write((String) marginStrings.get(
                            record.getTierName()));
                }

                if (!wrapLines) {
                    writer.write(val);
                } else {
                    if (!(val.length() > maxAnnotationLength)) {
                        writer.write(val);
                    } else {
                        valLines = breakValue(val, maxAnnotationLength);

                        for (int i = 0; i < valLines.length; i++) {
                            if (i != 0) {
                                writer.write((String) marginStrings.get(
                                        emptyLabel));
                            }

                            writer.write(valLines[i]);

                            if (i != (valLines.length - 1)) {
                                writer.write(NEW_LINE);
                            }
                        }
                    }
                }

                writer.write(NEW_LINE);

                if (includeTimeCodes) {
                    writer.write((String) marginStrings.get(tcLabel));
                    writer.write(TimeFormatter.toString(record.getBeginTime()));
                    writer.write(TIME_SEP);
                    writer.write(TimeFormatter.toString(record.getEndTime()));
                    writer.write(NEW_LINE + NEW_LINE);
                }
            }

            writer.flush();
            writer.close();
        } catch (Exception ex) {
            // FileNotFound, IO, Security, Null etc
            JOptionPane.showMessageDialog(null,
                ElanLocale.getString("ExportDialog.Message.Error"),
                ElanLocale.getString("Message.Warning"),
                JOptionPane.WARNING_MESSAGE);

            return false;
        } finally {
            try {
                writer.close();
            } catch (Exception ee) {
            }
        }

        return true;
    }

    /**
     * Creates a label string of length <code>numchars</code>. A number of
     * space characters will be added to the input string  to make it the
     * right length.
     *
     * @param name the input string
     * @param numchars the new length
     *
     * @return the input string with the right number of space characters added
     *         to it
     */
    private String getMarginString(String name, int numchars) {
        int nameLength = 0;

        if (name != null) {
            nameLength = name.length();
        }

        StringBuffer bf = new StringBuffer(name);

        for (int i = 0; i < (numchars - nameLength); i++) {
            bf.append(SPACE);
        }

        return bf.toString();
    }

    /**
     * Split a string into an array of substrings, each not longer than  the
     * max annotation length.
     *
     * @param val the string to split
     * @param maxAnnotationLength the maximum length of the substrings
     *
     * @return an array of substrings
     */
    private String[] breakValue(String val, int maxAnnotationLength) {
        if (val == null) {
            return new String[] {  };
        }

        if ((val.indexOf(SPACE) < 0) || (val.length() < maxAnnotationLength)) {
            return new String[] { val };
        }

        ArrayList vals = new ArrayList();
        String sub = null;

        while (val.length() > maxAnnotationLength) {
            sub = val.substring(0, maxAnnotationLength);

            int breakSpace = sub.lastIndexOf(SPACE_CHAR);

            if (breakSpace < 0) {
                breakSpace = val.indexOf(SPACE_CHAR);

                if (breakSpace < 0) {
                    vals.add(val);

                    break;
                } else {
                    vals.add(val.substring(0, breakSpace + 1));
                    val = val.substring(breakSpace + 1);
                }
            } else {
                vals.add(sub.substring(0, breakSpace + 1));
                val = val.substring(breakSpace + 1);
            }

            if (val.length() <= maxAnnotationLength) {
                vals.add(val);

                break;
            }
        }

        return (String[]) vals.toArray(new String[] {  });
    }

    //***********************
    // inner classes
    //***********************	

    /**
     * A class that extends AnnotationDataRecord with an index,  that denotes
     * its position in the tier output order  and that implements Comparable.<br>
     * Note: this class has a natural ordering that is inconsistent with
     * equals.
     *
     * @author Han Sloetjes
     */
    private class IndexedExportRecord extends AnnotationDataRecord
        implements Comparable {
        private int index;

        /**
         * Constructor.
         *
         * @param annotation the annotation
         * @param index the index in the tier order
         */
        IndexedExportRecord(Annotation annotation, int index) {
            super(annotation);
            this.index = index;
        }

        /**
         * Returns the index in the tier order.
         *
         * @return the index in the tier order
         */
        public int getIndex() {
            return index;
        }

        /**
         * Performs a two step comparison: <br>
         * - compare the begintimes - when they are the same, compare the
         * index
         *
         * @param o the object to compare with
         *
         * @return a negative integer, zero, or a positive integer as this
         *         object is less than,  equal to, or greater than the
         *         specified object
         *
         * @throws ClassCastException if the parameter is not an
         *         IndexedExportRecord
         */
        public int compareTo(Object o) throws ClassCastException {
            if (!(o instanceof IndexedExportRecord)) {
                throw new ClassCastException(
                    "Object is not an IndexedExportRecord");
            }

            IndexedExportRecord other = (IndexedExportRecord) o;

            if (this.getBeginTime() < other.getBeginTime()) {
                return -1;
            } else if (this.getBeginTime() > other.getBeginTime()) {
                return 1;
            }

            if (this.index < other.getIndex()) {
                return -1;
            } else if (this.index > other.getIndex()) {
                return 1;
            }

            return 0;
        }
    }
}
