package srslog

import (
	"crypto/tls"
	"net"
)

// dialerFunctionWrapper is a simple object that consists of a dialer function
// and its name. This is primarily for testing, so we can make sure that the
// getDialer method returns the correct dialer function. However, if you ever
// find that you need to check which dialer function you have, this would also
// be useful for you without having to use reflection.
type dialerFunctionWrapper struct {
	Name   string
	Dialer func() (serverConn, string, error)
}

// Call the wrapped dialer function and return its return values.
func (df dialerFunctionWrapper) Call() (serverConn, string, error) {
	return df.Dialer()
}

// getDialer returns a "dialer" function that can be called to connect to a
// syslog server.
//
// Each dialer function is responsible for dialing the remote host and returns
// a serverConn, the hostname (or a default if the Writer has not specified a
// hostname), and an error in case dialing fails.
//
// The reason for separate dialers is that different network types may need
// to dial their connection differently, yet still provide a net.Conn interface
// that you can use once they have dialed. Rather than an increasingly long
// conditional, we have a map of network -> dialer function (with a sane default
// value), and adding a new network type is as easy as writing the dialer
// function and adding it to the map.
func (w *Writer) getDialer() dialerFunctionWrapper {
	dialers := map[string]dialerFunctionWrapper{
		"":        dialerFunctionWrapper{"unixDialer", w.unixDialer},
		"tcp+tls": dialerFunctionWrapper{"tlsDialer", w.tlsDialer},
		"custom":  dialerFunctionWrapper{"customDialer", w.customDialer},
	}
	dialer, ok := dialers[w.network]
	if !ok {
		dialer = dialerFunctionWrapper{"basicDialer", w.basicDialer}
	}
	return dialer
}

// unixDialer uses the unixSyslog method to open a connection to the syslog
// daemon running on the local machine.
func (w *Writer) unixDialer() (serverConn, string, error) {
	sc, err := unixSyslog()
	hostname := w.hostname
	if hostname == "" {
		hostname = "localhost"
	}
	return sc, hostname, err
}

// tlsDialer connects to TLS over TCP, and is used for the "tcp+tls" network
// type.
func (w *Writer) tlsDialer() (serverConn, string, error) {
	c, err := tls.Dial("tcp", w.raddr, w.tlsConfig)
	var sc serverConn
	hostname := w.hostname
	if err == nil {
		sc = newNetConn(c)
		if hostname == "" {
			hostname = c.LocalAddr().String()
		}
	}
	return sc, hostname, err
}

// basicDialer is the most common dialer for syslog, and supports both TCP and
// UDP connections.
func (w *Writer) basicDialer() (serverConn, string, error) {
	c, err := net.Dial(w.network, w.raddr)
	var sc serverConn
	hostname := w.hostname
	if err == nil {
		sc = newNetConn(c)
		if hostname == "" {
			hostname = c.LocalAddr().String()
		}
	}
	return sc, hostname, err
}

// customDialer uses the custom dialer when the Writer was created
// giving developers total control over how connections are made and returned.
// Note it does not check if cdialer is nil, as it should only be referenced from getDialer.
func (w *Writer) customDialer() (serverConn, string, error) {
	c, err := w.customDial(w.network, w.raddr)
	var sc serverConn
	hostname := w.hostname
	if err == nil {
		sc = newNetConn(c)
		if hostname == "" {
			hostname = c.LocalAddr().String()
		}
	}
	return sc, hostname, err
}
