package main

import (
	"fmt"
	"log"
	"os"
	"os/signal"
	"syscall"

	"github.com/logrusorgru/aurora"

	mqtt "github.com/mochi-co/mqtt/server"
	"github.com/mochi-co/mqtt/server/listeners"
)

func main() {
	sigs := make(chan os.Signal, 1)
	done := make(chan bool, 1)
	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
	go func() {
		<-sigs
		done <- true
	}()

	fmt.Println(aurora.Magenta("Mochi MQTT Server initializing..."), aurora.Cyan("TCP"))

	server := mqtt.NewServer(nil)
	tcp := listeners.NewTCP("t1", ":1883")
	err := server.AddListener(tcp, &listeners.Config{
		Auth: &Auth{
			Users: map[string]string{
				"peach": "password1",
				"melon": "password2",
				"apple": "password3",
			},
			AllowedTopics: map[string][]string{
				// Melon user only has access to melon topics.
				// If you were implementing this in the real world, you might ensure
				// that any topic prefixed with "melon" is allowed (see ACL func below).
				"melon": {"melon/info", "melon/events"},
			},
		},
	})
	if err != nil {
		log.Fatal(err)
	}

	go func() {
		err := server.Serve()
		if err != nil {
			log.Fatal(err)
		}
	}()
	fmt.Println(aurora.BgMagenta("  Started!  "))

	<-done
	fmt.Println(aurora.BgRed("  Caught Signal  "))

	server.Close()
	fmt.Println(aurora.BgGreen("  Finished  "))
}

// Auth is an example auth provider for the server. In the real world
// you are more likely to replace these fields with database/cache lookups
// to check against an auth list. As the Auth Controller is an interface, it can
// be built however you want, as long as it fulfils the interface signature.
type Auth struct {
	Users         map[string]string   // A map of usernames (key) with passwords (value).
	AllowedTopics map[string][]string // A map of usernames and topics
}

// Authenticate returns true if a username and password are acceptable.
func (a *Auth) Authenticate(user, password []byte) bool {
	// If the user exists in the auth users map, and the password is correct,
	// then they can connect to the server. In the real world, this could be a database
	// or cached users lookup.
	if pass, ok := a.Users[string(user)]; ok && pass == string(password) {
		return true
	}

	return false
}

// ACL returns true if a user has access permissions to read or write on a topic.
func (a *Auth) ACL(user []byte, topic string, write bool) bool {

	// An example ACL - if the user has an entry in the auth allow list, then they are
	// subject to ACL restrictions. Only let them use a topic if it's available for their
	// user.
	if topics, ok := a.AllowedTopics[string(user)]; ok {
		for _, t := range topics {

			// In the real world you might allow all topics prefixed with a user's username,
			// or similar multi-topic filters.
			if t == topic {
				return true
			}
		}

		return false
	}

	// Otherwise, allow all topics.
	return true
}
