package nl.nikhef.sfp;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;

import com.ftdi.FTD2XXException;
import com.ftdi.FTDevice;

import nl.nikhef.rebus.dev.PCA9848;
import nl.nikhef.rebus.ftdi.GPIO;
import nl.nikhef.rebus.ftdi.I2C;
import nl.nikhef.rebus.ftdi.JavaFTD2xxMPSSE;
import nl.nikhef.rebus.ftdi.MPSSE;
import nl.nikhef.sfp.i2c.I2CLink;

public class MultiSFPDevice extends SFPDeviceBase {


	private static final Logger LOG = Logger.getLogger(MultiSFPDevice.class.getSimpleName());
	
	private static final int PCA_RESET	 = I2C.GPIO_AL0;
	private static final int ACTIVE_LED	 = I2C.GPIO_AL1;
	private static final int TX_LED		 = I2C.GPIO_AL2;
	private static final int RX_LED		 = I2C.GPIO_AL3;
	
	private static final int MODULE_PRESENT_IOS   = GPIO.GPIO_AL0 | GPIO.GPIO_AL1 | GPIO.GPIO_AL2 | GPIO.GPIO_AL3;
	private static final int MODULE_PRESENT_SHIFT = 4;

	
	private static final int OUTPUT_MASK = ACTIVE_LED | TX_LED | RX_LED;
	
	private static final int NO_OF_BAYS = 4;
	private FTDevice	_portA;	// FTDI Port A device
	private FTDevice	_portB;	// FTDI Port B device
	private String 		_serial;
	private I2C 		_i2c;
	private GPIO		_gpio;
	private MPSSE 		_mpsseA;
	private MPSSE 		_mpsseB;
	private PCA9848 	_mux;
	private int 		_selected = -1;
	private I2CLink[] _links = new I2CLink[NO_OF_BAYS];
	private boolean     _shutdown = false;
	private Object		_monitor;
	
	private volatile int _modulePresentMask = 0;
	
	public MultiSFPDevice(String ftdiSerial, Object monitor) throws IOException 
	{
		_serial = ftdiSerial;
		
	//	LOG.setLevel(Level.FINE);
		LOG.fine("Created in SFP devce");
		
		_monitor = monitor;
		
	
		try {
			_portA = FTDevice.getDevicesBySerialNumber(ftdiSerial + "A").get(0);
			_portB = FTDevice.getDevicesBySerialNumber(ftdiSerial + "B").get(0);
		} catch (FTD2XXException e) {
			throw new IOException("Could not initialize FTDI", e);
		}
		
		LogManager.getLogManager().getLogger("com.ftdi.FTDevice").setLevel(Level.WARNING);
		
		_mpsseA = new JavaFTD2xxMPSSE(_portA);
		_mpsseB = new JavaFTD2xxMPSSE(_portB);
		_i2c = new I2C(_mpsseA, I2C.SPEED_NORMAL, false);
		_i2c.setModes(OUTPUT_MASK | PCA_RESET, OUTPUT_MASK | PCA_RESET);
		
		// Reset PCA9848
		_i2c.setOutputs(0, PCA_RESET);
		_i2c.setOutputs(PCA_RESET, PCA_RESET);
		
		_gpio = new GPIO(_mpsseB);
		_gpio.setModes(0, MODULE_PRESENT_IOS);

		setLeds(false, false);
		// _i2c.setOutputs(0xF0, 0xE0);
		_i2c.wakeI2C();
		
		_mux = new PCA9848(_i2c, PCA9848.ADDRESS_HHH);
		_mux.setSingle(-1);
		for (int i = 0; i < _links.length; ++i) 
		{
			_links[i] = new MultiSFPI2CLink(i);			
		}
	}
	
	
	@Override
	public String getSerial() {
		return _serial;
	}

	@Override
	public int getBayCount() {
		return NO_OF_BAYS;
	}

	@Override
	public I2CLink getLink(int bay) throws IOException {
		return _links[bay];
	}

	@Override
	public boolean isModulePresent(int bay) {
		return ((_modulePresentMask >> bay) & 0x1) != 0;
	}


	private void setLeds(boolean tx, boolean rx) throws IOException
	{
		int leds = ACTIVE_LED;
		if (tx) leds |= TX_LED;
		if (rx) leds |= RX_LED;
		
		_i2c.setOutputs(leds, OUTPUT_MASK);
	}
	
	private void select(int bay) throws IOException {
		if (_selected == bay) return;
		try {
			LOG.fine(String.format("Select bay %d", bay));
			synchronized (_monitor) {
				_mux.setSingle(bay);	
			}
		} catch (IOException ioe) {
			_selected = -1;
			throw ioe;
		}
		_selected = bay;
	}
	
	private void updateIOState() throws IOException
	{
		int nowPresent;
		try {
			synchronized(_monitor) {
				nowPresent = 0xF ^ (_gpio.getInputs(MODULE_PRESENT_IOS) >> MODULE_PRESENT_SHIFT);
			}
		} catch (IOException io)
		{
			nowPresent = 0;			
		}
		
		int changes = nowPresent ^ _modulePresentMask;
		
		_modulePresentMask = nowPresent;
		
		System.out.printf("Present: %02x\n", _modulePresentMask);
		
		if (changes != 0) {
			for (int i = 0; i < NO_OF_BAYS; ++i)
			{
				if ((changes & 1 << i) != 0) {
					sdlMgr.getProxy().sfpModuleStateChanged(this, i);
				}
			}
		}
		
	}


	@Override
	public void updateModules() {
		try {
			updateIOState();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private  byte[] i2cWrRd(int addr, byte[] dataWr, boolean contRd, int rdLen) throws IOException {
		synchronized(_monitor) {
			setLeds(true, true);
			byte[] dta = _i2c.writeRead(addr, dataWr, rdLen);
			setLeds(false, false);
			return dta;
			}
	}
	
	private  void i2cWrite(int addr, byte[] data) throws IOException {
		synchronized(_monitor) {
			setLeds(true, false);
			_i2c.write(addr, data);
			setLeds(false, false);
		}
	}

	private synchronized byte[] i2cRead(int addr, int len) throws IOException {
		synchronized(_monitor) {
			setLeds(false, true);
			byte[] dta = _i2c.read(addr, len);
			setLeds(false, false);
			return dta;
		}
	}
	
		
	
	private class MultiSFPI2CLink implements I2CLink {

		private final int _muxPort;
		
		public MultiSFPI2CLink(int muxPort) {
			_muxPort = muxPort;
		}

		@Override
		public void open() throws IOException 
		{
			select(_muxPort);									
		}

		@Override
		public void close() throws IOException {
			// select(-1);
		}

		@Override
		public void shutdown() throws IOException {
		}

		public void checkSelected() throws IOException
		{
			if (_muxPort != _selected) {
				throw new IOException(String.format("Device bay %d is not selected, %d is", _muxPort, _selected));				
			}
		}
		
		@Override
		public void i2cWrite(int addr, byte[] data) throws IOException {
			checkSelected();
			MultiSFPDevice.this.i2cWrite(addr, data);
		}

		@Override
		public byte[] i2cRead(int addr, int len) throws IOException {
			checkSelected();
			return MultiSFPDevice.this.i2cRead(addr, len);
		}

		@Override
		public byte[] i2cWrRd(int addr, byte[] dataWr, boolean contRd, int rdLen) throws IOException {
			checkSelected();
			return MultiSFPDevice.this.i2cWrRd(addr, dataWr, contRd, rdLen);
		}
		
	}



	@Override
	public void shutdown() 
	{
		if (_shutdown) return;
		_shutdown = true;
		
		int wasPresent = _modulePresentMask;

		_modulePresentMask = 0;
		
		if (wasPresent != 0) {
			for (int i = 0; i < NO_OF_BAYS; ++i)
			{
				if ((wasPresent & 1 << i) != 0) {
					sdlMgr.getProxy().sfpModuleStateChanged(this, i);
				}
			}
		}
		

		try {
			_mpsseA.close();
		} catch (IOException e) {
		}
		try {
			_mpsseB.close();
		} catch (IOException e) {
		}
	}
}
