// =============================================================================
// Auth: Alex Celani
// File: led.go
// Revn: 06-26-2022  1.2
// Func: receive data as an endpoint, send response back to middleman
//
// TODO:
// =============================================================================
// CHANGE LOG
// -----------------------------------------------------------------------------
// 05-04-2022: init
// 05-09-2022: began commenting
// 05-10-2022: finished commenting
//             added alter() and forward()
//             made passed value to alter()/forward() a slice
//             changed refs to recv byte array to slice of n bytes
// 06-20-2022: changed name to reflect first draft of node.go
//*06-22-2022: bug squashing and commenting
// 06-23-2022: updated to produce three different colors
// 06-26-2022: commented
//
// =============================================================================

package main

import (
    "net"       // ResolveTCPAddr, conn.Write,Read,Close
                // ListenTCP, listener.Accept
    "os"        // Args, Stderr, Exit
    "fmt"       // Fprintf, Println
    "strings"   // ToLower
    "github.com/stianeikeland/go-rpio"      // talk to RasPi pins
)


// quick error checking
func check( err error ) {
    // if user checks an error, it better be nil
    if err != nil {
        // put error in string, print in stderr
        fmt.Fprintf( os.Stderr, "Fatal error: %s", err.Error() )
        os.Exit( 2 )        // quit
    }
}


// parse incoming message, keep only second word
func parse( recv string ) string {
    // split message over spaces
    keywords := strings.Split( recv, " " )
    return keywords[1]      // return the second word
}


// function to handle incoming connections
func handleClient( conn net.Conn ) {
    defer conn.Close()  // barring any error, still close connection

    var buf [512]byte   // declare large byte array, store messages

    // iterate forever to always read over connection
    for {
        // read n bytes from connection into buffer
        n, err := conn.Read( buf[0:] )
        if err != nil { // erroring on read will simply leave the 
            return      // function so it can start again later
        }

        // print recv'd message
        // string() only works on byte SLICES so [:] is required
        fmt.Println( "recv: ", string( buf[:n] ) )

        // split command into words for ease of parsing
        command := strings.Split( string( buf[:n] ), " " )

        if command[1] != "led" {    // confirm message belongs here
            os.Exit( 2 )            // if not, exit
        }

        var resp string             // declare response variable


        // TODO convert to switches?
        // len is 3 for "asK" commands
        if len( command ) == 3 {
            if command[2] == "color" {
                // if user wants color, return color
                resp = color
            } else if command[2] == "status" {
                // if user wants status, return status
                resp = status
            } else {
                // there are only those two fields
                resp = "usage unknown"
            }
        // len is 4 for tell commands
        } else if len( command ) == 4 {
            // if user wants to set color
            if command[2] == "color" {
                color = command[3]      // set current color
                resp = color            // return current color
                switch color {
                    case "blue":        // does user want blue?
                        // turn blue on, turn rest off
                        pinB.High()
                        pinR.Low()
                        pinG.Low()
                    case "red":         // does user want red?
                        // turn red on, turn rest off
                        pinB.Low()
                        pinR.High()
                        pinG.Low()
                    case "green":       // does user want green?
                        // turn green on, turn rest off
                        pinB.Low()
                        pinR.Low()
                        pinG.High()
                    default:            // color unknown
                        // turn off
                        pinB.Low()
                        pinR.Low()
                        pinG.Low()
                }
            // if user wants to set status
            } else if command[2] == "status" {
                status = command[3]     // set current status
                resp = status           // return current status
                switch status {
                    case "on":          // does user want on?
                        // default to red
                        pinB.Low()
                        pinR.High()
                        pinG.Low()
                    default:            // anything else, like off
                        // default to off
                        pinB.Low()
                        pinR.Low()
                        pinG.Low()
                }
            // user entered something else entirely
            } else {
                resp = "usage unknown"
            }
        // no other lengths are valid
        } else {
            os.Exit( 2 )        // exit in this case
        }

        fmt.Println( "answer: ", resp )     // print response

        // write that response back to original client
        _, err = conn.Write( []byte( resp ) )
        if err != nil { // erroring on write will simply leave the
            return      // function so it can start again later
        }

        fmt.Println( "sent: ", resp )   // print response
    }
}


//var params = make( map[string]string )
//var states = make( map[string]string )
var pinR rpio.Pin           // declare red led pin
var pinG rpio.Pin           // declare green led pin
var pinB rpio.Pin           // declare blue led pin
var color string            // declare color variable
var status string           // declare status variable


func main() {

    // initialize pins to GPIO 11, 9, and 25
    pinR = rpio.Pin( 11 )
    pinG = rpio.Pin( 9 )
    pinB = rpio.Pin( 25 )

	if err := rpio.Open(); err != nil {
        // on error, print error and exit
		fmt.Println(err)
		os.Exit(1)
	}

	// Unmap gpio memory when done
	defer rpio.Close()

	// Set pins to output mode
	pinR.Output()
	pinG.Output()
	pinB.Output()

    // ip:port
    // ip doesn't exist, implies localhost
    service := ":1202"      // capture ip address and host

    // "resolve" ip & host according to TCP rules
    tcpAddr, err := net.ResolveTCPAddr( "tcp", service )
    check( err )    // check error

    // bind and "listen" to ip and port, according to tcp rules
    listener, err := net.ListenTCP( "tcp", tcpAddr )
    check( err )    // check error

    // iterate forever
    // TODO i mean i can totally make this more user friendly
    for {
        // accept a connection that makes its way to bound port
        conn, err := listener.Accept()
        if err != nil {     // if connection fails...
            continue        // don't quit program, not fatal error
        }

        // asynchronous function to handle connection to client
        go handleClient( conn )
    }
}

