/*
  StreamGoose main server/UI launcher — Go reference implementation
 
  Created by Luxferre in 2024, released into public domain
*/
package main

import (
  "os"
  "fmt"
  "log"
  "strings"
  "encoding/json"
  "net/http"
  "io/ioutil"
  /* external */
  "centrifuge.hectabit.org/HectaBit/webview_go" /* for UI launch */
)

/* main chat message structure */
type ChatMessage struct {
  Id uint64        `json:"id"`        /* message id (auto-filled) */
  Type string      `json:"type"`      /* message type string */
  Timestamp string `json:"timestamp"` /* timestamp string (formatted) */
  Username string  `json:"username"`  /* username */
  Text string      `json:"text"`      /* message text */
  Icon string      `json:"icon"`      /* optional message icon URI */
}

var global_msg_id uint64 = 0 /* track global message ID */
var message_transport chan ChatMessage  /* global message channel */
var bro webview.WebView /* global browser instance handle */

/* escape HTML entities in JS parameters */
func esc(s string) string {
  s = strings.ReplaceAll(s, "&", "&amp;")
  s = strings.ReplaceAll(s, "'", "&apos;")
  s = strings.ReplaceAll(s, "\"", "&quot;")
  s = strings.ReplaceAll(s, "<", "&lt;")
  s = strings.ReplaceAll(s, ">", "&gt;")
  return s
}

/* chat message appender thread */
func appendChatMessages() {
  var msg ChatMessage
  for {
    msg = <-message_transport /* receive it from the channel */
    /* populate the running id */
    global_msg_id += 1
    msg.Id = global_msg_id

    /* directly call the append function on the JS side */
    fmt_str := "gooseAppendMessage('%d', '%s', '%s', '%s', '%s', '%s');"
    js := fmt.Sprintf(fmt_str, msg.Id, esc(msg.Type), esc(msg.Timestamp), esc(msg.Username), esc(msg.Text), esc(msg.Icon))
    log.Printf("Evaluating JS: %s", js)
    bro.Eval(js)

  }
}

/* POST /new handler (for external backends) */
func msgAddHandler(w http.ResponseWriter, r *http.Request) {
  decoder := json.NewDecoder(r.Body)
  var msg ChatMessage         /* create a new message */
  err := decoder.Decode(&msg) /* fill in the message structure */
  if err != nil {
    log.Println("Error decoding POST request:", err)
    return
  }
  /* post it to the transport channel */
  message_transport <- msg
  w.Write([]byte("{\"status\": 1}")) /* respond with a status */
}

/* entry point */
func main() {
  config_path := "config.json"
  if len(os.Args) > 1 {
    config_path = os.Args[1]
  }
  content, err := ioutil.ReadFile(config_path)
  if err != nil {
    log.Fatal("Error when opening config file: ", err)
  }
  var config map[string]interface{}
  err = json.Unmarshal(content, &config)
  if err != nil {
    log.Fatal("Error during Unmarshal(): ", err)
  }

  /* create the message transport channel */
  message_transport = make(chan ChatMessage)

  /* target server listening address */
  target_addr := fmt.Sprintf("%s:%d", config["server_listen_ip"].(string), int(config["server_port"].(float64)))

  go appendChatMessages() /* start the appender thread */

  go func() { /* start the server thread */
    /* serve the POST /new */
    http.HandleFunc("/new", msgAddHandler)
    /* serve the webroot directory */
    fs := http.FileServer(http.Dir("./webroot"))
    http.Handle("/", fs)
    log.Println(fmt.Sprintf("Listening on %s\n", target_addr))
    err = http.ListenAndServe(target_addr, nil)
    if err != nil {
      log.Fatal(err)
    }
  }()

  
  if _, ok := config["tw_backend_enabled"]; ok && config["tw_backend_enabled"].(bool) == true {
    go twitch_run(config)  /* start the Twitch backend */
  }

  if _, ok := config["yt_backend_enabled"]; ok && config["yt_backend_enabled"].(bool) == true {
    go youtube_run(config) /* start the YouTube backend */
  }

  /* start the GUI */
  debug := false
  if _, ok := config["debug"]; ok && config["debug"].(bool) == true {
    debug = true
  }
  bro = webview.New(debug) /* create a browser instance */
  bro.SetSize(320, 720, webview.HintNone)
  defer bro.Destroy()
  bro.SetTitle("StreamGoose v0.0.1")
  bro.Navigate("http://" + target_addr)
  bro.Run()

}
