/*
 * File:     CHATParser.java
 * Project:  MPI Linguistic Application
 * Date:     03 April 2006
 *
 * Copyright (C) 2001-2006  Max Planck Institute for Psycholinguistics
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * Created on Jun 11, 2004
 *
 * To change the template for this generated file go to
 * Window&gt;Preferences&gt;Java&gt;Code Generation&gt;Code and Comments
 */
package mpi.eudico.server.corpora.clomimpl.chat;

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

import mpi.eudico.server.corpora.clomimpl.abstr.MediaDescriptor;
import mpi.eudico.server.corpora.clomimpl.abstr.Parser;
import mpi.eudico.server.corpora.clomimpl.dobes.AnnotationRecord;
import mpi.eudico.server.corpora.clomimpl.dobes.LingTypeRecord;
import mpi.eudico.server.corpora.clomimpl.type.Constraint;

import mpi.util.MimeType;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Vector;


/**
 * @author hennie
 *
 * To change the template for this generated type comment go to
 * Window&gt;Preferences&gt;Java&gt;Code Generation&gt;Code and Comments
 *
 * @version sep 2005 the constructor is now public giving up the singleton pattern.
 * The path parameter of all getter methods could be removed in the next parser version
 * (add a public parse(String path) method)
 * Hashtable and Vector in Parser have been replaced by HashMap and ArrayList
 */
public class CHATParser extends Parser {
    //private static CHATParser parser;
    private final static String MAIN_TYPE = "orthography";
    private final static char TIER_NAME_SEPARATOR = '@';
    private final static String TS_ID_PREFIX = "ts";
    private final char BULLET = '\u0015';
    private String participantLine = null;
    private String mediaFileName = null;
    private ArrayList chatBlocks = new ArrayList();
    private ArrayList lingTypeRecords = new ArrayList();
    private ArrayList tierNames = new ArrayList();
    private HashMap parentHash = new HashMap();
    private ArrayList timeOrder = new ArrayList();
    private ArrayList timeSlots = new ArrayList(); // of long[2], {id,time}
    private ArrayList annotationRecords = new ArrayList();
    private HashMap annotRecordToTierMap = new HashMap();
    private String lastParsed = "";
    private BufferedReader br;

    /**
     * Private constructor for EAFParser because the Singleton pattern is
     * applied here.
     */
    public CHATParser() {
    }

    /**
     * The instance method returns the single incarnation of CHATParser to the
     * caller.
     *
     * @return DOCUMENT ME!
     */

    /*
    public static CHATParser Instance() {
        if (parser == null) {
            parser = new CHATParser();
        }

        return parser;
    }
     */
    /* (non-Javadoc)
     * @see mpi.eudico.server.corpora.clomimpl.abstr.Parser#getMediaDescriptors(java.lang.String)
     */
    public ArrayList getMediaDescriptors(String fileName) {
        ArrayList mediaDescriptors = new ArrayList();

        parse(fileName);

        String mediaURL = null;

        if (mediaFileName != null) {
            mediaURL = pathToURLString(mediaFileName);

            String mimeType = MimeType.getMimeTypeStringFromExtension(mediaFileName);

            //		String mimeType = MediaDescriptor.WAV_MIME_TYPE;
            MediaDescriptor md = new MediaDescriptor(mediaURL, mimeType);
            mediaDescriptors.add(md);
        }

        return mediaDescriptors;
    }

    /* (non-Javadoc)
     * @see mpi.eudico.server.corpora.clomimpl.abstr.Parser#getLinguisticTypes(java.lang.String)
     */
    public ArrayList getLinguisticTypes(String fileName) {
        parse(fileName);

        if (lingTypeRecords.size() != 0) {
            return lingTypeRecords;
        }

        Set labels = new HashSet();

        Iterator blockIter = chatBlocks.iterator();

        while (blockIter.hasNext()) {
            ArrayList block = (ArrayList) blockIter.next();

            Iterator lineIter = block.iterator();

            while (lineIter.hasNext()) {
                String[] line = (String[]) lineIter.next();
                String lbl = line[0];

                if (!lbl.equals("%snd") &&
                        !((lbl.length() > 1) &&
                        lbl.substring(1).startsWith("%"))) {
                    labels.add(lbl);
                }
            }
        }

        // create main "orthography" ling type for participant tiers
        LingTypeRecord orthoType = new LingTypeRecord();
        orthoType.setLingTypeId(MAIN_TYPE);
        orthoType.setTimeAlignable("true");
        orthoType.setGraphicReferences("false");

        lingTypeRecords.add(orthoType);

        // for each label, create a matching lingtype
        Iterator lblIter = labels.iterator();

        while (lblIter.hasNext()) {
            String label = (String) lblIter.next();

            if (!label.startsWith("*")) {
                LingTypeRecord lt = new LingTypeRecord();
                lt.setLingTypeId(label);
                lt.setTimeAlignable("false"); // all symbolic associations of ortho tier
                lt.setGraphicReferences("false");
                lt.setStereoType(Constraint.stereoTypes[Constraint.SYMBOLIC_ASSOCIATION]);

                lingTypeRecords.add(lt);
            }
        }

        return lingTypeRecords;
    }

    /* (non-Javadoc)
     * @see mpi.eudico.server.corpora.clomimpl.abstr.Parser#getTimeOrder(java.lang.String)
     */
    public ArrayList getTimeOrder(String fileName) {
        parse(fileName);

        // compose ordered list of timeslot ids from timeSlots
        // algorithm:
        // find first time after t = 0, put slot id in result list
        // find first time after t or equal to time of last result slot id
        // terminate when no time found
        //
        // handling of unaligned slots:
        // put all unaligned slots immediately preceding a result slot 
        // immediately before this slot in the result
        ArrayList unalignedSlots = new ArrayList();

        long[] firstSlotAfter = firstTimeSlotAfter(null, unalignedSlots);

        if (firstSlotAfter == null) {
            timeOrder.addAll(unalignedSlots);
        }

        while (firstSlotAfter != null) {
            timeOrder.addAll(unalignedSlots);
            timeOrder.add(firstSlotAfter);

            unalignedSlots.clear();
            firstSlotAfter = firstTimeSlotAfter(firstSlotAfter, unalignedSlots);
        }

        // add trailing unaligned timeslots, if any
        long[] lastAddedSlot = (long[]) timeOrder.get(timeOrder.size() - 1);

        if (timeSlots.indexOf(lastAddedSlot) != (timeSlots.size() - 1)) { // not last

            for (int i = timeSlots.indexOf(lastAddedSlot);
                    i < timeSlots.size(); i++) {
                timeOrder.add(timeSlots.get(i));

                if (i == (timeSlots.size() - 1)) { // align last slot manually
                    ((long[]) timeSlots.get(i))[1] = lastAddedSlot[1] + 1000;
                }
            }
        }

        ArrayList resultTimeOrder = new ArrayList();

        for (int i = 0; i < timeOrder.size(); i++) {
            resultTimeOrder.add(TS_ID_PREFIX +
                ((long[]) (timeOrder.get(i)))[0]);
        }

        return resultTimeOrder;
    }

    private long[] firstTimeSlotAfter(long[] afterTimeSlot,
        ArrayList unalignedSlots) {
        long[] firstSlot = null;
        long firstTimeAfter = Long.MAX_VALUE;

        ArrayList unalignedStore = new ArrayList();

        long afterTime = 0;
        long afterTimeId = -1;

        if (afterTimeSlot != null) {
            afterTime = afterTimeSlot[1];
            afterTimeId = afterTimeSlot[0];
        }

        Iterator tsIter = timeSlots.iterator();

        while (tsIter.hasNext()) {
            long[] ts = (long[]) tsIter.next();

            long time = ts[1];

            if (time < 0) { // unaligned
                unalignedStore.add(ts);
            } else if ((time >= afterTime) && (time < firstTimeAfter) &&
                    (!(ts[0] == afterTimeId)) && (!(timeOrder.contains(ts)))) {
                firstTimeAfter = time;
                firstSlot = ts;

                unalignedSlots.clear();
                unalignedSlots.addAll(unalignedStore);
                unalignedStore.clear();
            } else if (time > 0) { // not 'first time after', also not unaligned, so reset
                unalignedStore.clear();
            }
        }

        if (firstSlot == null) { // none found
            unalignedSlots.addAll(unalignedStore);
        }

        return firstSlot;
    }

    /* (non-Javadoc)
     * @see mpi.eudico.server.corpora.clomimpl.abstr.Parser#getTimeSlots(java.lang.String)
     */
    public HashMap getTimeSlots(String fileName) {
        parse(fileName);

        // generate HashMap from ArrayList with long[2]'s
        HashMap resultSlots = new HashMap();

        Iterator timeSlotIter = timeSlots.iterator();

        while (timeSlotIter.hasNext()) {
            long[] timeSlot = (long[]) timeSlotIter.next();
            String tsId = TS_ID_PREFIX + ((long) timeSlot[0]);
            String timeValue = Long.toString(((long) timeSlot[1]));

            resultSlots.put(tsId, timeValue);
        }

        return resultSlots;
    }

    /* (non-Javadoc)
     * @see mpi.eudico.server.corpora.clomimpl.abstr.Parser#getTierNames(java.lang.String)
     */
    public ArrayList getTierNames(String fileName) {
        // tierNames in ELAN are either the main tier '*PAR' labels, or
        // the combination of tier label plus participant, like '%mor@PAR'
        parse(fileName);

        return tierNames;
    }

    /* (non-Javadoc)
     * @see mpi.eudico.server.corpora.clomimpl.abstr.Parser#getParticipantOf(java.lang.String, java.lang.String)
     */
    public String getParticipantOf(String tierName, String fileName) {
        String participant = "";

        if (tierName.startsWith("*")) {
            participant = tierName.substring(1); // main tier label without *
        } else {
            int i = tierName.indexOf(TIER_NAME_SEPARATOR); // part of tier name after @

            if ((i > 0) && (tierName.length() > (i + 2))) {
                participant = tierName.substring(i + 1);
            }
        }

        return participant;
    }

    /* (non-Javadoc)
     * @see mpi.eudico.server.corpora.clomimpl.abstr.Parser#getLinguisticTypeOf(java.lang.String, java.lang.String)
     */
    public String getLinguisticTypeIDOf(String tierName, String fileName) {
        String lingTypeId = "";

        if (tierName.startsWith("*")) {
            lingTypeId = MAIN_TYPE; // main tier label without *
        } else {
            int i = tierName.indexOf(TIER_NAME_SEPARATOR); // part of tier name after @

            if (i > 0) {
                lingTypeId = tierName.substring(0, i);
            }
        }

        return lingTypeId;
    }

    /* (non-Javadoc)
     * @see mpi.eudico.server.corpora.clomimpl.abstr.Parser#getParentNameOf(java.lang.String, java.lang.String)
     */
    public String getParentNameOf(String tierName, String fileName) {
        parse(fileName);

        return (String) parentHash.get(tierName);
    }

    /* (non-Javadoc)
     * @see mpi.eudico.server.corpora.clomimpl.abstr.Parser#getAnnotationsOf(java.lang.String, java.lang.String)
     */
    public ArrayList getAnnotationsOf(String tierName, String fileName) {
        parse(fileName);

        ArrayList resultAnnotRecords = new ArrayList();

        Iterator it = annotRecordToTierMap.keySet().iterator();

        while (it.hasNext()) {
            AnnotationRecord annRec = (AnnotationRecord) it.next();

            if (annotRecordToTierMap.get(annRec).equals(tierName)) {
                resultAnnotRecords.add(annRec);
            }
        }

        return resultAnnotRecords;
    }

    private void parse(String fileName) {
        if (lastParsed.equals(fileName)) {
            return;
        }

        // (re)set everything to null for each parse
        participantLine = null;
        mediaFileName = null;
        chatBlocks.clear();
        lingTypeRecords.clear();
        tierNames.clear();
        parentHash.clear();
        timeOrder.clear();
        timeSlots.clear();
        annotationRecords.clear();
        annotRecordToTierMap.clear();

        br = null;

        // parse the file
        lastParsed = fileName;

        // do actual parsing
        try {
            br = new BufferedReader(new FileReader(fileName));
        } catch (Exception fnf) {
            fnf.printStackTrace();
        }

        String line = null;

        try {
            if ((line = br.readLine()) != null) {
                if (line.startsWith("@UTF8")) { // CHAT UTF-8
                    br.close();
                    br = new BufferedReader(new InputStreamReader(
                                new FileInputStream(fileName), "UTF-8"));
                }
            }
        } catch (IOException iox) {
            iox.printStackTrace();
        }

        parseLines();
        processBlocks();

        try {
            br.close();
        } catch (IOException io) {
            io.printStackTrace();
        }
    }

    private void parseLines() {
        String line = null;
        String outputLine = "";
        boolean recordingParticipant = false;

        ArrayList chatBlock = null;

        try {
            while ((line = br.readLine()) != null) {
                // Participant lines from header
                if (line.startsWith("@Participants:")) {
                    recordingParticipant = true;
                    participantLine = line;
                } else if (recordingParticipant == true) {
                    if (!(line.startsWith("@") || line.startsWith("*") ||
                            line.startsWith("%"))) {
                        // continuation of participants line
                        participantLine += line;
                    } else { // new header line or block line, end recording
                        recordingParticipant = false;
                    }
                }

                // CHAT "blocks"
                if (line.startsWith("*")) { // new block

                    // finish last line of previous block
                    if (!outputLine.equals("") && (chatBlock != null)) {
                        addLineToBlock(outputLine, chatBlock);
                    }

                    // output block
                    if (chatBlock != null) {
                        chatBlocks.add(chatBlock);
                    }

                    // start new recording
                    chatBlock = new ArrayList();

                    // add line to new recording
                    outputLine = line;
                } else if (line.startsWith("%") ||
                        ((line.length() > 1) &&
                        line.substring(1).startsWith("%"))) { // other lines

                    // finish last line
                    if (!outputLine.equals("") && (chatBlock != null)) {
                        addLineToBlock(outputLine, chatBlock);
                    }

                    outputLine = line;

                    if ((mediaFileName == null) &&
                            (startsWithMediaLabel(line) ||
                            ((line.length() > 1) &&
                            startsWithMediaLabel(line.substring(1))))) { // bullet in chat-utf8

                        // parse this line, second token is media file name.
                        StringTokenizer st = new StringTokenizer(line);

                        if (st.hasMoreTokens()) { // 'eat' %snd label
                            st.nextToken();
                        }

                        if (st.hasMoreTokens()) {
                            mediaFileName = st.nextToken();
                        }

                        // strip off possible double quotes
                        if (mediaFileName.startsWith("\"")) {
                            mediaFileName = mediaFileName.substring(1);
                        }

                        if (mediaFileName.endsWith("\"")) {
                            mediaFileName = mediaFileName.substring(0,
                                    mediaFileName.length() - 1);
                        }
                    }
                } else if (!line.startsWith("@")) { // no label, continuation of previous line
                    outputLine += line;
                }
            }

            // finish last line
            if (!outputLine.equals("") && (chatBlock != null)) {
                addLineToBlock(outputLine, chatBlock);
            }

            // output last block
            if (chatBlock != null) {
                chatBlocks.add(chatBlock);
            }
        } catch (FileNotFoundException fex) {
            fex.printStackTrace();
        } catch (IOException iex) {
            iex.printStackTrace();
        }
    }

    /**
     * Helper method to avoid copy and paste
     *
     * @param file DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     *
     * @throws IOException DOCUMENT ME!
     */

    //	private final BufferedReader file2br(File file) throws IOException {

    /*
       A file is opened from the operating system.
       This stream of bytes could be a UTF-8 encoded unicode stream.
       If a file interpreted as UTF-8 contains isolatin-1, the file
       cannot be read. An Exception is thrown.
       Therefore, special care has to be taken when reading in UTF-8.
       As a first measure, the filename is used to decide if to read as UTF-8.
       This has to be changend in a future version.
       This is just done in order to include Unicode characters into Eudico.
     */
    /*        Reader filereader;

            if (-1 != file.getName().lastIndexOf(".utf8.")) { // this means 'contains'
                filereader = new InputStreamReader(new FileInputStream(file),
                        "UTF-8");
            } else {
                // use the locale encoding.
                filereader = new FileReader(file);
            }

            BufferedReader br = new BufferedReader(filereader);

            return br;
        }
    */
    private void addLineToBlock(String theLine, ArrayList theBlock) {
        String label = null;
        String value = null;

        label = getLabelPart(theLine);
        value = getValuePart(theLine);

        if ((label != null) && (value != null)) {
            String[] line = { label, value };
            theBlock.add(line);
        } else if ((label != null) && (value == null)) {
            // maybe a valid tierlabel with empty annotation content
            String[] line = { label, "" };
            theBlock.add(line);
        }
    }

    private String getLabelPart(String theLine) {
        String label = null;

        int index = theLine.indexOf(':');

        if (index > 0) {
            label = theLine.substring(0, index);
        }

        return label;
    }

    private String getValuePart(String theLine) {
        String value = null;

        int index = theLine.indexOf(':');

        if (index < (theLine.length() - 2)) {
            value = theLine.substring(index + 1).trim();
        }

        return value;
    }

    private void processBlocks() {
        Set tNames = new HashSet();

        String annotationIdPrefix = "ann";
        long annotId = 0;
        long tsId = 0;

        // store last end per root annot, to prevent overlaps within tier
        HashMap lastEndTimes = new HashMap();

        long[] firstSlotAfterSync = null; // store first slot for a range of unaligned blocks,
                                          // time interval may apply to a sequence of blocks

        Iterator blockIter = chatBlocks.iterator();

        while (blockIter.hasNext()) {
            String participantLabel = "";
            String tierName = null;
            String[] mediaLine = null;

            String rootAnnotId = "";
            long beginTSId = 0;
            long endTSId = 0;
            long begin = TimeSlot.TIME_UNALIGNED;
            long end = TimeSlot.TIME_UNALIGNED;

            ArrayList block = (ArrayList) blockIter.next();

            Iterator lineIter = block.iterator();

            while (lineIter.hasNext()) {
                // (compose and) collect tier names
                String[] line = (String[]) lineIter.next();
                String lbl = line[0];
                String value = line[1];

                if (lbl.startsWith("*")) {
                    participantLabel = lbl;
                    tierName = lbl;
                } else if (!startsWithMediaLabel(lbl) &&
                        !(((lbl.length() > 1) &&
                        startsWithMediaLabel(lbl.substring(1))))) {
                    tierName = lbl +
                        participantLabel.replace('*', TIER_NAME_SEPARATOR);
                    parentHash.put(tierName, participantLabel);
                }

                tNames.add(tierName);

                // create AnnotationRecord for main and dependent tiers
                // create time slots per block
                if (lbl.startsWith("*")) { // main utterance tier

                    AnnotationRecord annRec = new AnnotationRecord();

                    rootAnnotId = annotationIdPrefix + annotId; // store annot id for parent referencing

                    annRec.setAnnotationId(annotationIdPrefix + annotId++);
                    annRec.setAnnotationType(AnnotationRecord.ALIGNABLE);

                    beginTSId = tsId++;
                    endTSId = tsId++;
                    annRec.setBeginTimeSlotId(TS_ID_PREFIX +
                        Long.toString(beginTSId));
                    annRec.setEndTimeSlotId(TS_ID_PREFIX +
                        Long.toString(endTSId));

                    // Feb 2006: add support for media and time information on the main 
                    // utterance tier: format BULLET%snd:"2MEHT10"_392998_397665BULLET
                    if (value.indexOf(BULLET) > -1) {
                        mediaLine = extractMediaAndTime(value.substring(
                                    value.indexOf(BULLET)));

                        if (mediaLine != null) {
                            if ((mediaLine[0] != null) &&
                                    startsWithMediaLabel(mediaLine[0])) {
                                if ((mediaLine[1] != null) &&
                                        (mediaFileName == null)) {
                                    mediaFileName = mediaLine[1];
                                }

                                if (mediaLine[2] != null) {
                                    try {
                                        begin = Long.parseLong(mediaLine[2]);
                                    } catch (NumberFormatException nfe) {
                                        System.out.println(
                                            "Invalid time value: " +
                                            mediaLine[2]);
                                    }
                                }

                                if (mediaLine[3] != null) {
                                    try {
                                        end = Long.parseLong(mediaLine[3]);
                                    } catch (NumberFormatException nfe) {
                                        System.out.println(
                                            "Invalid time value: " +
                                            mediaLine[3]);
                                    }
                                }
                            }
                        }

                        annRec.setValue(value.substring(0, value.indexOf(BULLET))
                                             .trim());
                    } else {
                        annRec.setValue(value);
                    }

                    annotationRecords.add(annRec);
                    annotRecordToTierMap.put(annRec, tierName);
                } else if ((startsWithMediaLabel(lbl)) ||
                        ((lbl.length() > 1) &&
                        startsWithMediaLabel(lbl.substring(1)))) {
                    String timeString = value;

                    if (timeString != null) {
                        StringTokenizer st = new StringTokenizer(timeString);

                        if (st.hasMoreTokens()) { // skip first token, the sound file name
                            st.nextToken();
                        }

                        if (st.hasMoreTokens()) { // second token is begin time

                            String bString = st.nextToken();
                            int positionOfDot = bString.indexOf("."); // for MED/X-Waves alined CHAT data

                            if (positionOfDot > 0) {
                                bString = bString.substring(0, positionOfDot);
                            }

                            begin = (new Long(bString)).longValue();
                        }

                        if (st.hasMoreTokens()) { // third token is end time

                            String eString = st.nextToken();
                            int positionOfDot = eString.indexOf("."); // for MED/X-Waves alined CHAT data

                            if (positionOfDot > 0) {
                                eString = eString.substring(0, positionOfDot);
                            }

                            end = (new Long(eString)).longValue();
                        }
                    }
                } else { // consider reference annotation on dependent tier

                    AnnotationRecord annRec = new AnnotationRecord();

                    annRec.setAnnotationId(annotationIdPrefix + annotId++);
                    annRec.setAnnotationType(AnnotationRecord.REFERENCE);
                    annRec.setReferredAnnotId(rootAnnotId);

                    annRec.setValue(value);

                    annotationRecords.add(annRec);
                    annotRecordToTierMap.put(annRec, tierName);
                }
            }

            long beginMsec = TimeSlot.TIME_UNALIGNED;

            if (begin != TimeSlot.TIME_UNALIGNED) {
                beginMsec = (long) begin;

                // prevent overlaps within one tier
                long lastEnd = 0;

                if (lastEndTimes.get(participantLabel) != null) {
                    lastEnd = ((Long) lastEndTimes.get(participantLabel)).longValue();
                }

                if (lastEnd > beginMsec) {
                    beginMsec = lastEnd;
                }
            }

            long endMsec = TimeSlot.TIME_UNALIGNED;

            if (end != TimeSlot.TIME_UNALIGNED) {
                endMsec = (long) end;
                lastEndTimes.put(participantLabel, new Long(endMsec));
            }

            long[] bSlot = { beginTSId, beginMsec };

            // in case %snd time intervale applies to a sequence of blocks, store first slot for later alignment
            if ((firstSlotAfterSync == null) &&
                    (begin == TimeSlot.TIME_UNALIGNED)) { // store 
                firstSlotAfterSync = bSlot;
            }

            long[] eSlot = { endTSId, endMsec };

            if ((firstSlotAfterSync != null) &&
                    (begin != TimeSlot.TIME_UNALIGNED) &&
                    (end != TimeSlot.TIME_UNALIGNED)) {
                firstSlotAfterSync[1] = beginMsec;
                bSlot[1] = TimeSlot.TIME_UNALIGNED;

                firstSlotAfterSync = null;
            }

            timeSlots.add(bSlot);
            timeSlots.add(eSlot);
        }

        tierNames = new ArrayList(tNames);
    }

    private boolean startsWithMediaLabel(String line) {
        boolean start = false;

        if (line.startsWith("%snd") || line.startsWith("%mov")) {
            start = true;
        }

        return start;
    }

    /**
     * Extracts label, medianame, begintime and endtime from a CHAT formatted
     * media string. This is the media string that follows an utterance on the
     * same line (as opposed to media information in a separate tier)
     * Format: {BULLET}%snd:"1MEHT10"_8742_10762{BULLET}
     * @param value the formatted string
     * @return a String array with the single tokens
     */
    private String[] extractMediaAndTime(String value) {
        if (value == null) {
            return null;
        }

        String[] result = new String[4];
        StringBuffer buf = new StringBuffer(value);

        // remove bullets
        if (buf.charAt(0) == BULLET) {
            buf.delete(0, 1);
        }

        if (buf.charAt(buf.length() - 1) == BULLET) {
            buf.delete(buf.length() - 1, buf.length());
        }

        int colon = buf.indexOf(":");
        int quot = buf.indexOf("\"");
        int quot2 = buf.lastIndexOf("\"");

        if (colon > -1) {
            result[0] = buf.substring(0, colon); //%snd or %mov
        }

        if ((quot > -1) && (quot2 > (quot + 1))) {
            // media filename, without extension!
            result[1] = buf.substring(quot + 1, quot2);

            int under = buf.indexOf("_", quot2);
            int under2 = buf.indexOf("_", under + 1);

            if (under > -1) {
                if (under2 > (under + 1)) {
                    result[2] = buf.substring(under + 1, under2);

                    if (under2 < (buf.length() - 1)) {
                        result[3] = buf.substring(under2 + 1);
                    }
                } else {
                    // only begintime?
                    result[2] = buf.substring(under + 1);
                }
            }
        }

        return result;
    }

    /*
     * This method should be in a Utility class or a URL class
     * Convert a path to a file URL string. Takes care of Samba related problems
     * file:///path works for all files except for samba file systems, there we need file://machine/path,
     * i.e. 2 slashes insteda of 3
     *
     * What's with relative paths?
     */
    private String pathToURLString(String path) {
        // replace all back slashes by forward slashes
        path = path.replace('\\', '/');

        // remove leading slashes and count them
        int n = 0;

        while (path.charAt(0) == '/') {
            path = path.substring(1);
            n++;
        }

        // add the file:// or file:/// prefix
        if (n == 2) {
            return "file://" + path;
        } else {
            return "file:///" + path;
        }
    }
}
