/*
 * Part of USBtinViewer - Simple GUI for USBtin - USB to CAN interface
 * http://www.fischl.de/usbtin
 *
 * Copyright (C) 2015  Thomas Fischl 
 * 
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

import de.fischl.usbtin.*;
import java.util.ArrayList;
import java.util.TreeMap;
import javax.swing.ImageIcon;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;

/**
 * Table model for log messages
 * 
 * @author Thomas Fischl
 */
public class MonitorMessageTableModel implements TableModel {

    /** Column titles */
    protected final String[] titles = new String[]{"Period", "Count", "Type", "Id", "DLC", "Data"};
    
    /** Column classes */
    protected final Class[] classes = new Class[]{String.class, String.class, ImageIcon.class, String.class, String.class, String.class};
    
    /** Type icons */
    protected ImageIcon[] icons;
    
    /** List containing active listeners */
    private final ArrayList<TableModelListener> listeners = new ArrayList<TableModelListener>();
    
    /** List of log messages to view */
    private final TreeMap<Integer, MonitorMessage> messages = new TreeMap<Integer, MonitorMessage>();

    /**
     * Standard constructor
     */
    public MonitorMessageTableModel() {
        icons = new ImageIcon[]{
            new ImageIcon(getClass().getResource("/res/icons/info.png")),
            new ImageIcon(getClass().getResource("/res/icons/error.png")),
            new ImageIcon(getClass().getResource("/res/icons/receive.png")),
            new ImageIcon(getClass().getResource("/res/icons/send.png"))
        };
    }

    /**
     * Add given message to message map
     * 
     * @param logmessage Message to add
     */
    public void add(LogMessage logmessage) {
        
        TableModelEvent tableEvent;
        
        // prepare key for this message
        int key = logmessage.getCanmsg().getId() << 2;                        
        if (logmessage.getCanmsg().isExtended()) {
            key |= 1;
        }
        if (logmessage.getType() == LogMessage.MessageType.OUT) {
            key |= 2;
        }
                
        // check if message already in tree, add/update it and generate event
        if (messages.containsKey(key)) {
            MonitorMessage message = (MonitorMessage) messages.get(key);            
            message.increaseCount();
            message.setPeriod(logmessage.getTimestamp() - message.getLastLogMessage().getTimestamp());
            message.setLastLogMessage(logmessage);
            
            int index = messages.headMap(key).size();
            tableEvent = new TableModelEvent(this, index, index, TableModelEvent.ALL_COLUMNS, TableModelEvent.UPDATE);
            
        } else {
            messages.put(key, new MonitorMessage(logmessage));
            
            int index = messages.headMap(key).size();
            tableEvent = new TableModelEvent(this, index, index, TableModelEvent.ALL_COLUMNS, TableModelEvent.INSERT);            
        }
                
        // report event to listeners
        for (int i = 0, n = listeners.size(); i < n; i++) {
            listeners.get(i).tableChanged(tableEvent);
        }
    }
    
    /**
     * Clear the message list
     */
    public void clear() {
        
        if (messages.size() == 0) return;        
        int lastRow = messages.size() - 1;

        messages.clear();

        TableModelEvent e = new TableModelEvent(this, 0, lastRow, TableModelEvent.ALL_COLUMNS, TableModelEvent.DELETE);
        for (int i = 0, n = listeners.size(); i < n; i++) {
            listeners.get(i).tableChanged(e);
        }
    }
    
    /**
     * Get count of rows
     * 
     * @return Count of rows in the message list
     */
    @Override
    public int getRowCount() {
        return messages.size();
    }

    /**
     * Get count of columns
     * 
     * @return Count of columns of message list
     */
    @Override
    public int getColumnCount() {
        return titles.length;
    }

    /**
     * Get name of given column
     * 
     * @param i Column id
     * @return Name of given column
     */
    @Override
    public String getColumnName(int i) {
        return titles[i];
    }

    /**
     * Get class of given column
     * 
     * @param i Column id
     * @return Class of given column
     */
    @Override
    public Class<?> getColumnClass(int i) {
        return classes[i];
    }

    /**
     * Determin if given cell is editable
     * 
     * @param row Row id
     * @param col Column id
     * @return True, if editable
     */
    @Override
    public boolean isCellEditable(int row, int col) {
        return false;
    }

    /**
     * Get value of given table cell
     * 
     * @param row Row id
     * @param col Column id
     * @return Value of given cell
     */
    @Override
    public Object getValueAt(int row, int col) {

        MonitorMessage message = (MonitorMessage) messages.values().toArray()[row];
        
        LogMessage logmessage = message.getLastLogMessage();
        
        CANMessage canmsg = logmessage.getCanmsg();
        switch (col) {
            case 0:                
                return message.getPeriod();
            case 1:
                return message.getCount();
            case 2:
                return icons[logmessage.getType().ordinal()];

            case 3:
                if (canmsg.isExtended()) {
                    return String.format("%08xh", canmsg.getId());
                } else {
                    return String.format("%03xh", canmsg.getId());
                }

            case 4:
                return canmsg.getData().length;

            case 5:

                if (canmsg.isRtr()) {
                    return "Remote Transmission Request";
                }

                String s = "";
                byte[] data = canmsg.getData();
                for (int i = 0; i < data.length; i++) {
                    if (i > 0) {
                        s = s.concat(" ");
                    }
                    s = s.concat(String.format("%02x", data[i]));
                }

                return s;

        }
        return "";
        
    }

    /**
     * Set value of given cell
     * 
     * @param o Object to set
     * @param i Row
     * @param i1 Column
     */
    @Override
    public void setValueAt(Object o, int i, int i1) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    /**
     * Add listern
     * 
     * @param tl Listener to add
     */
    @Override
    public void addTableModelListener(TableModelListener tl) {
        listeners.add(tl);
    }

    /**
     * Remove listener
     * 
     * @param tl Listener to remove
     */
    @Override
    public void removeTableModelListener(TableModelListener tl) {
        listeners.remove(tl);
    }

}