package de.ctdo.crashtest.domotics;

import de.ctdo.crashtest.log.Logger;
import gnu.io.*;
import java.io.IOException;
import java.io.OutputStream;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class Relaisboard implements IRelaisboard {
    private SerialPort serialPort;
    private OutputStream outputStream;
    private Boolean serialPortGeoeffnet = false;
    private String portName = "/dev/ttyUSB0";
    private final Object mLock = new Object();
    private final Map<Integer,Thread> threadMap = new ConcurrentHashMap<Integer, Thread>();
    private final Map<Integer,Boolean> stopMap = new ConcurrentHashMap<Integer, Boolean>();

    public Relaisboard(final String port) {
        this.portName = port;
    }

    private void sendData(final int relais, final boolean state) {
        synchronized (mLock) {
            if(relais >= 0 && relais < 8 && outputStream != null) {
                char charsOff[] = { 'a','b','c','d','e','f','g','h' };
                char charsOn[] = { 'A','B','C','D','E','F','G','H' };

                try {
                    if(state) {
                        outputStream.write(charsOn[relais]);
                    } else {
                        outputStream.write(charsOff[relais]);
                    }

                    System.out.println("Relaisboard: sendData " + relais + " = " + state);

                    outputStream.flush();
                } catch (IOException e) {
                    Logger.sLog("Relaisboard error: Fehler beim Senden");
                }
            }
        }
    }


    @Override
    public void open() {
        serialPortGeoeffnet = false;
        Boolean foundPort = false;

        if (serialPortGeoeffnet) {
            Logger.sLog("Serialport bereits geöffnet");
            return;
        }

        Enumeration enumComm = CommPortIdentifier.getPortIdentifiers();
        CommPortIdentifier serialPortId = null;

        while(enumComm.hasMoreElements()) {
            serialPortId = (CommPortIdentifier) enumComm.nextElement();
            if (portName.contentEquals(serialPortId.getName())) {
                foundPort = true;
                break;
            }
        }
        if (!foundPort) {
            Logger.sLog("Serialport nicht gefunden: " + portName);
            return;
        }

        try {
            // open port and tell the OS who we are. Wait max 500ms for release if port is currently owned.
            serialPort = (SerialPort) serialPortId.open("crashtest app", 500);
            serialPort.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
            serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);

            outputStream = serialPort.getOutputStream();
            serialPortGeoeffnet = true;
        } catch (PortInUseException e) {
            Logger.sLog("Relaisboard error: Port belegt " + portName);
        } catch (IOException e) {
            Logger.sLog("Relaisboard error: Keinen Zugriff auf OutputStream");
        } catch(UnsupportedCommOperationException e) {
            Logger.sLog("Relaisboard error: Konnte Schnittstellen-Paramter nicht setzen");
        }
    }

    @Override
    public void close() {
        if ( serialPortGeoeffnet ) {
            serialPort.close();
        }
        serialPortGeoeffnet = false;
        outputStream = null;
    }

    @Override
    public void setRelais(final int relais, final boolean state) {
        if(!serialPortGeoeffnet) return;

        stopRelaisThread(relais);

        Runnable r = new Runnable() {
            @Override
            public void run() {
                sendData(relais, state);
            }
        };

        new Thread(r).start();
    }

    @Override
    public void toggleRelais(final int relais, final int milliseconds) {
        if(!serialPortGeoeffnet) return;

        stopRelaisThread(relais);

        Runnable r = new Runnable() {
            @Override
            public void run() {
                sendData(relais, true);

                try {
                    Thread.sleep(milliseconds);
                } catch (InterruptedException ignored) { }

                sendData(relais, false);
            }
        };

        new Thread(r).start();
    }

    @Override
    public void blinkRelais(final int relais, final int pause) {
        if(!serialPortGeoeffnet) return;
        
        stopRelaisThread(relais);

        Runnable r = new Runnable() {
            @Override
            public void run() {
                while(true) {
                    sendData(relais, true);

                    try {
                        Thread.sleep(pause);
                    } catch (InterruptedException ignored) { }

                    if(stopMap.get(relais)) return;

                    sendData(relais, false);

                    try {
                        Thread.sleep(pause);
                    } catch (InterruptedException ignored) { }

                    if(stopMap.get(relais)) return;
                }
            }
        };

        stopMap.put(relais, false);
        Thread thread = new Thread(r);
        threadMap.put(relais,thread);
        thread.start();
    }

    @Override
    public void blinkRelaisStop(final int relais) {
        stopRelaisThread(relais);
    }

    private void stopRelaisThread(final int relais) {
        Thread thread = threadMap.get(relais);

        if(thread != null) {
            stopMap.put(relais, true);
            thread.interrupt();
            threadMap.remove(relais);
        }
    }
}
