/*
 * File:     Interlinear.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.interlinear;

import mpi.eudico.client.annotator.Preferences;

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

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

import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Vector;


/**
 * Main object for options for interlinearisation.  Creates a BlockMetrics
 * object that performs calculations etc. for interlinear layout.
 *
 * @author MPI
 */
public class Interlinear {
    // constants
    // units for page width and height

    /** constant for centimeter layout unit */
    public static final int CM = 0;

    /** constant for inch layout unit */
    public static final int INCH = 1;

    /** constant for pixel layout unit */
    public static final int PIXEL = 2;

    /** constant for character layout unit */
    public static final int NUM_CHARACTERS = 3;

    // wrap styles for blocks and lines

    /** block wrap style where each block start at a new 'block line' */
    public static final int EACH_BLOCK = 0;

    /**
     * block wrap style where where wrapping to a new line occurs when the next
     * block does not  fit completely on the current 'block line'
     */
    public static final int BLOCK_BOUNDARY = 1;

    /**
     * block wrap style where block wrapping only occurs when the next
     * annotations do not fit  on the current 'block lline'!
     */
    public static final int WITHIN_BLOCKS = 2;

    /**
     * block and line wrap style where no wrapping is applied; all annotations
     * are positions  on a single 'block line'
     */
    public static final int NO_WRAP = 3;

    // wrap styles for lines

    /**
     * line wrap style where a line that does not fit on the current line is
     * wrapped to the next
     */
    public static final int NEXT_LINE = 4;

    // time code types

    /** the hour/minutes/seconds/milliseconds format */
    public static final int HHMMSSMS = 0;

    /** the seconds/milliseconds format */
    public static final int SSMS = 1;

    /** the pure milliseconds format */
    public static final int MS = 2;

    // unit for text alignment

    /** constant for pixel based text alignment */
    public static final int PIXELS = 0;

    /** constant for bytes based text alignment */
    public static final int BYTES = 1;

    /** constant for character based alignment */
    public static final int CHARACTERS = 2;

    // font
    //	public static Font DEFAULTFONT = new Font("SansSerif", Font.PLAIN, 12);

    /** constant for the default font size for annotations */
    public static final int DEFAULT_FONT_SIZE = 12;

    /** the default font for printing */
    public static Font DEFAULTFONT = new Font("MS Arial Unicode", Font.PLAIN,
            DEFAULT_FONT_SIZE);

    /** the default preview font for character based alignment! */
    public static Font MONOSPACED_FONT = new Font("Monospaced", Font.PLAIN,
            DEFAULT_FONT_SIZE);

    /** the default font size for time codes */
    public static final int TIMECODE_FONT_SIZE = 10;

    // empty line style

    /** constant for template style empty line style */
    public static final int TEMPLATE = 0;

    /** constant for an empty line style where empty lines are hidden */
    public static final int HIDE_EMPTY_LINES = 1;

    // sorting style

    /** sorting as read from file */
    public static final int EXTERNALLY_SPECIFIED = 0;

    /** sorting according t otier dependencies */
    public static final int TIER_HIERARCHY = 1;

    /** sorting by linguistic type */
    public static final int BY_LINGUISTIC_TYPE = 2;

    /** sorting by participant */
    public static final int BY_PARTICIPANT = 3;

    /** random user defined sorting */
    public static final int USER_DEFINED = 4;

    /** constant for the mode where all timecodes are on a single line */
    public static final int SINGLE_TIMECODE = 0;

    /** constant for a timecode per root tier */
    public static final int MULTIPLE_TIMECODE = 1;

    // character encoding

    /** output text encoding in utf8 */
    public static final int UTF8 = 0;

    /** iso-latin encoding */
    public static final int ISOLATIN = 1;

    /** sil-ipa encoding */
    public static final int SIL = 2;

    // output modes

    /** preview/output mode for printing */
    public static final int PRINT = 100;

    /** preview output mode for interlinear text */
    public static final int INTERLINEAR_TEXT = 101;

    /** output mode for shoebox */
    public static final int SHOEBOX_TEXT = 102;

    // constants for text out

    /** default line width */
    public static final int DEFAULT_NUM_CHARS = 80;

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

    /** default number of newlines between blocks */
    public static final int DEFAULT_TEXT_BLOCK_SPACING = 2;

    /** the transcription */
    private final TranscriptionImpl transcription;
    private int width;
    private int height;
    private ArrayList visibleTiers;
    private boolean tierLabelsShown;
    private long[] visibleTimeInterval;
    private int blockWrapStyle;
    private int lineWrapStyle;
    private boolean timeCodeShown;
    private int timeCodeType;
    private HashMap fonts;
    private HashMap fontSizes;
    private boolean emptySlotsShown;
    private int lineSpacing;
    private int blockSpacing = -1;
    private Annotation activeAnnotation;
    private long[] selection;
    private long mediaTime;
    private int alignmentUnit;
    private int emptyLineStyle; // show full 'template' for block, or hide empty lines
    private int sortingStyle;
    private HashMap charEncodings;
    private int outputMode;
    private int timeCodeMultiplicity;
    private BlockMetrics metrics;
    private int pageHeight;
    private boolean selectionOnly = false;

    // preferences keys

    /** pref key */
    final String prefSelectionOnly = "Interlinear.SelectionOnly";

    /** pref key */
    final String prefLabelsShown = "Interlinear.LabelsShown";

    /** pref key */
    final String prefTimeCodeShown = "Interlinear.TimeCodeShown";

    /** pref key */
    final String prefTimeCodeType = "Interlinear.TimeCodeType";

    /** pref key */
    final String prefBlockWrapStyle = "Interlinear.BlockWrapStyle";

    /** pref key */
    final String prefLineWrapStyle = "Interlinear.LineWrapStyle";

    /** pref key */
    final String prefBlockSpacing = "Interlinear.BlockSpacing";

    /** pref key */
    final String prefBlockSpacingTextOut = "Interlinear.BlockSpacing.Text";

    /** pref key */
    final String prefLineSpacing = "Interlinear.LineSpacing";

    /** pref key */
    final String prefTierSortingStyle = "Interlinear.TierSortingStyle";

    /** pref key */
    final String prefVisibleTiers = "Interlinear.VisibleTiers";

    /** pref key */
    final String prefTierOrder = "Interlinear.TierOrder";

    /** pref key */
    final String prefFontSizes = "Interlinear.FontSizes";

    /** pref key */
    final String prefNumCharPerLine = "Interlinear.NumCharPerLine";

    /**
     * Creates a new Interlinear instance. Default mode is Print mode
     *
     * @param tr the transcription holding tiers and annotations.
     */
    public Interlinear(TranscriptionImpl tr) {
        this(tr, PRINT);
    }

    /**
     * Creates a new Interlinear instance.
     *
     * @param tr the transcription holding tiers and annotations.
     * @param mode one of <code>PRINT</code>, <code>INTERLINEAR_TEXT</code> or
     *        <code>SHOEBOX_TEXT</code>
     */
    public Interlinear(TranscriptionImpl tr, int mode) {
        transcription = tr;

        if ((mode < PRINT) || (mode > SHOEBOX_TEXT)) {
            outputMode = PRINT;
        } else {
            outputMode = mode;
        }

        metrics = new BlockMetrics(this);
        setDefaultValues();
    }

    /**
     * Resets the Metrics object.
     */
    private void resetMetrics() {
        metrics.reset();
    }

    /**
     * Initialises default values.
     */
    private void setDefaultValues() {
        if (outputMode == PRINT) {
            width = 550;
            pageHeight = 600;
            height = pageHeight;
            alignmentUnit = PIXELS;
            blockSpacing = 0;
        } else if (outputMode == INTERLINEAR_TEXT) {
            width = DEFAULT_NUM_CHARS;
            height = 800;
            pageHeight = height;
            alignmentUnit = CHARACTERS;
        } else {
            //shoebox
            width = DEFAULT_NUM_CHARS;
            height = 800;
            pageHeight = height;
            alignmentUnit = BYTES;
        }

        tierLabelsShown = true;
        blockWrapStyle = BLOCK_BOUNDARY;
        lineWrapStyle = NEXT_LINE;
        timeCodeShown = false;
        timeCodeType = HHMMSSMS;
        emptySlotsShown = false;
        emptyLineStyle = HIDE_EMPTY_LINES;
        sortingStyle = EXTERNALLY_SPECIFIED;

        timeCodeMultiplicity = MULTIPLE_TIMECODE;
        visibleTiers = new ArrayList();

        // defaults for font and fontsizes
        fonts = new HashMap();
        fontSizes = new HashMap();
        charEncodings = new HashMap();

        // set default visible tiers to all tier names
        if (transcription != null) {
            visibleTiers.addAll(transcription.getTiers());

            Iterator tierIt = visibleTiers.iterator();
            TierImpl t;

            while (tierIt.hasNext()) {
                t = (TierImpl) tierIt.next();

                if (outputMode == PRINT) {
                    setFont(t.getName(), DEFAULTFONT);
                } else {
                    setFont(t.getName(), MONOSPACED_FONT);
                }

                setFontSize(t.getName(), DEFAULT_FONT_SIZE);
            }
        }

        loadPreferences();
    }

    /**
     * Since Interlinear can be used with or withou a ui, this method should be
     * called  by other objects: the ui/dialog it is in (on closing/disposing
     * of the ui) or by the CommandAction that created this Interlinear
     * object.
     */
    public void savePreferences() {
        Preferences.set(prefSelectionOnly, new Boolean(selectionOnly),
            transcription);
        Preferences.set(prefBlockWrapStyle, blockWrapStyle, null);
        Preferences.set(prefLabelsShown, new Boolean(tierLabelsShown), null);
        Preferences.set(prefLineSpacing, lineSpacing, null);
        Preferences.set(prefLineWrapStyle, lineWrapStyle, null);
        Preferences.set(prefTierSortingStyle, sortingStyle, null);
        Preferences.set(prefTimeCodeType, timeCodeType, null);

        //  ordered, visible tiers
        String[] tierNames = new String[visibleTiers.size()];

        for (int i = 0; i < visibleTiers.size(); i++) {
            tierNames[i] = ((Tier) visibleTiers.get(i)).getName();
        }

        Preferences.set(prefVisibleTiers, tierNames, transcription);

        if (outputMode == PRINT) {
            Preferences.set(prefFontSizes, fontSizes, transcription);
            Preferences.set(prefBlockSpacing, blockSpacing, null);
        } else {
            Preferences.set(prefNumCharPerLine, width, null);
            Preferences.set(prefBlockSpacingTextOut, blockSpacing, null);
        }
    }

    /**
     * Load stored preferences.
     */
    void loadPreferences() {
        Object curPref;
        curPref = Preferences.get(prefSelectionOnly, transcription);

        if (curPref instanceof Boolean) {
            setSelectionOnly(((Boolean) curPref).booleanValue());
        }

        curPref = Preferences.get(prefBlockWrapStyle, null);

        if (curPref instanceof Integer) {
            setBlockWrapStyle(((Integer) curPref).intValue());
        }

        curPref = Preferences.get(prefLineWrapStyle, null);

        if (curPref instanceof Integer) {
            setLineWrapStyle(((Integer) curPref).intValue());
        }

        curPref = Preferences.get(prefLabelsShown, null);

        if (curPref instanceof Boolean) {
            setTierLabelsShown(((Boolean) curPref).booleanValue());
        }

        curPref = Preferences.get(prefTimeCodeShown, null);

        if (curPref instanceof Boolean) {
            setTimeCodeShown(((Boolean) curPref).booleanValue());
        }

        curPref = Preferences.get(prefTimeCodeType, null);

        if (curPref instanceof Integer) {
            setTimeCodeType(((Integer) curPref).intValue());
        }

        curPref = Preferences.get(prefVisibleTiers, transcription);

        if (curPref instanceof String[]) {
            // setVisibleTiers((String[]) curPref);
            String[] vis = (String[]) curPref;
            String name = null;
            TierImpl t = null;

            ArrayList allVis = new ArrayList();

            for (int i = 0; i < vis.length; i++) {
                name = vis[i];

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

                if (t != null) {
                    allVis.add(t);
                }
            }

            Object allOrdered = Preferences.get(prefTierOrder, transcription);
            ArrayList at = new ArrayList();

            if (allOrdered instanceof ArrayList) {
                at = (ArrayList) allOrdered; // saved ordered list of tiers
            }

            // add new tiers to the list of visible tiers
            Vector v = transcription.getTiers();

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

                if (!allVis.contains(t) && !at.contains(name)) {
                    allVis.add(t);
                }
            }

            setVisibleTiers(allVis);
        }

        if (outputMode == PRINT) {
            curPref = Preferences.get(prefFontSizes, transcription);

            if (curPref instanceof HashMap) {
                fontSizes = (HashMap) curPref;

                Iterator keysIt = fontSizes.keySet().iterator();
                String tierName;
                Object size;

                while (keysIt.hasNext()) {
                    tierName = (String) keysIt.next();
                    size = fontSizes.get(tierName);

                    if (size instanceof Integer) {
                        setFontSize(tierName, ((Integer) size).intValue());
                    }
                }
            }

            curPref = Preferences.get(prefLineSpacing, null);

            if (curPref instanceof Integer) {
                setLineSpacing(((Integer) curPref).intValue());
            }

            curPref = Preferences.get(prefBlockSpacing, null);

            if (curPref instanceof Integer) {
                setBlockSpacing(((Integer) curPref).intValue());
            }
        }

        if (outputMode != PRINT) {
            curPref = Preferences.get(prefNumCharPerLine, null);

            if (curPref instanceof Integer) {
                setWidth(((Integer) curPref).intValue());
            }

            curPref = Preferences.get(prefBlockSpacingTextOut, null);

            if (curPref instanceof Integer) {
                setBlockSpacing(((Integer) curPref).intValue());
            }
        }
    }

    /**
     * Initialises a reset and recalculation of the annotation blocks and
     * pages, using the graphics of the specified BufferedImage.
     *
     * @param bi the BufferedImage object, provided by a preview renderer
     */
    public void renderView(BufferedImage bi) {
        calculateMetrics(bi.getGraphics());
    }

    /**
     * Initialises a reset and recalculation of the annotation blocks,  based
     * on character alignment.
     */
    public void renderView() {
        calculateMetrics();
    }

    /**
     * Calls a suitable renderer to render the contents of the specified page
     * to the specified Graphics object.
     *
     * @param g the Graphics object
     * @param pageIndex the page to render
     *
     * @return true if the page was successfully rendered
     */
    public boolean renderPage(Graphics g, int pageIndex) {
        if (alignmentUnit == PIXELS) { // call to renderView should be consistent with params
            PixelRenderer.render(this, g, pageIndex);

            return true;
        }

        return false;
    }

    /**
     * Calculate annotation widths in blocks (including dependent annotations),
     * calculate print blocks, a combination of annotation blocks that fit
     * (partially) on a block line and calculate pages / page breaks.
     *
     * @param graphics the graphics object to use for the calculations
     */
    protected void calculateMetrics(Graphics graphics) {
        resetMetrics();

        metrics.calculateAnnotationBlocks(graphics);

        metrics.calculatePrintBlocks();

        metrics.calculatePageBreaks();

        setHeight(pageHeight * metrics.getPageBreaks().size());
    }

    /**
     * Calculates metrics based on byte-wise or character wise alignment.
     */
    protected void calculateMetrics() {
        resetMetrics();

        metrics.calculateAnnotationBlocks(null);

        metrics.calculatePrintBlocks();

        // this creates a single page breaks object
        metrics.calculatePageBreaks();
    }

    /**
     * Renders the view to the specifeid BufferedImage using the specied
     * offsets. In case of a character based interlinearization the
     * <code>PixelRenderer</code> converts the character positions to pixel
     * based  positions in order to create a preview of the final text output.
     *
     * @param bi the BufferedImage object to render to
     * @param offset the hor. and vert. offset
     */
    public void drawViewOnImage(BufferedImage bi, int[] offset) {
        if (alignmentUnit == PIXELS) { // call to renderView should be consistent with params
            PixelRenderer.render(this, bi, offset);
        } else {
            PixelRenderer.renderCharacterPreview(this, bi, offset);
        }
    }

    // getters and setters

    /**
     * Returns the active annotation
     *
     * @return the active annotation
     */
    public Annotation getActiveAnnotation() {
        return activeAnnotation;
    }

    /**
     * Returns the alignment unit type.
     *
     * @return the alignment unit type, one of <code>PIXELS</code>,
     *         <code>BYTES</code> or <code>CHARACTERS</code>
     */
    public int getAlignmentUnit() {
        return alignmentUnit;
    }

    /**
     * Returns the block wrap style type.
     *
     * @return the block wrap style type, one of <code>NO_WRAP</code>,
     *         <code>EACH_BLOCK</code>,  <code>BLOCK_BOUNDARIES</code> or
     *         <code>WITHIN_BLOCKS</code>
     */
    public int getBlockWrapStyle() {
        return blockWrapStyle;
    }

    /**
     * Returns whether empty slots are shown.
     *
     * @return true when empty slot are painted, false otherwise
     */
    public boolean isEmptySlotsShown() {
        return emptySlotsShown;
    }

    /**
     * Returns the Font to use for the specified tier.
     *
     * @param tierName the name of the tier
     *
     * @return the font for printing or preview
     */
    public Font getFont(String tierName) {
        Font f = (Font) fonts.get(tierName);

        if (f == null) { // use default font
            f = DEFAULTFONT;
        }

        return f;
    }

    /**
     * Returns the font size used for the specified tier
     *
     * @param tierName the name of the tier
     *
     * @return the size of the font for this tier
     */
    public int getFontSize(String tierName) {
        int size = 0;
        Integer sizeInt = (Integer) fontSizes.get(tierName);

        if (sizeInt != null) {
            size = sizeInt.intValue();
        } else {
            size = DEFAULT_FONT_SIZE;
        }

        return size;
    }

    /**
     * Returns the total height necessary for rendering
     *
     * @return the total height
     */
    public int getHeight() {
        if (height > 0) {
            return height;
        } else {
            return 0;
        }
    }

    /**
     * Returns the number of pixels to add between lines.
     *
     * @return the number of pixels to add between lines
     */
    public int getLineSpacing() {
        return lineSpacing;
    }

    /**
     * Returns the number of pixels to add between blocks.
     *
     * @return the number of pixels to add between blocks.
     */
    public int getBlockSpacing() {
        if (blockSpacing < 0) { // default: derived from line spacing

            if (outputMode == PRINT) {
                return 20 + (3 * getLineSpacing());
            } else {
                return DEFAULT_TEXT_BLOCK_SPACING;
            }
        } else {
            return blockSpacing;
        }
    }

    /**
     * Set the number of pixels to add between blocks.
     *
     * @param blockSpacing the number of pixels to add between blocks.
     */
    public void setBlockSpacing(int blockSpacing) {
        this.blockSpacing = blockSpacing;
    }

    /**
     * Returns the line wrap style.
     *
     * @return either <code>NO_WRAP</code> or <code>NEXT_LINE</code>
     */
    public int getLineWrapStyle() {
        return lineWrapStyle;
    }

    /**
     * Returns the current media time.
     *
     * @return the current media time
     */
    public long getMediaTime() {
        return mediaTime;
    }

    /**
     * Returns the selection begin and end time.
     *
     * @return the selection begin and end time
     */
    public long[] getSelection() {
        return selection;
    }

    /**
     * Returns whether or not the tier labels should be included in the output.
     *
     * @return true if the tier labels are included in the left margin, false
     *         otherwise
     */
    public boolean isTierLabelsShown() {
        return tierLabelsShown;
    }

    /**
     * Returns whether or not time information of each block should be included
     * in the  output.
     *
     * @return true if time information is included in the output, false
     *         otherwise
     */
    public boolean isTimeCodeShown() {
        return timeCodeShown;
    }

    /**
     * Returns the type of time codes to use.
     *
     * @return the timecode type, one of <code>HHMMSSMS</code>, or
     *         <code>SSMS</code>
     */
    public int getTimeCodeType() {
        return timeCodeType;
    }

    /**
     * Returns a list of the currently visible tiers.
     *
     * @return a sorted list of the visible tier objects
     */
    public ArrayList getVisibleTiers() {
        return visibleTiers;
    }

    /**
     * Returns the currently visible time interval.
     *
     * @return the currently visible time interval, begin and end time
     */
    public long[] getVisibleTimeInterval() {
        return visibleTimeInterval;
    }

    /**
     * Returns the width of the output area, either in pixels or in number of
     * bytes or characters
     *
     * @return the output width
     */
    public int getWidth() {
        if (width > 0) {
            return width;
        } else {
            // find width from max horizontally used space
            //return metrics.getMaxHorizontallyUsedWidth();
            return 0;
        }
    }

    /**
     * Sets the active annotation.
     *
     * @param annotation the active annotation
     */
    public void setActiveAnnotation(Annotation annotation) {
        activeAnnotation = annotation;
    }

    /**
     * Sets the alignment unit.
     *
     * @param i the new alignment unit
     *
     * @see #getAlignmentUnit()
     */
    public void setAlignmentUnit(int i) {
        alignmentUnit = i;
    }

    /**
     * Sets the block wrap style type.
     *
     * @param i the new block wrap style type
     *
     * @see #getBlockWrapStyle()
     */
    public void setBlockWrapStyle(int i) {
        blockWrapStyle = i;
    }

    /**
     * Sets the empty slots shown value.
     *
     * @param b the new empty slots shown value
     *
     * @see #getEmptyLineStyle()
     */
    public void setEmptySlotsShown(boolean b) {
        emptySlotsShown = b;
    }

    /**
     * Sets the (total) height necessary for the output.
     *
     * @param i the new height
     */
    public void setHeight(int i) {
        height = i;
    }

    /**
     * Sets the number of pixels to insert between lines.
     *
     * @param i the space between two lines
     */
    public void setLineSpacing(int i) {
        lineSpacing = i;
    }

    /**
     * Sets the line wrap style.
     *
     * @param i the line wrap style
     *
     * @see #getLineWrapStyle()
     */
    public void setLineWrapStyle(int i) {
        lineWrapStyle = i;
    }

    /**
     * Sets the current media time.
     *
     * @param l the time in ms
     */
    public void setMediaTime(long l) {
        mediaTime = l;
    }

    /**
     * Sets the selction begin and end time
     *
     * @param ls array of length 2, begin and end time
     */
    public void setSelection(long[] ls) {
        selection = ls;
    }

    /**
     * Sets the visibility of the tier labels.
     *
     * @param show if true include the labels
     */
    public void setTierLabelsShown(boolean show) {
        tierLabelsShown = show;
    }

    /**
     * Sets whether time code information should be included in the output.
     *
     * @param b when true time codes are included in the output
     */
    public void setTimeCodeShown(boolean b) {
        timeCodeShown = b;
    }

    /**
     * Sets the format for the time codes.
     *
     * @param i the format type
     *
     * @see #getTimeCodeType()
     */
    public void setTimeCodeType(int i) {
        timeCodeType = i;
    }

    /**
     * Sets the visible tiers.
     *
     * @param strings array of the names of the visible tiers
     */
    public void setVisibleTiers(String[] strings) {
        visibleTiers.clear();

        if (strings != null) {
            for (int i = 0; i < strings.length; i++) {
                Tier t = transcription.getTierWithId(strings[i]);

                if (t != null) {
                    visibleTiers.add(t);
                }
            }
        }
    }

    /**
     * Sets the visible tiers.
     *
     * @param visTiers a list of tier objects
     */
    public void setVisibleTiers(ArrayList visTiers) {
        if (visTiers != null) {
            visibleTiers = visTiers;
        } else {
            visibleTiers.clear();
        }
    }

    /**
     * Sets the visible time interval.
     *
     * @param ls the begin and end time of the visible time interval, in ms
     */
    public void setVisibleTimeInterval(long[] ls) {
        visibleTimeInterval = ls;
    }

    /**
     * Sets the width of the document, in pixels, bytes or characters
     *
     * @param i the new width
     *
     * @see #getWidth()
     */
    public void setWidth(int i) {
        width = i;
    }

    /**
     * Creates a font object to use for the specified tier, using the
     * preferred, user defined font size.
     *
     * @param tierName the name of the tier
     * @param f the base font object for the tier
     */
    public void setFont(String tierName, Font f) {
        int fontSize = getFontSize(tierName);
        f = f.deriveFont((float) fontSize);

        fonts.put(tierName, f);
    }

    /**
     * Sets the font size for the specified tier.
     *
     * @param tierName the namew of the tier
     * @param size the size for the font
     */
    public void setFontSize(String tierName, int size) {
        fontSizes.put(tierName, new Integer(size));
        fonts.put(tierName, getFont(tierName).deriveFont((float) size));
    }

    /**
     * Returns the empty line style.
     *
     * @return the empty line style, either <code>TEMPLATE</code> or
     *         <code>HIDE_EMPTY_LINES</code>
     */
    public int getEmptyLineStyle() {
        return emptyLineStyle;
    }

    /**
     * Sets the empty line style.
     *
     * @param i empty line style, either<code>TEMPLATE</code> or
     *        <code>HIDE_EMPTY_LINES</code>
     */
    public void setEmptyLineStyle(int i) {
        emptyLineStyle = i;
    }

    /**
     * Returns the sorting style (sorting of the tiers).
     *
     * @return the sorting style, either <code>EXTERNALLY_SPECIFIED</code>,
     *         <code>TIER_HIERARCHY</code>, <code>BY_LINGUISTIC_TYPE</code> or
     *         <code>BY_PARTICIPANT</code>
     */
    public int getSortingStyle() {
        return sortingStyle;
    }

    /**
     * Sets the sorting style for the tiers.
     *
     * @param i the new sorting style
     *
     * @see #getSortingStyle()
     */
    public void setSortingStyle(int i) {
        sortingStyle = i;
    }

    /**
     * Returns the Transcription object.
     *
     * @return the Transcription object
     */
    public TranscriptionImpl getTranscription() {
        return transcription;
    }

    /**
     * Returns the output mode.
     *
     * @return the output mode, either <code>PRINT</code>,
     *         <code>INTERLINEAR_TEXT</code>  or <code>SHOEBOX_TEXT</code>
     */
    public int getOutputMode() {
        return outputMode;
    }

    /**
     * Sets the output mode.
     *
     * @param mode the output mode
     *
     * @see #getOutputMode()
     */
    public void setOutputMode(int mode) {
        outputMode = mode;
    }

    /**
     * Returns the page height, in case of <code>PRINT</code> mode.
     *
     * @return the page height in pixels
     */
    public int getPageHeight() {
        return pageHeight;
    }

    /**
     * Sets the page heigth.
     *
     * @param height page height in pixels
     */
    public void setPageHeight(int height) {
        pageHeight = height;
    }

    /**
     * Returns the character encoding for a particular tier.
     *
     * @param tierName the tier to find the encoding for
     *
     * @return the character encoding
     */
    public int getCharEncoding(String tierName) {
        int encoding = UTF8;

        if (tierName == null) {
            return encoding; // UTF8 is always the default
        }

        if (!charEncodings.containsKey(tierName)) {
            return encoding;
        }

        Integer encodingInt = (Integer) charEncodings.get(tierName);

        if (encodingInt != null) {
            encoding = encodingInt.intValue();
        }

        return encoding;
    }

    /**
     * Sets the character encoding for a tier.
     *
     * @param tierName the tier to specify the encoding for
     * @param charEncoding the new encoding, either <code>UTF8</code>
     *        (default),  <code>ISOLATIN</code>, <code>SIL</code>
     */
    public void setCharEncoding(String tierName, int charEncoding) {
        charEncodings.put(tierName, new Integer(charEncoding));
    }

    /**
     * Returns the empty horizontal space between neightbouring annotations.
     *
     * @return the empty horizontal space between neightbouring annotations, in
     *         pixels or in number of characters
     */
    public int getEmptySpace() {
        int emptySpace = 10; // 10 pixels in case of image

        if ((getAlignmentUnit() == BYTES) ||
                (getAlignmentUnit() == CHARACTERS)) {
            emptySpace = 1;
        }

        return emptySpace;
    }

    /**
     * Returns the BlockMetrics object containing the formatted annotation
     * layout  for output.
     *
     * @return the object holding the formatted annotation output layout
     */
    public BlockMetrics getMetrics() {
        return metrics;
    }

    /**
     * Returns whether the output is restricted to the selected time interval.
     *
     * @return whether the output is restricted to the selected time interval
     */
    public boolean isSelectionOnly() {
        return selectionOnly;
    }

    /**
     * Sets whether the output is restricted to the selected time interval.
     *
     * @param b if true only the annotations in the selected interval are
     *        included in the output
     */
    public void setSelectionOnly(boolean b) {
        selectionOnly = b;
    }

    /**
     * Sets whether a single time code line should be used or rather a time
     * code line  per root tier. (Not used yet)
     *
     * @param style single Time code line or multiple time code lines, one of
     *        <code>SINGLE_TIMECODE</code>, or <code>MULTIPLE_TIMECODE</code>.
     */
    public void setTimeCodeMultiplicity(int style) {
        timeCodeMultiplicity = style;
    }

    /**
     * Returns whether a single time code line should be used or rather a time
     * code line  per root tier.
     *
     * @return one of <code>SINGLE_TIMECODE</code>, or
     *         <code>MULTIPLE_TIMECODE</code>.
     */
    public int getTimeCodeMultiplicity() {
        return timeCodeMultiplicity;
    }
}
