// This example is an MQTT client built with the natiu-mqtt package.  It sends
// machine.CPUFrequency() readings to the broker every second for 10 seconds.
//
// Note: It may be necessary to increase the stack size when using
// paho.mqtt.golang.  Use the -stack-size=4KB command line option.

//go:build ninafw || wioterminal || challenger_rp2040 || comboat_fw

package main

import (
	"context"
	"errors"
	"fmt"
	"io"
	"log"
	"machine"
	"math/rand"
	"net"
	"time"

	mqtt "github.com/soypat/natiu-mqtt"
	"tinygo.org/x/drivers/netlink"
	"tinygo.org/x/drivers/netlink/probe"
)

var (
	ssid   string
	pass   string
	broker string = "test.mosquitto.org:1883"
	topic  string = "cpu/freq"
)

func main() {
	waitSerial()

	link, _ := probe.Probe()

	err := link.NetConnect(&netlink.ConnectParams{
		Ssid:       ssid,
		Passphrase: pass,
	})
	if err != nil {
		log.Fatal(err)
	}

	clientId := "tinygo-client-" + randomString(10)
	fmt.Printf("ClientId: %s\n", clientId)

	// Get a transport for MQTT packets
	fmt.Printf("Connecting to MQTT broker at %s\n", broker)
	conn, err := net.Dial("tcp", broker)
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	// Create new client
	client := mqtt.NewClient(mqtt.ClientConfig{
		Decoder: mqtt.DecoderNoAlloc{make([]byte, 1500)},
		OnPub: func(_ mqtt.Header, _ mqtt.VariablesPublish, r io.Reader) error {
			message, _ := io.ReadAll(r)
			fmt.Printf("Message %s received on topic %s\n", string(message), topic)
			return nil
		},
	})

	// Connect client
	var varconn mqtt.VariablesConnect
	varconn.SetDefaultMQTT([]byte(clientId))
	ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
	err = client.Connect(ctx, conn, &varconn)
	if err != nil {
		log.Fatal("failed to connect: ", err)
	}

	// Subscribe to topic
	ctx, _ = context.WithTimeout(context.Background(), 10*time.Second)
	err = client.Subscribe(ctx, mqtt.VariablesSubscribe{
		PacketIdentifier: 23,
		TopicFilters: []mqtt.SubscribeRequest{
			{TopicFilter: []byte(topic), QoS: mqtt.QoS0},
		},
	})
	if err != nil {
		log.Fatal("failed to subscribe to", topic, err)
	}
	fmt.Printf("Subscribed to topic %s\n", topic)

	// Publish on topic
	pubFlags, _ := mqtt.NewPublishFlags(mqtt.QoS0, false, false)
	pubVar := mqtt.VariablesPublish{
		TopicName: []byte(topic),
	}

	for i := 0; i < 10; i++ {
		if !client.IsConnected() {
			log.Fatal("client disconnected: ", client.Err())
		}

		freq := float32(machine.CPUFrequency()) / 1000000
		payload := fmt.Sprintf("%.02fMhz", freq)

		pubVar.PacketIdentifier++
		err = client.PublishPayload(pubFlags, pubVar, []byte(payload))
		if err != nil {
			log.Fatal("error transmitting message: ", err)
		}

		time.Sleep(time.Second)

		conn.SetReadDeadline(time.Now().Add(10 * time.Second))
		err = client.HandleNext()
		if err != nil {
			log.Fatal("handle next: ", err)
		}

	}

	client.Disconnect(errors.New("disconnected gracefully"))

	for {
		select {}
	}
}

// Returns an int >= min, < max
func randomInt(min, max int) int {
	return min + rand.Intn(max-min)
}

// Generate a random string of A-Z chars with len = l
func randomString(len int) string {
	bytes := make([]byte, len)
	for i := 0; i < len; i++ {
		bytes[i] = byte(randomInt(65, 90))
	}
	return string(bytes)
}

// Wait for user to open serial console
func waitSerial() {
	for !machine.Serial.DTR() {
		time.Sleep(100 * time.Millisecond)
	}
}
