import processing.serial.*;
import processing.net.*; 
import java.io.*;
import java.net.*;

/*
 * Das Programm
 *    testet alle seriellen Ports, lauscht
 *    identifiziert das Board, extrahiert die Daten und
 *    schickt diese an Snap (bzw. wartet, dass Snap sich diese holt)
 *
 * Achtung, das Board sendet immer! Eine Anfrage mit Rueckantwort dauert
 * beim MBit ca. 600ms. Das ist zu lang!
 *
 * Sie möchten das Programm erweitern, um eigene Boards nutzen zu können?
 * Bitte suchen Sie nach ERWEITERN im Quelltext. Beachten Sie auch, dass
 * die Formware Ihres Boards angepasst werden muss. Dies alles ist aber recht
 * einfach möglich.
 *
 * Sie können das Programm nach Belieben ändern, dokumentieren Sie bitte Ihre
 * Änderungen und entfernen Sie keine Copyright-Informationen
 *
 * Viel Spaß wünscht das Lehrerbildungszentrum Göttingen
 * Andreas Flemming, 2017, Lizenz: CC BY NC SA
 */
 
boolean debug = false;
String fehlerText1 = "Kein Gerät gefunden.";
String fehlerText2 = "Bitte schließe ein Board mit einem USB-Kabel an.";
 
enum STATUS {NICHT_VERBUNDEN, VERBUNDEN, NETZWERK_FEHLER};
STATUS status = STATUS.NICHT_VERBUNDEN;

// ERWEITERN: Bitte fügen Sie den Namen und das Kürzel, welches
// das Board auf Anfrage sendet, am Ende der Arrays ein
String[] alleGeraeteNamen = {"Micro:Bit", "Esplora", "Octopus", "Calliope"};
String[] alleGeraeteKuerzel = {"mb", "es", "oc", "ca"};
String aktuellesGeraet = null;

// ERWEITERN: Tragen Sie hier eine weitere Folge von Daten ein, die Ihr Board sendet.
// Das sollte die gleiche Reihenfolge sein wie sie vom Board kommt. Da an Snap
// immer alles gesendet wird, ist dies nur für die Darstellung wichtig
String[][] alleWerte = {
    {"Beschleunigung X", "Beschleunigung Y", "Beschleunigung Z", "Button A", "Button B"},
    {"Beschleunigung X", "Beschleunigung Y", "Beschleunigung Z", "Button Links", "Button Rechts",
     "Button Hoch", "Button Runter", "Joystick X", "Joystick Y", "Helligkeit", "Lautstärke",
     "Temperatur", "Schieberegler"},
    {"Temperatur", "Luftdruck", "Luftfeuchtigkeit"},
    {"Beschleunigung X", "Beschleunigung Y", "Beschleunigung Z", "Button A", "Button B",
     "Helligkeit", "Temperatur"} };

Serial serialPort = null;
String datenZeile = "---";
int port = 2235;
ServerSocket serverSocket = null;
InputStream inputStream = null;
BufferedReader reader = null;
OutputStream outputStream = null;
PrintWriter writer = null;
Socket socket;

void setup()
{
    size(600, 300);
    try
    {
        serverSocket = new ServerSocket(port);
    }
    catch (Exception e) 
    { 
        debugMsg(1, e.getMessage());
        serverSocket = null;
        status = STATUS.NETZWERK_FEHLER;
    }
    if (null != serverSocket) 
    {
         thread("antworteSnap");
    }
}

void draw()
{
    surface.setTitle("Sensordaten an Snap");
    
    // Die Hintergrundfarbe zeigt den Fortschritt an
    color hintergrundFarbe = 0, feldFarbe = 0;
    switch (status)
    {
        case NICHT_VERBUNDEN: hintergrundFarbe = color(150, 10, 50); feldFarbe = color(180, 30, 50); break;
        case VERBUNDEN: hintergrundFarbe = color(40, 160, 40); feldFarbe = color(50, 200, 50); break;
        case NETZWERK_FEHLER: hintergrundFarbe = color(140, 160, 40); feldFarbe = color(150, 200, 50); break;
    }
    background(hintergrundFarbe);
    fill(255);

    switch (status)
    {
        case NETZWERK_FEHLER:
            textSize(16);
            stroke(0); fill(feldFarbe);
            rect(20, 20, 560, 60, 7);
            fill(255);
            text("Netzwerkfehler, kann ServerSocket nicht öffnen", 30, 43); 
            text("Warten hilft nicht.", 30, 63); 
            stroke(0); fill(feldFarbe);
            rect(20, 100, 560, 180, 7);
            break;
            
        case NICHT_VERBUNDEN:
            textSize(16);
            stroke(0); fill(feldFarbe);
            rect(20, 20, 560, 60, 7);
            fill(255);
            text(fehlerText1, 30, 43); 
            text(fehlerText2, 30, 63); 
            stroke(0); fill(feldFarbe);
            rect(20, 100, 560, 180, 7);
            sucheSeriellesGeraet();
            break;
            
        case VERBUNDEN:
            textSize(16);
            stroke(0); fill(feldFarbe);
            rect(20, 20, 560, 60, 7);
            fill(255);
            text(gibBoardName() + " gefunden!", 30, 43); 
            text("In Snap erfrage localhost:" + port, 30, 63);
            stroke(0); fill(feldFarbe);
            rect(20, 100, 560, 180, 7);
            erfrageDaten();
            break;
    }
}

/*
 * Da println unter Processing nur mit Vorsicht angewendet werden soll (println in jedem
 * Frame ist zu viel. Deshalb kann man bei Debugausgaben den Teiler angeben.
 */
void debugMsg(int t, String msg)
{
    if (debug)
    {
        if (frameCount % t == 0) println(msg);
    }
}

/* 
 * Diese Routine such das serielle Gerät. Dabei werden alle gelisteten Port geöffnet
 * und es wird eine Bitte um Authentifizierung gesendet.
 * TODO: Es fehelen die Pattern für serielle Schnittstellen unter MacOS.
 */
void sucheSeriellesGeraet()
{
    int pNummer = 0;
    String[] allePorts = Serial.list();

    while (pNummer < allePorts.length)
    {
        serialPort = null;
        /*
         * Nur Ports testen, die COM, ACM, USB im Namen haben
         * Wie ist es bei Apple?
         */
         
        if ( (allePorts[pNummer].indexOf("COM") == -1) &&
             (allePorts[pNummer].indexOf("ACM") == -1) &&
             (allePorts[pNummer].indexOf("USB") == -1))
        {
            pNummer++;
            continue;
        }
             
        try
        {
            debugMsg(1, "testen: " + allePorts[pNummer]);
            serialPort = new Serial(this, allePorts[pNummer], 115200);
        }
        catch (Exception e)
        {
            // Ging schief, also weiter
            debugMsg(1, "Keine Hardware an " + allePorts[pNummer]);
            serialPort = null;
        }
        delay(50);
        if (null != serialPort)
        {
            // Ein Port lässt sich öffnen. lauschen und Board identifizieren
            //sPort.clear(); 
            debugMsg(1, "Habe Port " + allePorts[pNummer]);
            delay(50);
            
            // maximal 10 Sekunden nach Zeichen suchen, der MBit braucht ein wenig Zeit...
            int startLauschen = millis();
            boolean anfangGefunden = false;
            boolean endeGefunden = false;
            String zeile = "";
            while ((millis() - startLauschen < 10000) && !endeGefunden)
            {
                if (serialPort.available() > 0)
                {
                    char zeichen = serialPort.readChar();
                    if ((false == anfangGefunden) && (zeichen == '('))
                    {
                        zeile = "";
                        anfangGefunden = true;
                    }
                    else if (anfangGefunden)
                    {
                        if (zeichen == ')')
                            endeGefunden = true;
                        else
                            zeile += zeichen;
                    }
                } else { delay(10); /* ttyUSB0 macht sonst Probleme */  }
            }
                
            String kennung = zeile.split(",")[0];            
            switch (kennung)
            {
                case "mb": aktuellesGeraet = "mb"; break;
                case "oc": aktuellesGeraet = "oc"; break;
                case "es": aktuellesGeraet = "es"; break;
                case "ca": aktuellesGeraet = "ca"; break;
                // ERWEITERN: Bitte hier eine Zeile mit der eigenen Kennung eintragen
                default: aktuellesGeraet = null; 
                         fehlerText1 = "Unbekanntes Gerät mit Kennung " + kennung;
                         fehlerText2 = "Nutzen Sie das aktuelle Programm?";
                         break;
            }
        }
        else
        {
            pNummer++; // Weiter suchen
            
        }
        
        if (aktuellesGeraet != null) break;
    }
    
    if (aktuellesGeraet != null) 
    {
        debugMsg(1, "Mit seriellem Port verbunden, Hardware: " + aktuellesGeraet);
        status = STATUS.VERBUNDEN;
        fehlerText1 = null;
        fehlerText2 = null;
    }
}

String gibBoardName()
{
    int i = gibBoardIndex();
    if (i > -1)
        return alleGeraeteNamen[i];
    else
        return "Unbekannt";
}

int gibBoardIndex()
{
    if (null == aktuellesGeraet)
    {
        return -1;
    }
    else
    {
        int i = 0;
        for (String n : alleGeraeteKuerzel)
        {
            if (n.equals(aktuellesGeraet)) return i;
            i++;
        }
        return -1;
    }
}

void erfrageDaten()
{
    int startLauschen = millis();
    boolean anfangGefunden = false;
    boolean endeGefunden = false;
    String zeile = "";
    while ((millis() - startLauschen < 1000) && !endeGefunden)
    {
        if (serialPort.available() > 0)
        {
            char zeichen = serialPort.readChar();
            if (false == anfangGefunden)
            {
                if (zeichen == '(')
                {
                    zeile = "";
                    anfangGefunden = true;
                }
            }
            else if (anfangGefunden)
            {
                if (zeichen == ')')
                {
                    endeGefunden = true;
                    serialPort.clear();
                }
                else
                    zeile += zeichen;
            }
        } else { delay(10); /* ttyUSB0 macht sonst Probleme */  }
    }
    
    // Daten gefunden?
    if (zeile.length() > 3 && endeGefunden)
    {
        datenZeile = zeile.substring(3, zeile.length());
        datenDarstellen(datenZeile);
    }
    else
    {
        // Zu lange gewartet. Verbindung verloren!
        status = STATUS.NICHT_VERBUNDEN;
        aktuellesGeraet = null;
        serialPort.clear();
        serialPort = null;
        fehlerText1 = "Gerät verloren.";
        fehlerText2 = "Bitte Kabel prüfen.";
        delay(100);
    }
}

void datenDarstellen(String z)
{
    int b = gibBoardIndex();
    if (-1 == b)
    {
        // geraet abgezogen
        serialPort = null;
        return;
    }
    
    String[] aw = z.split(","); 
    textSize(14);
    fill(255);
    int i = 0;
    for (String w : aw)
    {
        text(alleWerte[b][i] + ": " + w, 30 + (i % 2) * 270, 122 + (i/2) * 20);
        i++;
    }
}

/*
 * Der Server-Thread laeuft parallel. Die Art der Anfrage wird
 * einfach ignoriert, wir schicken alle Werte als plain Text.
 */
void antworteSnap()
{
    while(true)
    {
        try
        {
            println("Warte auf Empfang");
            socket = serverSocket.accept();
            println("Empfang"); //<>//
            
            // InputStream oeffnen
            inputStream = socket.getInputStream();
            reader = new BufferedReader(new InputStreamReader(inputStream));
            
            // Ausgabe oeffnen
            outputStream = socket.getOutputStream();
            // writer = new PrintWriter(new OutputStreamWriter(outputStream));
            writer = new PrintWriter(outputStream);
            
            String str = ".";
            /* alte Variante */
            while (!str.equals(""))
            {
              str = reader.readLine();
              println(str);
            }
            
            writer.println("HTTP/1.0 200 OK"); 
            writer.println("Content-Type: text/plain"); 
            writer.println("Server: SnapServer");
            writer.println("Content-Length: " + datenZeile.length()); 
            writer.println("Access-Control-Allow-Origin: *");
            writer.println("Connection: close"); // Will close stream
            writer.println(); // End of headers
            
            writer.print(datenZeile);
            writer.flush();
            
            socket.close();
        }
        catch (Exception e) 
        {
            println("Error:"); 
            //println(e.getMessage()); 
        }
    }
}