/*
 * File:     SubtitleViewer.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.viewer;

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

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

import mpi.eudico.client.mediacontrol.ControllerEvent;

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.util.ACMEditEvent;
import mpi.eudico.server.corpora.util.ACMEditListener;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

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

import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.text.DefaultHighlighter;
import javax.swing.text.Highlighter;


/**
 * Viewer for a subtitle
 * @version Aug 2005 Identity removed
 */
public class SubtitleViewer extends AbstractViewer implements SingleTierViewer,
    ACMEditListener, ActionListener {
    private JMenu fontMenu;
    private ButtonGroup fontSizeBG;
    private int fontSize;
    private JPopupMenu popup;
    private Dimension minDimension = new Dimension(400, 50);
    private JTextArea taSubtitle;
    private JScrollPane jspSubtitle;
    private TierImpl tier;
    private Vector annotations = new Vector();
    private long begintime = 0;
    private long endtime = 0;
    private long[] arrTags;
    private Highlighter highlighter;
    private Highlighter.HighlightPainter selectionPainter;
    private InlineEditBox inlineEditBox;

    /**
     * Constructor
     *
     */
    public SubtitleViewer() {
        try {
            setLayout(new BorderLayout());

            taSubtitle = new JTextArea() { //don't eat up any key events
                        protected boolean processKeyBinding(KeyStroke ks,
                            KeyEvent e, int condition, boolean pressed) {
                            return false;
                        }
                    };

            taSubtitle.setFont(Constants.DEFAULTFONT);
            taSubtitle.setLineWrap(true);
            taSubtitle.setBackground(Constants.DEFAULTBACKGROUNDCOLOR);
            taSubtitle.setForeground(Constants.DEFAULTFOREGROUNDCOLOR);
            taSubtitle.setEditable(false);
            taSubtitle.addMouseListener(new SubtitleViewerMouseListener(this));

            highlighter = taSubtitle.getHighlighter();
            selectionPainter = new DefaultHighlighter.DefaultHighlightPainter(Constants.SELECTIONCOLOR);

            jspSubtitle = new JScrollPane(taSubtitle);
            add(jspSubtitle, BorderLayout.CENTER);

            fontSize = 12;

            setVisible(true);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * Sets minimum for the subtitle viewer See also getPreferredSize
     *
     * @return DOCUMENT ME!
     */
    public Dimension getMinimumSize() {
        return minDimension;
    }

    /**
     * Sets minimum for the subtitle viewer See also getMinimumSize
     *
     * @return DOCUMENT ME!
     */
    public Dimension getPreferredSize() {
        return minDimension;
    }

    /**
     * AR notification that the selection has changed method from SelectionUser
     * not implemented in AbstractViewer
     */
    public void updateSelection() {
        doUpdate();
    }

    /**
     * AR heeft dit hier neergezet, zie abstract viewer voor get en set
     * methodes van ActiveAnnotation. Update method from ActiveAnnotationUser
     */
    public void updateActiveAnnotation() {
    }

    /**
     * AR heeft dit hier neergezet Er moet nog gepraat worden over wat hier te
     * doen valt
     *
     * @param e DOCUMENT ME!
     */
    public void ACMEdited(ACMEditEvent e) {
        switch (e.getOperation()) {
        case ACMEditEvent.ADD_ANNOTATION_HERE:
        case ACMEditEvent.ADD_ANNOTATION_BEFORE:
        case ACMEditEvent.ADD_ANNOTATION_AFTER:
        case ACMEditEvent.CHANGE_ANNOTATIONS:
        case ACMEditEvent.REMOVE_ANNOTATION:
        case ACMEditEvent.CHANGE_ANNOTATION_TIME:
        case ACMEditEvent.CHANGE_ANNOTATION_VALUE: {
            setTier(getTier());
            doUpdate();
        }
        }
    }

    /**
     * method from ElanLocaleListener not implemented in AbstractViewer
     */
    public void updateLocale() {
        createPopup();
    }

    private void createPopup() {
        popup = new JPopupMenu("");

        fontSizeBG = new ButtonGroup();
        fontMenu = new JMenu(ElanLocale.getString("Menu.View.FontSize"));

        JRadioButtonMenuItem fontRB;

        for (int i = 0; i < Constants.FONT_SIZES.length; i++) {
            fontRB = new JRadioButtonMenuItem(String.valueOf(
                        Constants.FONT_SIZES[i]));
            fontRB.setActionCommand("font" + Constants.FONT_SIZES[i]);

            if (fontSize == Constants.FONT_SIZES[i]) {
                fontRB.setSelected(true);
            }

            fontRB.addActionListener(this);
            fontSizeBG.add(fontRB);
            fontMenu.add(fontRB);
        }

        popup.add(fontMenu);
    }

    /**
     * DOCUMENT ME!
     *
     * @param e DOCUMENT ME!
     */
    public void actionPerformed(ActionEvent e) {
        String strAction = e.getActionCommand();

        if (strAction.indexOf("font") != -1) {
            int index = strAction.indexOf("font") + 4;

            try {
                fontSize = Integer.parseInt(strAction.substring(index));
                repaint();
            } catch (Exception ex) {
                ex.printStackTrace();
            }

            taSubtitle.setFont(Constants.DEFAULTFONT.deriveFont(
                    (float) fontSize));
        }
    }

    /**
     * AR notification that some media related event happened method from
     * ControllerListener not implemented in AbstractViewer
     *
     * @param event DOCUMENT ME!
     */
    public void controllerUpdate(ControllerEvent event) {
        doUpdate();
    }

    /**
     * Update the complete subtitle viewer Determines whether current time is
     * in a selection Sets the correct value in the subtitle viewer
     */
    public void doUpdate() {
        String strTagValue = "";
        Annotation ann;
        boolean bFound = false;
        long mediatime = getMediaTime();
        int annotations_size = annotations.size();

        if (arrTags == null) {
            return;
        }

        //use fast search to determine approximate index from array
        int index = Math.abs(Arrays.binarySearch(arrTags, mediatime));

        //make it an index which can be used with the vector
        index = index / 2;

        //determine first and last index in vector to use in loop
        int beginindex = index - 2;
        int endindex = index + 2;

        if (beginindex < 0) {
            beginindex = 0;
        }

        if (endindex > (annotations_size - 1)) {
            endindex = annotations_size - 1;
        }

        long selectionBeginTime = getSelectionBeginTime();
        long selectionEndTime = getSelectionEndTime();

        //now there is a maximum of only 5 indexes to loop through
        for (int i = beginindex; i <= endindex; i++) {
            ann = (Annotation) annotations.elementAt(i);
            begintime = ann.getBeginTimeBoundary();
            endtime = ann.getEndTimeBoundary();

            // special case: if a selection (active annotation) is played, 
            // at the end mediatime == end time of active annotation. Include endtime
            // in comparison in such case
            if (((ann == getActiveAnnotation()) && (endtime == mediatime)) ||
                    ((begintime == selectionBeginTime) &&
                    (endtime == selectionEndTime) && (endtime == mediatime))) {
                bFound = true;
                strTagValue = ann.getValue();
                strTagValue = strTagValue.replace('\n', ' ');

                break;
            }

            if ((mediatime >= begintime) && (mediatime < endtime)) {
                bFound = true;
                strTagValue = ann.getValue();

                /*
                if (ann == getActiveAnnotation()) {
                    taSubtitle.setBorder(BorderFactory.createLineBorder(
                        Constants.ACTIVEANNOTATIONCOLOR));
                } else {
                    taSubtitle.setBorder(null);
                }
                */

                //next line for JDK1.4 only
                //strTagValue = strTagValue.replaceAll("\n", "");
                strTagValue = strTagValue.replace('\n', ' ');

                break;
            }
        }

        //set the appropriate text
        if (bFound) {
            try {
                taSubtitle.setText(" " + strTagValue); //+ '\u0332')
            } catch (Exception ex) {
                taSubtitle.setText("");
            }
        } else {
            taSubtitle.setText("");

            //taSubtitle.setBorder(null);
        }

        //handle colors
        if ((tier != null) && (selectionBeginTime != selectionEndTime) &&
                (mediatime >= begintime) &&
                ((mediatime < endtime) ||
                ((mediatime == endtime) && (selectionBeginTime == begintime))) &&
                (((selectionBeginTime >= begintime) &&
                (selectionBeginTime < endtime)) ||
                ((selectionEndTime >= begintime) &&
                (selectionEndTime < endtime)))) {
            try {
                highlighter.addHighlight(1, taSubtitle.getText().length(),
                    selectionPainter);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        } else {
            taSubtitle.getHighlighter().removeAllHighlights();
        }

        repaint();
    }

    /**
     * Sets the tier which is shown in the subtitle viewer
     *
     * @param tier The tier which should become visible
     */
    public void setTier(Tier tier) {
        // added by AR
        putOriginalComponentBack();

        if (tier == null) {
            this.tier = null;
            annotations = new Vector();
            doUpdate();
        } else {
            this.tier = (TierImpl) tier;

            try {
                annotations = this.tier.getAnnotations();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

        buildArray();
        doUpdate();
    }

    /**
     * Gets the tier which is shown in the subtitle viewer
     *
     * @return DOCUMENT ME!
     */
    public Tier getTier() {
        return tier;
    }

    /**
     * Returns the current font size.
     *
     * @return the current font size
     */
    public int getFontSize() {
        return fontSize;
    }

    /**
     * Sets the font size.
     *
     * @param size the new font size
     */
    public void setFontSize(int size) {
        fontSize = size;

        if (fontSizeBG != null) {
            Enumeration en = fontSizeBG.getElements();
            JMenuItem item;
            String value;

            while (en.hasMoreElements()) {
                item = (JMenuItem) en.nextElement();
                value = item.getText();

                try {
                    int v = Integer.parseInt(value);

                    if (v == fontSize) {
                        item.setSelected(true);
                        taSubtitle.setFont(Constants.DEFAULTFONT.deriveFont(
                                (float) fontSize));

                        break;
                    }
                } catch (NumberFormatException nfe) {
                    //// do nothing
                }
            }
        } else {
            createPopup();
            taSubtitle.setFont(Constants.DEFAULTFONT.deriveFont(
                    (float) fontSize));
        }
    }

    /**
     * Builds an array with all begintimes and endtimes from a tag Used for
     * searching (quickly) a particular tag
     */
    private void buildArray() {
        int annotations_size = annotations.size();
        arrTags = new long[2 * annotations_size];

        int arrIndex = 0;
        Annotation ann;

        for (int i = 0; i < annotations_size; i++) {
            ann = (Annotation) annotations.elementAt(i);

            begintime = ann.getBeginTimeBoundary();
            endtime = ann.getEndTimeBoundary();

            arrTags[arrIndex++] = begintime;
            arrTags[arrIndex++] = endtime;
        }
    }

    //when inline edit box disappears, it calls this method
    public void putOriginalComponentBack() {
        inlineEditBox = null;
        removeAll();
        add(jspSubtitle, BorderLayout.CENTER);
        validate();
        repaint();
    }

    /**
     * Hhandles mouse actions on the subtitle viewer
     */
    private class SubtitleViewerMouseListener extends MouseAdapter {
        private SubtitleViewer subtitleViewer;

        /**
         * Creates a new SubtitleViewerMouseListener instance
         *
         * @param sv DOCUMENT ME!
         */
        SubtitleViewerMouseListener(SubtitleViewer sv) {
            subtitleViewer = sv;
        }

        /**
         * The mouse pressed event handler.
         * @param e the mouse event
         */
        public void mousePressed(MouseEvent e) {
            if (SwingUtilities.isRightMouseButton(e) || e.isPopupTrigger()) {
                Point p = e.getPoint();
                SwingUtilities.convertPointToScreen(p, subtitleViewer);

                int x = e.getPoint().x;
                int y = e.getPoint().y;

                popup.show(subtitleViewer, x, y);

                return;
            }
        }

        /**
         * DOCUMENT ME!
         *
         * @param e DOCUMENT ME!
         */
        public void mouseClicked(MouseEvent e) {
            //bring up edit dialog
            if (e.getClickCount() == 2) {
                inlineEditBox = new InlineEditBox(subtitleViewer);

                Annotation annotation = getAnnotation();

                if (annotation != null) {
                    if (e.isShiftDown()) {
                        // open CV
                        inlineEditBox.setAnnotation(annotation, true);
                    } else {
                        inlineEditBox.setAnnotation(annotation);
                    }

                    inlineEditBox.configureEditor(JPanel.class, null, getSize());
                    removeAll();
                    add(inlineEditBox, BorderLayout.CENTER);
                    validate();

                    //repaint();
                    inlineEditBox.startEdit();
                }

                return;
            }

            if (!taSubtitle.getText().equals("")) {
                stopPlayer();
                setActiveAnnotation(getAnnotation());
            }
        }

        private Annotation getAnnotation() {
            Annotation annotation = null;

            int annotations_size = annotations.size();

            for (int i = 0; i < annotations_size; i++) {
                annotation = (Annotation) annotations.elementAt(i);

                //exact time always exists in this case
                //if (getSelectionBeginTime() == tag.getBeginTime())
                // HS 4 dec 03: the subtitle viewer always seems to show the tag
                // at the media time, if any
                if ((getMediaTime() >= annotation.getBeginTimeBoundary()) &&
                        (getMediaTime() < annotation.getEndTimeBoundary())) {
                    return annotation;
                }
            }

            return null;
        }
    }

    //end of SubtitleViewerMouseListener
}
