/*
 * File:     GridViewer.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.grid;

import mpi.eudico.client.annotator.viewer.SingleTierViewer;

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

import mpi.eudico.server.corpora.clomimpl.abstr.TierImpl;
import mpi.eudico.server.corpora.clomimpl.type.Constraint;

import mpi.eudico.server.corpora.util.ACMEditEvent;

import mpi.library.util.LogUtil;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.table.TableColumn;


/**
 * This class adds functionality for showing all annotations of one tier plus
 * corresponding annotations on children tiers
 * @version Aug 2005 Identity removed
 */
public class GridViewer extends AbstractEditableGridViewer
    implements SingleTierViewer {
    /** Holds value of property DOCUMENT ME! */
    public static final int SINGLE_TIER_MODE = 0;

    /** Holds value of property DOCUMENT ME! */
    public static final int MULTI_TIER_MODE = 1;
    private int mode = SINGLE_TIER_MODE;

    /** Holds value of property DOCUMENT ME! */
    private final String EMPTY = "";
    private TierImpl tier;

    /**
     * Holds visibility values for the count, begintime, endtime and duration
     * columns in multi tier mode
     */
    private List childTiers = new ArrayList();

    /**
     * Stores the names of child tiers the moment they are added to the table.
     * This 'old' name can then be used to find the right column after a
     * change  of the tier name.
     */
    private Map childTierNames = new HashMap();
    private Set storedInvisibleColumns = new HashSet();

    /**
     * Constructor
     */
    public GridViewer() {
        super(new AnnotationTable(new GridViewerTableModel()));

        // default for MultiTier-View
        storedInvisibleColumns.add(GridViewerTableModel.BEGINTIME);
        storedInvisibleColumns.add(GridViewerTableModel.ENDTIME);
        storedInvisibleColumns.add(GridViewerTableModel.DURATION);
    }

    /**
     * DOCUMENT ME!
     *
     * @param annotations DOCUMENT ME!
     */
    public void updateDataModel(List annotations) {
        removeChildrenColumns();
        childTiers.clear();
        childTierNames.clear();
        super.updateDataModel(annotations);
    }

    /**
     * Checks the kind of edit that has happened and updates the table when
     * necessary.
     *
     * @param e the ACMEditEvent
     */
    public void ACMEdited(ACMEditEvent e) {
        if (tier == null) {
            return;
        }

        switch (e.getOperation()) {
        case ACMEditEvent.ADD_ANNOTATION_HERE:

            if (isCreatingAnnotation) {
                // if the new annotation is created by this gridviewer return
                isCreatingAnnotation = false;

                return;
            }

        // fallthrough
        case ACMEditEvent.ADD_ANNOTATION_BEFORE:

        // fallthrough
        case ACMEditEvent.ADD_ANNOTATION_AFTER:

        // fallthrough  
        case ACMEditEvent.CHANGE_ANNOTATION_TIME:

            // TierImpl invTier = (TierImpl) e.getInvalidatedObject();
            // annotationsChanged(invTier);
            // jul 2004: redo all; we can not rely on the fact that only
            // dependent
            // tiers will be effected by this operation...
            // (problem: unaligned annotations on time-subdivision tiers)
            annotationsChanged(null);

            break;

        case ACMEditEvent.CHANGE_ANNOTATIONS:

        // fallthrough
        case ACMEditEvent.REMOVE_ANNOTATION:

            // it is not possible to determine what tiers have been effected
            // update the whole data model
            annotationsChanged(null);

            break;

        case ACMEditEvent.CHANGE_TIER:

            // a tier is invalidated the kind of change is unknown
            TierImpl invalTier = (TierImpl) e.getInvalidatedObject();
            tierChanged(invalTier);

            break;

        case ACMEditEvent.ADD_TIER:

        // fallthrough
        case ACMEditEvent.REMOVE_TIER:

            TierImpl ti = (TierImpl) e.getModification();
            tierChanged(ti);

            break;

        default:
            super.ACMEdited(e);
        }
    }

    /**
     * If a change in the specified tier could have effected any of the tiers
     * in the table rebuild the table data model entirely. The change could be
     * a change in the name, or in the tier hierarchy or whatever.
     *
     * @param changedTier the invalidated tier
     */
    private void tierChanged(TierImpl changedTier) {
        if (mode == SINGLE_TIER_MODE) {
            if (changedTier == tier) {
                setTier(changedTier);
            }
        } else {
            setTier(tier);
        }
    }

    /**
     * Sets the tier to the selected tier in the combobox.
     *
     * @param tier the current tier for the grid/table
     */
    public void setTier(Tier tier) {
        // stop editing
        gridEditor.cancelCellEditing();

        this.tier = (TierImpl) tier;

        // added by AR
        if (tier == null) {
            updateDataModel(new ArrayList());
        } else {
            List annotations = null;

            try {
                annotations = this.tier.getAnnotations();
            } catch (Exception ex) {
                LOG.warning(LogUtil.formatStackTrace(ex));
            }

            updateDataModel(annotations);

            if (mode == MULTI_TIER_MODE) {
                extractChildTiers(this.tier);
                addExtraColumns();
            }
        }

        updateSelection();
        doUpdate();
    }

    /**
     * In multi tier mode finds those child tiers of the current tier that have
     * a LinguisticType with a SYMBOLIC_ASSOCIATION Constraint. These tiers
     * appear in the table as an extra column.
     *
     * @param tier DOCUMENT ME!
     */
    protected void extractChildTiers(TierImpl tier) {
        if (tier != null) {
            List depTiers = tier.getDependentTiers();
            Iterator tierIt = depTiers.iterator();
            TierImpl t;

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

                if (t.getParentTier() == tier) {
                    if (t.getLinguisticType().getConstraints().getStereoType() == Constraint.SYMBOLIC_ASSOCIATION) {
                        childTiers.add(t);
                        childTierNames.put(t, t.getName());

                        extractChildTiers(t);
                    }
                }
            }
        }
    }

    /**
     * Update method from ActiveAnnotationUser.
     */
    public void updateActiveAnnotation() {
        if (tier == null) {
            return;
        }

        if (getActiveAnnotation() == null) {
            repaint();

            return;
        }

        if (mode == SINGLE_TIER_MODE) {
            super.updateActiveAnnotation();
        } else {
            if ((getActiveAnnotation().getTier() != tier) &&
                    !childTiers.contains(getActiveAnnotation().getTier())) {
                repaint();

                return;
            }
        }

        doUpdate();
    }

    /**
     * DOCUMENT ME!
     */
    protected void addExtraColumns() {
        if (childTiers.size() == 0) {
            return;
        }

        Tier tierChild = null;

        // update vector with extra columns
        int vecChildren_size = childTiers.size();

        for (int i = 0; i < vecChildren_size; i++) {
            tierChild = (Tier) childTiers.get(i);
            handleExtraColumn(tierChild);
        }
    }

    private void handleExtraColumn(Tier childTier) {
        try {
            List v = createChildAnnotationVector(childTier);

            String name = childTier.getName();
            dataModel.addChildTier(name, v);

            int columnIndex = dataModel.findColumn(name);
            TableColumn tc = new TableColumn();
            tc.setHeaderValue(name);
            tc.setIdentifier(name);
            table.addColumn(tc);

            int curIndex = table.getColumnModel().getColumnIndex(name);
            table.moveColumn(curIndex, columnIndex);
            updateColumnModelIndices();
            table.setColumnVisible(name, true);
        } catch (Exception ex) {
            LOG.warning(LogUtil.formatStackTrace(ex));
        }
    }

    /**
     * Fills a Vector for a child tier with the same size as the parent tier's
     * annotations Vector. At the indices where the childtier has no child
     * annotation an empty String is inserted.
     *
     * @param childTier the dependent tier
     *
     * @return a Vector filled with child annotations and/or empty strings
     */
    private List createChildAnnotationVector(Tier childTier) {
        List cv = new ArrayList(dataModel.getRowCount());

        List existingChildren = ((TierImpl) childTier).getAnnotations();
        AnnotationCore annotation;
        AnnotationCore childAnnotation;
        long begin;

        for (int i = 0, j = 0; i < dataModel.getRowCount(); i++) {
            annotation = dataModel.getAnnotationCore(i);
            begin = annotation.getBeginTimeBoundary();

            if (j < existingChildren.size()) {
                childAnnotation = (AnnotationCore) existingChildren.get(j);

                if (childAnnotation.getBeginTimeBoundary() == begin) {
                    cv.add(childAnnotation);
                    j++;
                } else {
                    cv.add(EMPTY);
                }
            } else {
                cv.add(EMPTY);
            }
        }

        return cv;
    }

    /**
     * Sets the edit mode. On a change of the edit mode the current visible
     * columns are stored and the previous visible columns are restored.
     *
     * @param mode the new edit mode, one of SINGLE_TIER_MODE or
     *        MULTI_TIER_MODE
     */
    public void setMode(int mode) {
        if (this.mode == mode) {
            return;
        }

        this.mode = mode;

        Set invisibleColumns = getInvisibleColumns();
        setInvisibleColumns(storedInvisibleColumns);
        storedInvisibleColumns = invisibleColumns;
    }

    /**
     * DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    protected Set getInvisibleColumns() {
        Set invisibleColumns = new HashSet();
        TableColumn tc;
        invisibleColumns.clear();

        for (int i = 0; i < table.getColumnCount(); i++) {
            tc = table.getColumnModel().getColumn(i);

            if (!table.isColumnVisible((String) tc.getIdentifier())) {
                invisibleColumns.add(tc.getIdentifier());
            }
        }

        return invisibleColumns;
    }

    /**
     * DOCUMENT ME!
     *
     * @param invisibleColumns DOCUMENT ME!
     */
    protected void setInvisibleColumns(Set invisibleColumns) {
        TableColumn tc;

        for (int i = 0; i < table.getColumnCount(); i++) {
            tc = table.getColumnModel().getColumn(i);
            table.setColumnVisible(dataModel.getColumnName(i),
                !invisibleColumns.contains(tc.getIdentifier()));
        }
    }

    /**
     * Check whether the invalidated tier is displayed in the table and update
     * the table if so.
     *
     * @param invTier the invalidated tier
     */
    protected void annotationsChanged(TierImpl invTier) {
        if ((invTier == null) || (invTier == tier) ||
                invTier.getDependentTiers().contains(tier) ||
                childTiers.contains(invTier)) {
            List annotations = tier.getAnnotations();
            dataModel.updateAnnotations(annotations);

            for (int i = 0; i < childTiers.size(); i++) {
                Tier childTier = (Tier) childTiers.get(i);
                List vec = createChildAnnotationVector(childTier);
                dataModel.addChildTier(childTier.getName(), vec);
            }

            updateSelection();
            doUpdate();
        }
    }

    /**
     * DOCUMENT ME!
     */
    private void removeChildrenColumns() {
        if (childTiers.size() > 0) {
            for (int i = 0; i < childTiers.size(); i++) {
                TierImpl t = (TierImpl) childTiers.get(i);
                String columnID = (String) childTierNames.get(t);

                try {
                    table.removeColumn(table.getColumn(columnID));
                    updateColumnModelIndices();
                } catch (IllegalArgumentException iae) {
                    LOG.warning("Column not found: " + iae.getMessage());
                }
            }
        }
    }

    /**
     * When adding/removing and/or moving a table column the table column model
     * indices don't seem to be updated automatically.
     */
    private void updateColumnModelIndices() {
        Enumeration ten = table.getColumnModel().getColumns();
        TableColumn tabcol = null;
        int tableIndex;

        while (ten.hasMoreElements()) {
            tabcol = (TableColumn) ten.nextElement();
            tableIndex = table.getColumnModel().getColumnIndex(tabcol.getIdentifier());
            tabcol.setModelIndex(tableIndex);
        }
    }

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