lib mers_libs/http_requests
lib mers_libs/gui

// MusicDB is a private project of mine.
// Basically, a SBC is hooked up to some speakers.
// It has a database of my songs and can play them.
// It can be controlled through HTTP requests, such as
//   p- to stop
//   p+ to play
//   qi to get the queue index / playback position
//   l to list all the songs
//   ql or qL to list the queue
//   ...
// this mers program should work as a very simple gui to remotely control
// the playback of songs. It should be able to add/remove songs to/from the queue,
// to start/stop playback, and to display some information about the current song.

// the url used to connect to the SBC and use the HTTP api
api_url = "http:\//192.168.2.103:26315/api/raw?"

// start the GUI
base = gui_init()
set_title("Mers MusicDB Remote")

playback_controls = base.gui_add(Row: [])
play_button = playback_controls.gui_add(Button: "Play")
stop_button = playback_controls.gui_add(Button: "Stop")

// these functions abstract away the api interactions
// because they aren't pretty.
// feel free to skip past this section and look at the gui code instead
fn make_api_request(to_api string) {
    http_get(api_url.add(to_api))
}
fn get_queue_index() {
    r = make_api_request("qi")
    switch! r {
        string {
            r.regex("[0-9]").assume_no_enum().get(0).assume1().parse_int().assume1()
        }
        // return the error
        Err(ErrBuildingRequest(string)/ErrGettingResponseText(string)) r
    }
}
fn get_db_contents() {
    // list only 7 = 1 (title) + 2 (artist) + 4 (album)
    r = make_api_request("lo7,")
    switch! r {
        string {
            entries = r.regex("#.*:\n.*\n.*\n.*").assume_no_enum()
            // initialize the list with a default value
            // because mers doesnt know the inner type without it.
            db = [[0 "title" "artist" "album"] ...]
            &db.remove(0)
            for entry entries {
                lines = entry.regex(".*")
                switch! lines {
                    [string ...] {
                        index_line = &lines.get(0).assume1()
                        index = index_line.substring(1 index_line.len().sub(1)).parse_int().assume1()
                        title = &lines.get(1).assume1().substring(1)
                        artist = &lines.get(2).assume1().substring(1)
                        album = &lines.get(3).assume1().substring(1)
                        &db.push([index title artist album])
                    }
                    Err(string) {
                        println("invalid response from server; 3u123128731289")
                        exit(1)
                    }
                }
            }
            db
        }
        Err(ErrBuildingRequest(string)/ErrGettingResponseText(string)) {
            println("couldn't request db from server:")
            print("  ")
            println(r.to_string())
            exit(1)
        }
    }
}
fn get_queue() {
    r = make_api_request("ql")
    switch! r {
        string {
            queue = [0 ...]
            &queue.remove(0)
            for index r.regex("[0-9]+[ \n]").assume_no_enum() {
                &queue.push(index.substring(0 index.len().sub(1)).parse_int().assume1())
            }
            queue
        }
        Err(ErrBuildingRequest(string)/ErrGettingResponseText(string)) {
            println("couldn't request queue from server:")
            print("  ")
            println(r.to_string())
            exit(1)
        }
    }
}
fn play() {
    r = make_api_request("p+")
    switch! r {
        // success! but nothing to return.
        string []
        // return the error
        Err(ErrBuildingRequest(string)/ErrGettingResponseText(string)) r
    }
}
fn stop() {
    r = make_api_request("p-")
    switch! r {
        // success! but nothing to return.
        string []
        // return the error
        Err(ErrBuildingRequest(string)/ErrGettingResponseText(string)) r
    }
}
fn queue_song(song_id int) {
    make_api_request("q+{0}".format(song_id.to_string()))
}

// fetch the database contents (a list of songs)
database = get_db_contents()

// this is where all the songs will be displayed.
songs_gui = base.gui_add(Row: [])
queue_list = songs_gui.gui_add(Column: [])
library = songs_gui.gui_add(Column: [])

limit = 0 // set to 0 to disable library length limit
for entry database {
    // <SONG> by <ARTIST> on <ALBUM>
    song = library.gui_add(Row: [])
    song.gui_add(Button: entry.1)
    song.gui_add(Text: "by {0} on {1}".format(entry.2 entry.3))
    limit = limit.sub(1)
    if limit.eq(0) [[]] else []
}

// regularly update the queue
thread(() {
    queue_index = get_queue_index().assume_no_enum("if the server isn't reachable, it's ok to crash")
    queue_list_inner = queue_list.gui_add(Column: [])
    prev_artist = ""
    prev_album = ""
    for song_id get_queue() {
        this_is_playing = if queue_index.eq(0) { queue_index = -1 true } else if queue_index.gt(0) { queue_index = queue_index.sub(1) false } else { false }
        song = &database.get(song_id).assume1()
        row = queue_list_inner.gui_add(Row: [])
        text = if this_is_playing ">> " else ""
        text = text.add(song.1)
        if prev_artist.eq(song.2) {
            if prev_album.eq(song.3) {
            } else {
                text = text.add("(on {0})".format(song.3))
            }
        } else {
            text = text.add("(by {0} on {1})".format(song.2 song.3))
        }
        row.gui_add(Text: text)
        []
    }
    sleep(10)
    queue_list_inner.gui_remove()
})

loop {
    for event gui_updates() {
        switch! event {
            ButtonPressed([int ...]) {
                e = event.noenum()
                println("Pressed button {0}".format(e.to_string()))
                match e {
                    &e.eq(&play_button) {
                        println("pressed play.")
                        play()
                    }
                    &e.eq(&stop_button) {
                        println("pressed stop.")
                        stop()
                    }
                    {
                        // the button is in a row that is in the library,
                        // so pop()ing twice gets the library path.
                        // THIS WILL BREAK IF THE GUI LIBRARY SWITCHES TO IDs INSTEAD OF PATHS!
                        last = &e.pop().assume1()
                        slast = &e.pop().assume1()
                        matches = &e.eq(&library)
                        &e.push(slast)
                        &e.push(last)
                        if matches e else []
                    } {
                        // the second to last part of the path is the row within library, which is also the index in the db
                        song = &database.get(&e.get(e.len().sub(2)).assume1()).assume1()
                        println("Added song \"{0}\" to queue.".format(song.1))
                        queue_song(song.0)
                    }
                    true println("A different button was pressed (unreachable)")
                }
            }
        }
    }
    []
}