package nl.nikhef.safaripark.monitor;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.swing.event.TableModelEvent;
import javax.swing.table.AbstractTableModel;

import nl.nikhef.safaripark.ContextCache;
import nl.nikhef.sfp.SFPDevice;
import nl.nikhef.sfp.SFPDeviceListener;
import nl.nikhef.sfp.SFPManager;
import nl.nikhef.sfp.SFPProvider;
import nl.nikhef.sfp.SFPProviderListener;
import nl.nikhef.sfp.ddmi.DDMI;
import nl.nikhef.sfp.ddmi.DDMIContext;
import nl.nikhef.sfp.ddmi.DDMIFilter;
import nl.nikhef.sfp.ddmi.DDMIUtils;
import nl.nikhef.sfp.ddmi.DDMIValue;

@SuppressWarnings("serial")
public class MonitorModel extends AbstractTableModel implements SFPDeviceListener, SFPProviderListener {

	private final DDMI _ddmi;
	private List<DDMIValue> _columns;
	
	private SortedSet<SFPDevice> _devices = new TreeSet<SFPDevice>();
	
	private ContextCache _ctxCache;
	
	private List<DevAndBay> _rows = new ArrayList<DevAndBay>();
	
	private class DevAndBay
	{
		final SFPDevice dev;
		final int bay;
		DDMIContext ctx;
		private String[] data;
		
		
		public DevAndBay(SFPDevice dev, int bay) throws IOException {
			
			// this.dev.getLink(bay);
			ctx = _ctxCache.getContextFor(dev, bay);
			
			this.dev = dev;
			this.bay = bay;
		}
	}
	
	
	public MonitorModel(DDMI ddmi, ContextCache ctxCache, SFPManager sfpManager) {
		_ctxCache = ctxCache;
		sfpManager.addSFPProviderListener(this);
		_ddmi = ddmi;
		_columns = _ddmi.findElements(new DDMIFilter<DDMIValue>() {

			@Override
			public boolean matches(DDMIValue element) {
				return element.isMonitor();
			}

			@Override
			public Class<DDMIValue> matchClass() {
				return DDMIValue.class;
			}
			
		});
		
	}
	
	
	public List<DDMIValue> getColumns() {
		return _columns;
	}
	
	public void setColumns(List<DDMIValue> columns) {
		_columns = columns;
		fireTableStructureChanged();
	}
	
	@Override
	public int getColumnCount() {
		return _columns.size() + 1;
	}
	
	@Override
	public String getColumnName(int column) {
		if (column == 0) return "Module";
		return _columns.get(column - 1).getLabel();
	}

	@Override
	public int getRowCount() {
		return _rows.size();
	}
	
	
	public void updateData()
	{
		for (int i = 0; i < _rows.size(); i++)
		{
			DevAndBay dab = _rows.get(i);
			
			if (dab.data == null || dab.data.length != _columns.size()) 
			{
				dab.data = new String[_columns.size()];
			}
			
			for (int j = 0; j < _columns.size(); j++)
			{
				DDMIValue val = _columns.get(j);
				if (val.isValid(dab.ctx)) {
					try {
						dab.data[j] = DDMIUtils.getValueAsSting(val, dab.ctx);
					} catch (Exception e) {
						dab.data[j] = "?";
					}
				} else {
					dab.data[j] = "-";
				}
			}
		}
		fireTableDataChangedPreserveSelect();
	}
	

	@Override
	public Object getValueAt(int row, int col) {
		
		DevAndBay dab = _rows.get(row);
		
		if (col == 0) {
			return dab.dev.getModuleSerial(dab.bay);
		} else {
			if (dab.data != null && (col - 1) < dab.data.length) 
			{
				return dab.data[col - 1];
			}
			return "?";
		}
	}


     public void fireTableDataChangedPreserveSelect() {
         fireTableChanged(new TableModelEvent(this, //tableModel
                                              0, //firstRow
                                              getRowCount() - 1, //lastRow 
                                              TableModelEvent.ALL_COLUMNS, //column 
                                              TableModelEvent.UPDATE)); //changeType
     }
	
	@Override
	public void sfpModuleStateChanged(SFPDevice dev, int bay) 
	{
		if (!_devices.contains(dev)) {
			_devices.add(dev);
		}
		if (updateRowCount()) {
			fireTableDataChanged();
		}
	}


	private boolean updateRowCount() {
		int rc = 0;
		
		List<DevAndBay> dAb = new ArrayList<DevAndBay>();
		
		for (SFPDevice dev : _devices)
		{
			for (int i = 0; i < dev.getBayCount(); ++i) 
			{
				if (dev.isModulePresent(i)) {
					
					if (!dev.hasDiagnostics(i)) continue;
					
					try {
						dAb.add(new DevAndBay(dev, i));
						rc++;
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		}
		
		_rows = dAb;
		return true;
	}


	@Override
	public void sfpDeviceAdded(SFPProvider provider, SFPDevice dev) {
		_devices.add(dev);
		dev.addDeviceListener(this);
	}


	@Override
	public void sfpDeviceRemoved(SFPProvider provider, SFPDevice dev) {
		_devices.remove(dev);
		dev.removeDeviceListener(this);
	}


	public void writeHeader(PrintWriter logFile) {
		
		logFile.print("Module Serial");
		
		for (DDMIValue val : _columns) {
			logFile.print(";" + val.getName());
		}
		logFile.println();
	}

	public void writeData(PrintWriter logFile) 
	{
		for (int i = 0; i < _rows.size(); i++)
		{
			DevAndBay dab = _rows.get(i);
			
			logFile.print(dab.dev.getModuleSerial(dab.bay));
			
			for (int j = 0; j < _columns.size(); j++)
			{
				logFile.print(";");
				logFile.print(dab.data[j]);
			}
			logFile.println();
		}
	}
	
	
}
