// Package config loads application settings
package config

import (
	"bytes"
	"gitlab.crans.org/nounous/ghostream/stream/telnet"
	"log"
	"net"
	"strings"

	"github.com/spf13/viper"
	"gitlab.crans.org/nounous/ghostream/auth"
	"gitlab.crans.org/nounous/ghostream/auth/basic"
	"gitlab.crans.org/nounous/ghostream/auth/ldap"
	"gitlab.crans.org/nounous/ghostream/internal/monitoring"
	"gitlab.crans.org/nounous/ghostream/stream/forwarding"
	"gitlab.crans.org/nounous/ghostream/stream/srt"
	"gitlab.crans.org/nounous/ghostream/stream/webrtc"
	"gitlab.crans.org/nounous/ghostream/web"
	"gopkg.in/yaml.v2"
)

// Config holds application configuration
type Config struct {
	Auth       auth.Options
	Forwarding forwarding.Options
	Monitoring monitoring.Options
	Srt        srt.Options
	Telnet     telnet.Options
	Web        web.Options
	WebRTC     webrtc.Options
}

// New configuration with default values
func New() *Config {
	return &Config{
		Auth: auth.Options{
			Enabled: true,
			Backend: "Basic",
			Basic: basic.Options{
				Credentials: map[string]string{
					// Demo user with password "demo"
					"demo": "$2b$15$LRnG3eIHFlYIguTxZOLH7eHwbQC/vqjnLq6nDFiHSUDKIU.f5/1H6",
				},
			},
			LDAP: ldap.Options{
				URI:    "ldap://127.0.0.1:389",
				UserDn: "cn=users,dc=example,dc=com",
			},
		},
		Forwarding: make(map[string][]string),
		Monitoring: monitoring.Options{
			Enabled:       true,
			ListenAddress: ":2112",
		},
		Srt: srt.Options{
			Enabled:       true,
			ListenAddress: ":9710",
			MaxClients:    64,
		},
		Telnet: telnet.Options{
			Enabled:       false,
			ListenAddress: ":8023",
			Width:         80,
			Height:        45,
			Delay:         50,
		},
		Web: web.Options{
			Enabled:                     true,
			Favicon:                     "/static/img/favicon.svg",
			Hostname:                    "localhost",
			ListenAddress:               ":8080",
			Name:                        "Ghostream",
			MapDomainToStream:           make(map[string]string),
			PlayerPoster:                "/static/img/no_stream.svg",
			ViewersCounterRefreshPeriod: 20000,
		},
		WebRTC: webrtc.Options{
			Enabled:     true,
			MaxPortUDP:  10005,
			MinPortUDP:  10000,
			STUNServers: []string{"stun:stun.l.google.com:19302"},
		},
	}
}

// Load global configuration as a struct
func Load() (*Config, error) {
	// Viper needs to know if a key exists in order to override it.
	// See https://github.com/spf13/viper/issues/188
	b, err := yaml.Marshal(New())
	if err != nil {
		return nil, err
	}
	defaultConfig := bytes.NewReader(b)
	viper.SetConfigType("yaml")
	if err := viper.MergeConfig(defaultConfig); err != nil {
		return nil, err
	}

	// Overwrite configuration from file if exists
	viper.SetConfigName("ghostream.yml")
	viper.SetConfigType("yaml")
	viper.AddConfigPath("$HOME/.ghostream")
	viper.AddConfigPath("/etc/ghostream")
	viper.AddConfigPath(".")
	if err := viper.ReadInConfig(); err != nil {
		if _, ok := err.(viper.ConfigFileNotFoundError); ok {
			// Config file not found, ignore and use defaults
			log.Print(err)
		} else {
			// Config file was found but another error was produced
			return nil, err
		}
	} else {
		// Config loaded
		log.Printf("Using config file: %s", viper.ConfigFileUsed())
	}

	// Overwrite configuration from environnement variables
	// Replace "." to "_" for nested structs
	// e.g. GHOSTREAM_LDAP_URI will apply to Config.LDAP.URI
	viper.AutomaticEnv()
	viper.SetEnvPrefix("ghostream")
	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))

	// Load onto struct
	cfg := &Config{}
	if err := viper.UnmarshalExact(cfg); err != nil {
		return nil, err
	}

	// Copy STUN configuration to clients
	cfg.Web.STUNServers = cfg.WebRTC.STUNServers

	// Copy SRT server port to display it on web page
	_, srtPort, err := net.SplitHostPort(cfg.Srt.ListenAddress)
	if err != nil {
		return nil, err
	}
	cfg.Web.SRTServerPort = srtPort

	return cfg, nil
}