package org.yamcs.ui.eventviewer;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.Vector;

import javax.swing.table.AbstractTableModel;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yamcs.protobuf.Yamcs;
import org.yamcs.protobuf.Yamcs.Event;
import org.yamcs.utils.TimeEncoding;

import com.google.protobuf.Descriptors.FieldDescriptor.Type;
import com.google.protobuf.ExtensionRegistry;
import com.google.protobuf.GeneratedMessage.GeneratedExtension;
import com.google.protobuf.InvalidProtocolBufferException;

class EventTableModel extends AbstractTableModel implements Observer {
    private static final long serialVersionUID = 1L;
    private static final Logger log=LoggerFactory.getLogger(EventTableModel.class);
    private static List<String> columnNames=new ArrayList<String>();
    private List<GeneratedExtension<Yamcs.Event, Type>> extensions=new ArrayList<GeneratedExtension<Yamcs.Event, Type>>();
    private ExtensionRegistry registry;

    /** Column indices */
    public static final int SOURCE_COL          = 0;
    public static final int GENERATION_TIME_COL = 1;
    public static final int RECEPTION_TIME_COL  = 2;
    public static final int EVENT_TYPE_COL      = 3;
    public static final int EVENT_TEXT_COL      = 4;
    public static final int FIRST_EXTENSION_COL = 5;

    /** Vector with all events */
    private Vector<Event>   allEvents           = null;

    /** Parallel data model with only visible events */
    private Vector<Event>   visibleEvents       = null;

    /** Filtering table */
    FilteringRulesTable     filteringTable      = null;

    public EventTableModel(FilteringRulesTable table, List<Map<String, String>> extraColumns) {
        columnNames.add("Source");
        columnNames.add("Generation Time");
        columnNames.add("Reception Time");
        columnNames.add("Type");
        columnNames.add("Description");
        
        allEvents = new Vector<Event>();
        visibleEvents = new Vector<Event>();
        filteringTable = table;
        filteringTable.registerObserver(this);
        if(extraColumns!=null) {
	        for(Map<String,String> col:extraColumns) {
	            if(registry==null) {
	                registry=ExtensionRegistry.newInstance();
	            }
	            try {
	                Class<?> extensionClazz=Class.forName(col.get("class"));
	                Field field=extensionClazz.getField(col.get("extension"));
	                @SuppressWarnings("unchecked")
	                GeneratedExtension<Yamcs.Event, Type> extension=(GeneratedExtension<Yamcs.Event, Type>)field.get(null);
	                extensions.add(extension);
	                registry.add(extension);
	                log.info("Installing extension "+extension.getDescriptor().getFullName());
	            } catch (IllegalAccessException e) {
	                log.error("Could not load extension class", e);
	                continue;
	            } catch (ClassNotFoundException e) {
	                log.error("Could not load extension class", e);
	                continue;
	            } catch (SecurityException e) {
	                log.error("Could not load extension class", e);
	                continue;
	            } catch (NoSuchFieldException e) {
	                log.error("Could not load extension class", e);
	                continue;
	            }
	            columnNames.add(col.get("label"));
	        }
        }
    }

    @Override
    public int getRowCount() {
        return visibleEvents.size();
    }

    @Override
    public int getColumnCount() {
        return columnNames.size();
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        Object value = null;
        Event event = visibleEvents.elementAt(rowIndex);

        if(columnIndex==SOURCE_COL) {
            value=event.getSource();
        } else if(columnIndex==EVENT_TEXT_COL) {
            value=event; // return event here to be compatible with older code
        } else if(columnIndex==EVENT_TYPE_COL) {
            value=event.getType();
        } else if(columnIndex==GENERATION_TIME_COL) {
            value=TimeEncoding.toString(event.getGenerationTime());
        } else if(columnIndex==RECEPTION_TIME_COL) {
            value=TimeEncoding.toString(event.getReceptionTime());
        } else if(columnIndex>=FIRST_EXTENSION_COL) {
            int extensionIndex=columnIndex-FIRST_EXTENSION_COL;
            if(columnIndex-FIRST_EXTENSION_COL<extensions.size()) {
                value=event.getExtension(extensions.get(extensionIndex));
            }
        }
        return value;
    }

    @Override
    public String getColumnName(int col) {
        return columnNames.get(col);
    }

    /**
     * Add list of events into model.
     * @param eventList List of events to be added.
     */
    public void addEvents(final List<Event> eventList) {
        List<Event> newEvents=eventList;
        if(!extensions.isEmpty()) {
            try {
                newEvents=new ArrayList<Event>(eventList.size());
                for(Event evt:eventList) {
                    newEvents.add(Event.parseFrom(evt.toByteArray(), registry));
                }
            } catch(InvalidProtocolBufferException e) {
                e.printStackTrace();
            }
        }
        allEvents.addAll(newEvents);

        int firstr = visibleEvents.size();
        for (Event event : newEvents) {
            if (filteringTable.isEventVisible(event)) {
                visibleEvents.add(event);
            }
        }
        int lastr = visibleEvents.size() - 1;

        if (firstr <= lastr) {
            fireTableRowsInserted(firstr, lastr);
        }
    }

    /**
     * Add single event into model.
     * @param event Event to be added.
     */
    public void addEvent(final Event event) {
        Event newEvent = event;
        if(!extensions.isEmpty()) {
            try {
                newEvent=Event.parseFrom(event.toByteArray(), registry);
            } catch (InvalidProtocolBufferException e) {
                e.printStackTrace();
            }
        }
        allEvents.add(newEvent);

        if (filteringTable.isEventVisible(newEvent)) {
            visibleEvents.add(newEvent);
            int addedRow = visibleEvents.size() - 1;
            fireTableRowsInserted(addedRow, addedRow);
        }
    }

    /**
     * Access to event on the specific row.
     * @param row Row
     * @return Event on the row.
     */
    public Event getEvent(int row)
    {
        return visibleEvents.elementAt(row);
    }

    /**
     * Remove all events from the model.
     */
    public void clear()
    {
        allEvents.clear();
        visibleEvents.clear();
    }

    /**
     * Apply new filtering rules. After the change of filtering rules the
     * visible data must conform with them.
     */
    public void applyNewFilteringRules()
    {
        visibleEvents.clear();

        for (Event event : allEvents)
        {
            if (filteringTable.isEventVisible(event))
            {
                visibleEvents.add(event);
            }
        }

        fireTableDataChanged();
    }

    @Override
    public void update(Observable o, Object arg)
    {
        applyNewFilteringRules();
    }
}

@@ -39,82 +39,106 @@
 ort 
-java.lang.reflect.Field;%0Aimport java.util.ArrayList;%0Aimport java.util.L
+com.google.protobuf.Descriptors.FieldDescriptor.Type;%0Aimport com.google.protobuf.ExtensionReg
 ist
+ry
 ;%0Aim
@@ -146,150 +146,123 @@
 ort 
-java.util.Map;%0Aimport java.util.Observable;%0Aimport java.util.Observer;%0Aimport java.util.Vector;%0A%0Aimport javax.swing.table.AbstractTableModel;%0A
+com.google.protobuf.GeneratedMessage.GeneratedExtension;%0Aimport com.google.protobuf.InvalidProtocolBufferException;
 %0Aimp
@@ -436,229 +436,96 @@
 ort 
-com.google.protobuf.Descriptors.FieldDescriptor.Type;%0Aimport com.google.protobuf.ExtensionRegistry;%0Aimport com.google.protobuf.GeneratedMessage.GeneratedExtension;%0Aimport com.google.protobuf.InvalidProtocolBufferException
+javax.swing.table.AbstractTableModel;%0Aimport java.lang.reflect.Field;%0Aimport java.util.*
 ;%0A%0Ac
@@ -6635,32 +6635,82 @@
 d clear()%0A    %7B%0A
+        fireTableRowsDeleted(0, getRowCount()-1);%0A
         allEvent

Fix ArrayIndexOutOfBounds when clearing after changing filter