package com.fiveco.demo;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.util.regex.Pattern;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
import javax.swing.UIManager;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;

import com.fivecolibs.devices.FCOCDevice;
import com.fivecolibs.devices.FCOCIPI2CDevice;
import com.fivecolibs.devices.FCOCPortTCP;
import com.fivecolibs.devices.FCOCRAPDevice;
import com.fivecolibs.devices.FCOCRegisterBase;
import com.fivecolibs.devices.FCOEComState;
import com.fivecolibs.devices.FCOEDataEventType;
import com.fivecolibs.devices.FCOIDeviceEvent;
import com.fivecolibs.jComponents.FCOCGridBagJPanel;
import com.fivecolibs.utils.FCOCUtils;

/*****************************************************************************
 * PROJECT: FiveCo's Devices Code sample
 *
 * Copyright: FiveCo Copyright 2002-2021
 *
 * @author agardiol
 * @version 3.0
 *
 *          <pre>
 * Revision:
 * 1.0  27.08.13 agardiol 	First version.
 * 1.1  12.09.13 agardiol 	Update FCOCDevice to v1.8
 * 2.0  16.10.20 agardiol	Update to last library revision (16.10.20)
 * 							Add FRAP devices types (serial port connections)
 * 3.0  13.07.21 agardiol	Change library to JAR file
 *
 * Externals JAR dependencies:
 * 		FiveCoLibs.jar					FiveCo's library
 * 		image4j-0.7.2.jar				http://image4j.sourceforge.net/
 * 		purejavacomm.jar				http://www.sparetimelabs.com/purejavacomm/purejavacomm.php
 * 		spring-core-5.2.14.RELEASE.jar	https://github.com/spring-projects/spring-framework/
 *
 *
 * License:
 * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser 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 Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this program. If not, see
 * <http://www.gnu.org/licenses/>.
 *
 *          </pre>
 *
 *****************************************************************************/
@SuppressWarnings("serial")
public class FCOCDemoApp extends JFrame implements ActionListener, FCOIDeviceEvent, WindowListener {

	private final static String	m_szVersion			= "Version 3.0 (13.07.2021)";
	private FCOCDevice			m_FiveCoDevice;										// FiveCo device (by example FMod-IPECMOT 48/10)
	private final String		m_szIPAddress		= "192.168.16.101"; // "169.254.5.5";

	/* Simple GUI */
	private final JLabel		m_TCPComMessage		= new JLabel("TCP com state : Disconnected");
	private final JLabel		m_VersionLabel		= new JLabel("Rev :");
	private final JLabel		m_VersionDispLabel	= new JLabel("----------------");
	private final JLabel		m_MACLabel			= new JLabel("MAC address : ");
	private final JLabel		m_MACValueLabel		= new JLabel("xx-xx-xx-xx-xx-xx");
	private final JLabel		m_IPLabel			= new JLabel("IP Address :");
	private final JSpinner		m_IP1Text			= new JSpinner(new SpinnerNumberModel(1, 0, 255, 1));
	private final JSpinner		m_IP2Text			= new JSpinner(new SpinnerNumberModel(1, 0, 255, 1));
	private final JSpinner		m_IP3Text			= new JSpinner(new SpinnerNumberModel(1, 0, 255, 1));
	private final JSpinner		m_IP4Text			= new JSpinner(new SpinnerNumberModel(1, 1, 254, 1));
	private final JButton		m_ChgIPButton		= new JButton("Change");
	private final JLabel		m_SNMLabel			= new JLabel("Subnet mask :");
	private final JSpinner		m_SNM1Text			= new JSpinner(new SpinnerNumberModel(255, 0, 255, 1));
	private final JSpinner		m_SNM2Text			= new JSpinner(new SpinnerNumberModel(255, 0, 255, 1));
	private final JSpinner		m_SNM3Text			= new JSpinner(new SpinnerNumberModel(255, 0, 255, 1));
	private final JSpinner		m_SNM4Text			= new JSpinner(new SpinnerNumberModel(255, 0, 255, 1));
	private final JButton		m_ChgSNMButton		= new JButton("Change");
	private final JLabel		m_NameLabel			= new JLabel("Device name : ");
	private final JTextField	m_NameText			= new JTextField(new DeviceNamePlainDocument(), "", 16);
	private final JButton		m_ChgNameButton		= new JButton("Change");

	/* Constructor and main method --------------------------------------------------------------------------------- */

	/**
	 * Demo application constructor
	 */
	public FCOCDemoApp() {
		super("FiveCo's device class demo application - " + m_szVersion);

		// Create GUI and display it
		addWindowListener(this);
		final FCOCGridBagJPanel MainPanel = new FCOCGridBagJPanel(3, 3, 3, 3);

		MainPanel.add(m_TCPComMessage, 0, 0, 5, 1);
		MainPanel.add(m_VersionLabel, 0, 1, 1, 1);
		MainPanel.add(m_VersionDispLabel, 1, 1, 2, 1);
		MainPanel.add(m_MACLabel, 0, 2, 1, 1);
		MainPanel.add(m_MACValueLabel, 1, 2, 4, 1);
		MainPanel.add(m_IPLabel, 0, 3, 1, 1, 20, 10);
		MainPanel.add(m_IP1Text, 1, 3, 1, 1, 12, 0);
		MainPanel.add(m_IP2Text, 2, 3, 1, 1, 12, 0);
		MainPanel.add(m_IP3Text, 3, 3, 1, 1, 12, 0);
		MainPanel.add(m_IP4Text, 4, 3, 1, 1, 12, 0);
		MainPanel.add(m_ChgIPButton, 5, 3, 1, 1, 36, 0, GridBagConstraints.HORIZONTAL, GridBagConstraints.CENTER);
		MainPanel.add(m_SNMLabel, 0, 4, 1, 1, 20, 10);
		MainPanel.add(m_SNM1Text, 1, 4, 1, 1, 12, 0);
		MainPanel.add(m_SNM2Text, 2, 4, 1, 1, 12, 0);
		MainPanel.add(m_SNM3Text, 3, 4, 1, 1, 12, 0);
		MainPanel.add(m_SNM4Text, 4, 4, 1, 1, 12, 0);
		MainPanel.add(m_ChgSNMButton, 5, 4, 1, 1, 36, 0, GridBagConstraints.HORIZONTAL, GridBagConstraints.CENTER);
		MainPanel.add(m_NameLabel, 0, 6, 1, 1);
		MainPanel.add(m_NameText, 1, 6, 4, 1);
		MainPanel.add(m_ChgNameButton, 5, 6, 1, 1, 36, 0, GridBagConstraints.HORIZONTAL, GridBagConstraints.CENTER);
		add(MainPanel);

		/* Add action listener for buttons */
		m_ChgIPButton.addActionListener(this);
		m_ChgSNMButton.addActionListener(this);
		m_ChgNameButton.addActionListener(this);

		setMinimumSize(new Dimension(600, 200));
		setResizable(true);
		FCOCUtils.centerFrame(this);
		pack();
		setVisible(true);

		connectToDevice();
	}

	/**
	 * Start application
	 *
	 * @param args
	 *            Not used
	 */
	public static void main(final String[] args) {
		try {
			UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
		} catch (final Exception e) {
		}

		final FCOCDemoApp App = new FCOCDemoApp();
		App.setDefaultCloseOperation(EXIT_ON_CLOSE);
	}

	/* Private methods --------------------------------------------------------------------------------------------- */

	private void connectToDevice() {

		try {
			// Build FiveCo device object

			// Connect to a FiveCo's IP device with the legacy protocol
			m_FiveCoDevice = new FCOCIPI2CDevice(new FCOCPortTCP(m_szIPAddress), 32);

			// Alternative: To connect by serial port to a FiveCo's device with FRAP protocol
			// m_FiveCoDevice = new FCOCRAPDevice(32, 128, new FCOCSerialPort("Com1", 115200), 0x01, 0x55, false, -1);

			// Set this app as a device listener (to receive callbacks)
			m_FiveCoDevice.addDeviceListener(this);

		} catch (final Exception e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}

		/* FiveCo's device registers description */
		/* Should be defined in an Enum for good practice */
		try {
			m_FiveCoDevice.addRegister("Type", 0x00, 4, FCOCRegisterBase.nAT_R, FCOCRegisterBase.nDT_FIXEDP_50_50, false);
			m_FiveCoDevice.addRegister("Version", 0x01, 4, FCOCRegisterBase.nAT_R, FCOCRegisterBase.nDT_UINT, false);
			m_FiveCoDevice.addRegister("Reset", 0x02, 0, FCOCRegisterBase.nAT_FCT, -1, false);
			m_FiveCoDevice.addRegister("SaveUser", 0x03, 0, FCOCRegisterBase.nAT_FCT, -1, false);
			m_FiveCoDevice.addRegister("RestoreUser", 0x04, 0, FCOCRegisterBase.nAT_FCT, -1, false);
			m_FiveCoDevice.addRegister("RestoreFactory", 0x05, 0, FCOCRegisterBase.nAT_FCT, -1, false);
			// m_FiveCoDevice.addRegister("Voltage", 0x07, 4, FCOCRegisterBase.nAT_R, FCOCRegisterBase.nDT_FIXEDP, true);

			// m_FiveCoDevice.addRegister("ComOptions", 0x10, 4, FCOCRegisterBase.nAT_RW, FCOCRegisterBase.nDT_BIN, false);
			m_FiveCoDevice.addRegister("MACAddress", 0x11, 6, FCOCRegisterBase.nAT_R, FCOCRegisterBase.nDT_ARRAY, false);
			m_FiveCoDevice.addRegister("IPAddress", 0x12, 4, FCOCRegisterBase.nAT_RW, FCOCRegisterBase.nDT_ARRAY, false);
			m_FiveCoDevice.addRegister("SubnetMask", 0x13, 4, FCOCRegisterBase.nAT_RW, FCOCRegisterBase.nDT_ARRAY, false);
			// m_FiveCoDevice.addRegister("TCPWatchdog", 0x14, 1, FCOCRegisterBase.nAT_RW, FCOCRegisterBase.nDT_UINT, false);
			m_FiveCoDevice.addRegister("Name", 0x15, 16, FCOCRegisterBase.nAT_RW, FCOCRegisterBase.nDT_TEXT, false);

			// ... to be completed with other registers of FiveCo's device ...
			// By example for IPECMOT :
			// m_FiveCoDevice.addRegister("Position", 0x26, 4, FCOCRegisterBase.nAT_RW, FCOCRegisterBase.nDT_INT, true);

		} catch (final Exception e) {
			System.err.println("Exception : Create FiveCo device failed (" + e.getLocalizedMessage() + ")");
		}

		// Read initial parameters
		try {
			m_FiveCoDevice.getValue("Version");
			m_FiveCoDevice.getValue("IPAddress");
			m_FiveCoDevice.getValue("MACAddress");
			m_FiveCoDevice.getValue("SubnetMask");
			m_FiveCoDevice.getValue("Name");
		} catch (final Exception e) {
			System.err.println("Exception : Read initial parameters failed (" + e.getLocalizedMessage() + ")");
		}
	}

	private void disconnectFromDevice() {
		if (m_FiveCoDevice != null) {
			m_TCPComMessage.setText("Disconnecting from device.");
			m_FiveCoDevice.delete();
			m_FiveCoDevice = null;
			m_TCPComMessage.setText("Disconnected.");
		}
	}

	/* FCOIDeviceEvent methods ------------------------------------------------------------------------------------- */

	/*
	 * Callback for connection state. Reconnection is automatically tried. (non-Javadoc)
	 * @see com.fiveco.devices.FCOIDeviceEvent#comDevEvtOccured(com.fiveco.devices.FCOCDevice, int)
	 */
	@Override
	public void comDevEvtOccured(final FCOCDevice Source, final FCOEComState ComState) {
		boolean isError = true;
		String szComMessage;
		switch (ComState) {
			case CONNECTING:
				szComMessage = "Connecting...";
				isError = false;
				break;
			case CONNECTED:
				szComMessage = "Connected";
				if (m_FiveCoDevice != null) {
					if (m_FiveCoDevice.getClass() == FCOCIPI2CDevice.class) {
						System.out.println(m_szIPAddress);
					} else if ((m_FiveCoDevice.getClass() == FCOCRAPDevice.class)) {

					}
				}
				isError = false;
				break;
			case LOST:
				szComMessage = "Connection lost";
				break;
			case DISCONNECTED:
				szComMessage = "Disconnected";
				isError = false;
				break;
			case TIMEOUT:
				szComMessage = "Timeout";
				break;
			case PERIODTOOSHORT:
				szComMessage = "Com period too short";
				isError = false;
				break;
			case FAILED:
				szComMessage = "Connection failed";
				break;
			case CHECKSUMERROR:
				szComMessage = "Checksum error";
				break;
			case FRAMEIDERROR:
				szComMessage = "Bad frame ID in RX frame";
				break;
			case FRAMESIZEERROR:
				szComMessage = "Bad size of RX frame";
				break;
			case FRAMEDATAERROR:
				szComMessage = "Data error in received frame.";
				break;
			case FRAGMENTED:
				return;
			default:
				szComMessage = "Unknown (" + ComState + ")";
				break;
		}
		if (isError) {
			System.out.println("Com error : " + szComMessage);
			m_TCPComMessage.setForeground(Color.RED);
		} else if (ComState == FCOEComState.PERIODTOOSHORT) {
			m_TCPComMessage.setForeground(FCOCUtils.m_ORANGEWARNING);
		} else if (ComState != FCOEComState.DISCONNECTED) {
			m_TCPComMessage.setForeground(FCOCUtils.m_MEDIUMGREEN);
		} else {
			m_TCPComMessage.setForeground(Color.BLACK);
		}
		m_TCPComMessage.setText("Com state : " + szComMessage);
	}

	/*
	 * Callback for data read from device. Type of object data depends on register type defined in connectToDevice()
	 * (non-Javadoc)
	 * @see com.fiveco.devices.FCOIDeviceEvent#dataDevEvtOccured(com.fiveco.devices.FCOCDevice, java.lang.String,
	 * java.lang.Object, java.lang.Object)
	 */
	@Override
	public void dataDevEvtOccured(	final FCOCDevice Source, final String szRegisterName, final Object Data, final FCOEDataEventType EvtType,
									final Object CallbackParameter) {

		if (szRegisterName.compareTo("Version") == 0) {
			final long lVersion = ((Long) Data).longValue();
			if ((lVersion & 0xFF00FF00) == 0) {
				m_VersionLabel.setText("Rev :");
				m_VersionDispLabel.setText((int) (lVersion / 65536) + "." + (lVersion & 0xFFFF));
			} else {
				m_VersionLabel.setText("Rev (FW - HW) :");
				m_VersionDispLabel.setText(	(int) ((lVersion & 0xFF00) >> 8)	+ "." + (lVersion & 0xFF) + " - " + (int) ((lVersion & 0xFF000000) >> 24)
											+ "." + ((lVersion & 0xFF) >> 16));
			}
		} else if (szRegisterName.compareTo("IPAddress") == 0) {
			final Byte[] aIPAddress = ((Byte[]) Data);
			m_IP4Text.setValue(((int) aIPAddress[0]) & 0xFF);
			m_IP3Text.setValue(((int) aIPAddress[1]) & 0xFF);
			m_IP2Text.setValue(((int) aIPAddress[2]) & 0xFF);
			m_IP1Text.setValue(((int) aIPAddress[3]) & 0xFF);
		} else if (szRegisterName.compareTo("SubnetMask") == 0) {
			final Byte[] aSubnetMask = ((Byte[]) Data);
			m_SNM4Text.setValue(((int) aSubnetMask[0]) & 0xFF);
			m_SNM3Text.setValue(((int) aSubnetMask[1]) & 0xFF);
			m_SNM2Text.setValue(((int) aSubnetMask[2]) & 0xFF);
			m_SNM1Text.setValue(((int) aSubnetMask[3]) & 0xFF);
		} else if (szRegisterName.compareTo("MACAddress") == 0) {
			final Byte[] aMACAddress = ((Byte[]) Data);
			String szMACAddress = "";
			for (int i = 5; i >= 0; i--) {
				szMACAddress += (FCOCUtils.toHexString(aMACAddress[i]) + " ");
			}
			m_MACValueLabel.setText(szMACAddress);
		} else if (szRegisterName.compareTo("Name") == 0) {
			m_NameText.setText((String) Data);
		}
	}

	@Override
	public void autoreadRegAddedToQueue(final FCOCDevice arg0, final String arg1, final Object arg2) {
		// TODO Auto-generated method stub
	}

	/* ActionListener methods -------------------------------------------------------------------------------------- */

	@Override
	public void actionPerformed(final ActionEvent e) {

		final Object EvtSource = e.getSource();

		if (EvtSource == m_ChgIPButton) {
			final Byte[] aIPAddress = new Byte[4];
			aIPAddress[3] = new Byte((byte) ((SpinnerNumberModel) m_IP1Text.getModel()).getNumber().intValue());
			aIPAddress[2] = new Byte((byte) ((SpinnerNumberModel) m_IP2Text.getModel()).getNumber().intValue());
			aIPAddress[1] = new Byte((byte) ((SpinnerNumberModel) m_IP3Text.getModel()).getNumber().intValue());
			aIPAddress[0] = new Byte((byte) ((SpinnerNumberModel) m_IP4Text.getModel()).getNumber().intValue());
			m_FiveCoDevice.setValue("IPAddress", aIPAddress);
		} else if (EvtSource == m_ChgSNMButton) {
			final Byte[] aSubnetMask = new Byte[4];
			aSubnetMask[3] = new Byte((byte) ((SpinnerNumberModel) m_SNM1Text.getModel()).getNumber().intValue());
			aSubnetMask[2] = new Byte((byte) ((SpinnerNumberModel) m_SNM2Text.getModel()).getNumber().intValue());
			aSubnetMask[1] = new Byte((byte) ((SpinnerNumberModel) m_SNM3Text.getModel()).getNumber().intValue());
			aSubnetMask[0] = new Byte((byte) ((SpinnerNumberModel) m_SNM4Text.getModel()).getNumber().intValue());
			m_FiveCoDevice.setValue("SubnetMask", aSubnetMask);
		} else if (EvtSource == m_ChgNameButton) {
			m_FiveCoDevice.setValue("Name", m_NameText.getText());
		}
	}

	/* WindowListener methods -------------------------------------------------------------------------------------- */

	@Override
	public void windowOpened(final WindowEvent e) {
	}

	@Override
	public void windowClosing(final WindowEvent WinEvt) {

		final Object WinEvtSource = WinEvt.getSource();

		if (WinEvtSource == this) {
			disconnectFromDevice();
		}
	}

	@Override
	public void windowClosed(final WindowEvent e) {
	}

	@Override
	public void windowIconified(final WindowEvent e) {
	}

	@Override
	public void windowDeiconified(final WindowEvent e) {
	}

	@Override
	public void windowActivated(final WindowEvent e) {
	}

	@Override
	public void windowDeactivated(final WindowEvent e) {
	}

	/* Additional private classes ---------------------------------------------------------------------------------- */

	/**
	 * Plaindocument to check device name validity
	 */
	class DeviceNamePlainDocument extends PlainDocument {
		@Override
		public void insertString(final int nOffset, final String szString, final AttributeSet Attrib) throws BadLocationException {
			final String szCurrent = getText(0, getLength());
			final String szBeforeOffset = szCurrent.substring(0, nOffset);
			final String szAfterOffset = szCurrent.substring(nOffset, szCurrent.length());
			final String proposedResult = szBeforeOffset + szString + szAfterOffset;
			if (Pattern.compile(".{0,16}").matcher(proposedResult).matches()) {
				super.insertString(nOffset, szString, Attrib);
			}
		}
	}

}
