/*
 * File:     ExportShoebox.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.gui.ShoeboxMarkerDialog;

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

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

import mpi.eudico.client.util.CheckBoxTableCellRenderer;

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

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.shoebox.MarkerRecord;
import mpi.eudico.server.corpora.clomimpl.shoebox.ShoeboxTypFile;
import mpi.eudico.server.corpora.clomimpl.shoebox.ToolboxEncoderInfo;
import mpi.eudico.server.corpora.clomimpl.shoebox.interlinear.Interlinearizer;
import mpi.eudico.server.corpora.clomimpl.type.Constraint;

import mpi.eudico.util.TranscriptionUtil;

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.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Vector;

import javax.swing.ButtonGroup;
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.JRadioButton;
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 to a Shoebox/Toolbox file.
 *
 * @author Han Sloetjes
 */
public class ExportShoebox extends JDialog implements ActionListener,
    ItemListener, ListSelectionListener {
    private TranscriptionImpl transcription;

    /** ui elements */
    private JLabel titleLabel;
    private JPanel titlePanel;
    private JPanel tierSelectionPanel;
    private JTable tierTable;
    private TierExportTableModel model;
    private JPanel optionsPanel;
    private JLabel charPerLineLabel;
    private JCheckBox wrapLinesCB;
    private JTextField numCharTF;
    private JLabel timeFormatLabel;
    private JRadioButton ssMSFormatRB;
    private JRadioButton hhMMSSMSFormatRB;
    private JPanel buttonPanel;
    private JButton startButton;
    private JButton closeButton;
    private JButton upButton;
    private JButton downButton;
    private JPanel markerPanel;
    private JTextField typField;
    private JButton typButton;
    private JLabel typeLabel;
    private JButton fieldSpecButton;
    private JRadioButton typeRB;
    private JRadioButton specRB;
    private JRadioButton tierNamesRB;
    private JLabel tierNamesLabel;
    private JCheckBox generateMarkersCB;

    // 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";

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

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

    /**
     * Initializes UI elements.
     */
    protected void initComponents() {
        titleLabel = new JLabel();
        titlePanel = new JPanel();
        tierSelectionPanel = new JPanel();
        optionsPanel = new JPanel();
        charPerLineLabel = new JLabel();
        wrapLinesCB = new JCheckBox();
        wrapLinesCB.setSelected(true);
        numCharTF = new JTextField(4);
        timeFormatLabel = new JLabel();
        ssMSFormatRB = new JRadioButton();
        hhMMSSMSFormatRB = new JRadioButton();
        buttonPanel = new JPanel();
        startButton = new JButton();
        closeButton = new JButton();
        upButton = new JButton();
        downButton = new JButton();

        typField = new JTextField("", 23);
        typButton = new JButton("...");
        typeLabel = new JLabel();
        fieldSpecButton = new JButton();

        ButtonGroup buttonGroup = new ButtonGroup();
        typeRB = new JRadioButton();
        typeRB.setSelected(true);
        typeRB.addItemListener(this);
        specRB = new JRadioButton();
        specRB.addItemListener(this);
        tierNamesRB = new JRadioButton();
        tierNamesRB.addItemListener(this);
        buttonGroup.add(typeRB);
        buttonGroup.add(specRB);
        buttonGroup.add(tierNamesRB);
        tierNamesLabel = new JLabel();
        generateMarkersCB = new JCheckBox();

        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());

        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 = 0;
        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 = 1;
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.insets = insets;
        tierSelectionPanel.add(upButton, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 1;
        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);

        ButtonGroup group = new ButtonGroup();
        group.add(ssMSFormatRB);
        ssMSFormatRB.setSelected(true);
        group.add(hhMMSSMSFormatRB);

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

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

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.insets = insets;
        optionsPanel.add(hhMMSSMSFormatRB, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 4;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.insets = insets;
        optionsPanel.add(ssMSFormatRB, 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);

        markerPanel = new JPanel();
        markerPanel.setLayout(new GridBagLayout());

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = GridBagConstraints.NONE;
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.insets = insets;
        markerPanel.add(typeRB, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = GridBagConstraints.NONE;
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.insets = insets;
        markerPanel.add(typeLabel, gridBagConstraints);

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

        typButton.addActionListener(this);
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 3;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = GridBagConstraints.NONE;
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.insets = insets;
        markerPanel.add(typButton, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = GridBagConstraints.NONE;
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.insets = insets;
        markerPanel.add(specRB, gridBagConstraints);

        fieldSpecButton.addActionListener(this);
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = GridBagConstraints.NONE;
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.insets = insets;
        markerPanel.add(fieldSpecButton, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = GridBagConstraints.NONE;
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.insets = insets;
        markerPanel.add(tierNamesRB, gridBagConstraints);

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

        generateMarkersCB.addItemListener(this);
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.fill = GridBagConstraints.NONE;
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.insets = insets;
        markerPanel.add(generateMarkersCB, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.insets = insets;
        getContentPane().add(markerPanel, 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 = 4;
        gridBagConstraints.insets = insets;
        getContentPane().add(buttonPanel, gridBagConstraints);

        setDefaultNumOfChars();
        setEnabledAutoGenerate(false);

        setShoeboxMarkerRB();

        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 = getHierarchicallySortedTiers(transcription);

                TierImpl t;

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

                    // add all                      
                    model.addRow(new Object[] { Boolean.TRUE, 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);
        }
    }

    private Vector getHierarchicallySortedTiers(TranscriptionImpl transcription) {
        // for each root tier, find dependency tree.
        // store in a Vector with Vectors, one for each root.
        // take the largest tier tree first, this is likely to be the interlinear tree
        Vector tierTrees = new Vector();
        Vector sortedTiers = new Vector();

        Vector topTiers = TranscriptionUtil.getTopTiers(transcription);

        for (int i = 0; i < topTiers.size(); i++) {
            TierImpl topTier = (TierImpl) topTiers.elementAt(i);
            tierTrees.add(getTierTree(topTier));
        }

        Collections.sort(tierTrees, new VectorComparator());

        for (int j = 0; j < tierTrees.size(); j++) {
            sortedTiers.addAll((Vector) tierTrees.elementAt(j));
        }

        return sortedTiers;
    }

    public Vector getTierTree(TierImpl tier) {
        Vector tierTree = new Vector();
        Vector tierTrees = new Vector();

        Vector children = tier.getChildTiers();

        tierTree.add(tier);

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

        Collections.sort(tierTrees, new VectorComparator());

        for (int j = 0; j < tierTrees.size(); j++) {
            tierTree.addAll((Vector) tierTrees.elementAt(j));
        }

        return tierTree;
    }

    /**
     * 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();
    }

    private void setShoeboxMarkerRB() {
        if ((ShoeboxTypFile.getMarkers() != null) &&
                (ShoeboxTypFile.getMarkers().size() > 0)) {
            specRB.setSelected(true);
        } else if ((ShoeboxTypFile.getTypFileName() != null) &&
                !ShoeboxTypFile.getTypFileName().equals("")) {
            typeRB.setSelected(true);
            typField.setText(ShoeboxTypFile.getTypFileName());
        } else {
            tierNamesRB.setSelected(true);
        }
    }

    /**
     * Applies localized strings to the ui elements. For historic reasons the
     * string identifiers start with "TokenizeDialog"
     */
    protected void updateLocale() {
        setTitle(ElanLocale.getString("ExportShoebox.Title"));
        titleLabel.setText(ElanLocale.getString("ExportShoebox.Title"));
        tierSelectionPanel.setBorder(new TitledBorder(ElanLocale.getString(
                    "ExportShoebox.Label.SelectTiers")));
        optionsPanel.setBorder(new TitledBorder(ElanLocale.getString(
                    "ExportShoebox.Label.Options")));
        markerPanel.setBorder(new TitledBorder(ElanLocale.getString(
                    "ExportShoebox.Label.Markers")));
        wrapLinesCB.setText(ElanLocale.getString(
                "ExportShoebox.Label.WrapBlocks"));
        charPerLineLabel.setText(ElanLocale.getString(
                "ExportShoebox.Label.NumberChars"));
        timeFormatLabel.setText(ElanLocale.getString(
                "ExportShoebox.Label.Format"));
        hhMMSSMSFormatRB.setText(ElanLocale.getString(
                "InterlinearizerOptionsDlg.TimeCodeFormat.TimeCode"));
        ssMSFormatRB.setText(ElanLocale.getString(
                "InterlinearizerOptionsDlg.TimeCodeFormat.Seconds"));
        typeLabel.setText(ElanLocale.getString("ExportShoebox.Label.Type"));
        fieldSpecButton.setText(ElanLocale.getString(
                "ExportShoebox.Button.FieldSpec"));
        tierNamesLabel.setText(ElanLocale.getString(
                "ExportShoebox.Label.UseTierNames"));
        generateMarkersCB.setText(ElanLocale.getString(
                "ExportShoebox.CheckBox.AutoGenerateMarkers"));

        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 (!checkMarkerFields()) {
            return false;
        }

        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(
                    "ExportShoebox.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(
                        "ExportShoebox.Message.InvalidNumber"));
                numCharTF.selectAll();
                numCharTF.requestFocus();

                return false;
            }
        }

        int timeFormat = Interlinearizer.SSMS;

        if (hhMMSSMSFormatRB.isSelected()) {
            timeFormat = Interlinearizer.HHMMSSMS;
        }

        // 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, timeFormat);
    }

    /**
     * Checks the contents of marker input fields and next the existence of the
     * designated files.
     *
     * @return true if the files exist, false otherwise
     */
    private boolean checkMarkerFields() {
        if (typeRB.isSelected() &&
                ((typField.getText() == null) ||
                (typField.getText().length() == 0))) {
            showError(ElanLocale.getString("ImportDialog.Message.SpecifyType"));

            return false;
        }

        if (typeRB.isSelected()) {
            if (!(new File(typField.getText()).exists())) {
                showError(ElanLocale.getString("ImportDialog.Message.NoType"));

                return false;
            } else {
                ShoeboxTypFile.setTypFileName(typField.getText());
            }
        } else { // specify field names is selected
            ShoeboxTypFile.setTypFileName("");
        }

        return true;
    }

    /**
     * Shows an error dialog.
     *
     * @param message
     */
    private void showError(String message) {
        JOptionPane.showMessageDialog(this, message,
            ElanLocale.getString("Message.Error"), JOptionPane.ERROR_MESSAGE);
    }

    /**
     * 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");
        }

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

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

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

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

            exportFile = chooser.getSelectedFile();

            if (exportFile != null) {
                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;
        }
    }

    private void chooseTyp() {
        String lastUsedDir = (String) Preferences.get("LastUsedShoeboxTypDir",
                null);

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

        JFileChooser chooser = new JFileChooser(lastUsedDir);
        chooser.setDialogTitle(ElanLocale.getString("ImportDialog.Title.Select"));
        chooser.setFileFilter(ElanFileFilter.createFileFilter(
                ElanFileFilter.SHOEBOX_TYP_TYPE));
        chooser.setApproveButtonText(ElanLocale.getString(
                "ImportDialog.Approve"));
        chooser.setDialogType(JFileChooser.OPEN_DIALOG);

        int option = chooser.showDialog(this, null);

        if (option == JFileChooser.APPROVE_OPTION) {
            File curDir = chooser.getCurrentDirectory();

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

            File f = chooser.getSelectedFile();

            if (f != null) {
                typField.setText(f.getAbsolutePath());
                ShoeboxTypFile.setTypFileName(f.getAbsolutePath());
            }
        }
    }

    private void specifyFieldSpecs() {
        ShoeboxTypFile.setTypFileName("");
        new ShoeboxMarkerDialog(null, true).setVisible(true);
    }

    private void autoGenerateMarkerFile() {
        // generate marker records for each tier.
        // only marker, parent marker and stereotype have to be set, rest is default
        Vector markerRecords = new Vector();

        try {
            Vector tiers = transcription.getTiers();

            for (int i = 0; i < tiers.size(); i++) {
                TierImpl t = (TierImpl) tiers.elementAt(i);

                MarkerRecord mkrRecord = new MarkerRecord();
                mkrRecord.setMarker(t.getName());

                if (t.hasParentTier()) {
                    mkrRecord.setParentMarker(t.getParentTier().getName());

                    if (t.getLinguisticType() != null) {
                        int stereotype = t.getLinguisticType().getConstraints()
                                          .getStereoType();

                        if ((stereotype == Constraint.SYMBOLIC_SUBDIVISION) ||
                                (stereotype == Constraint.TIME_SUBDIVISION) ||
                                (stereotype == Constraint.INCLUDED_IN)) {
                            //mkrRecord.setStereoType(Constraint.publicStereoTypes[2]);
                            mkrRecord.setStereoType(Constraint.stereoTypes[Constraint.SYMBOLIC_SUBDIVISION]);
                        } else if (stereotype == Constraint.SYMBOLIC_ASSOCIATION) {
                            //mkrRecord.setStereoType(Constraint.publicStereoTypes[3]);
                            mkrRecord.setStereoType(Constraint.stereoTypes[Constraint.SYMBOLIC_ASSOCIATION]);
                        }
                    }
                }

                mkrRecord.setCharset(MarkerRecord.UNICODESTRING);
                mkrRecord.setParticipantMarker(false);
                mkrRecord.setExcluded(false);

                markerRecords.add(mkrRecord);
            }

            // store in mkr file with name of transcription, next to eaf
            String fileName = transcription.getPathName();

            if (fileName.toLowerCase().endsWith(".eaf")) {
                fileName = fileName.substring(0, fileName.lastIndexOf("."));
            }

            fileName += ".mkr";

            final File newSaveFile = new File(fileName);

            if (newSaveFile != null) {
                if (newSaveFile.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;
                    }
                }

                FileOutputStream out = new FileOutputStream(newSaveFile);
                BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
                            out, "UTF-8"));

                Iterator markerIter = markerRecords.iterator();

                while (markerIter.hasNext()) {
                    writer.write(((MarkerRecord) markerIter.next()).toString());
                }

                writer.close();
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * 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) {
                if (generateMarkersCB.isSelected()) {
                    autoGenerateMarkerFile();
                }

                closeDialog(null);
            } else {
                // show message
            }
        } else if (source == closeButton) {
            closeDialog(null);
        } else if (source == upButton) {
            moveUp();
        } else if (source == downButton) {
            moveDown();
        } else if (source == typButton) {
            chooseTyp();
            typeRB.setSelected(true);
        } else if (source == fieldSpecButton) {
            specifyFieldSpecs();
            specRB.setSelected(true);
        }
    }

    /**
     * The item state changed handling.
     *
     * @param ie the ItemEvent
     */
    public void itemStateChanged(ItemEvent ie) {
        if (ie.getSource() == wrapLinesCB) {
            if (wrapLinesCB.isSelected()) {
                setDefaultNumOfChars();
                numCharTF.requestFocus();
            } else {
                numCharTF.setEnabled(false);
                numCharTF.setBackground(Constants.DEFAULTBACKGROUNDCOLOR);
            }
        } else if (ie.getSource() == typeRB) {
            setEnabledAutoGenerate(false);
        } else if (ie.getSource() == specRB) {
            setEnabledAutoGenerate(false);
        } else if (ie.getSource() == tierNamesRB) {
            setEnabledAutoGenerate(true);
        }
    }

    private void setDefaultNumOfChars() {
        numCharTF.setEnabled(true);
        numCharTF.setBackground(Constants.SHAREDCOLOR4);

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

    private void setEnabledAutoGenerate(boolean enable) {
        generateMarkersCB.setSelected(false);
        generateMarkersCB.setEnabled(enable);
    }

    /**
     * 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.
     *
     * @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,
        final int timeFormat) {
        int markerSource = ToolboxEncoderInfo.TIERNAMES; // default

        if (typeRB.isSelected()) {
            markerSource = ToolboxEncoderInfo.TYPFILE;
        } else if (specRB.isSelected()) {
            markerSource = ToolboxEncoderInfo.DEFINED_MARKERS;
        }

        ToolboxEncoderInfo tbEncoderInfo = new ToolboxEncoderInfo(charsPerLine,
                markerSource, timeFormat);

        if (fileName != null) {
            new ACM23TranscriptionStore().storeTranscriptionIn(transcription,
                tbEncoderInfo, new Vector(orderedTiers), fileName,
                TranscriptionStore.SHOEBOX);
        }

        return true;
    }

    //***********************
    // inner classes
    //***********************	
    class VectorComparator implements Comparator {
        /**
         * Compares Vectors, on basis of their size. The largest one comes first
         *
         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
         */
        public int compare(Object arg0, Object arg1) {
            Vector v0 = (Vector) arg0;
            Vector v1 = (Vector) arg1;

            if (v0.size() < v1.size()) {
                return 1;
            }

            if (v0.size() > v1.size()) {
                return -1;
            }

            return 0;
        }
    }
}
