package host

import (
	"fmt"
	"net"
	"strings"

	"git.giaever.org/joachimmg/go-log.git/log"
	"git.giaever.org/joachimmg/m-dns/errors"
)

type Host interface {
	GetInstance() Instance
	GetService() Service
	GetDomain() Domain
	GetHostname() Hostname
	GetIPs() []HostIP
	GetPort() HostPort
	GetTXTs() []string
	GetServiceAddr() HostString
	GetInstanceAddr() HostString
	GetHostnameAddr() HostString
	GetDiscoveryAddr() HostString
	String() string
}

type host struct {
	instance Instance
	service  Service
	domain   Domain
	hostname Hostname
	ip       []HostIP
	port     HostPort
	txt      []TXT
}

func New(instance, service, domain, hostname string, ip []net.IP, port int, txt []string) (*host, error) {

	h := new(host)

	if h == nil {
		log.Traceln(errors.Host, errors.OutOfMemory)
		return nil, errors.OutOfMemory
	}

	var err error
	if h.instance, err = String(instance).isInstanceVariable(); err != nil {
		return nil, err
	}

	if h.service, err = String(service).isServiceVariable(); err != nil {
		return nil, err
	}

	if h.domain, err = String(domain).isDomainVariable(); err != nil {
		return nil, err
	}

	if h.hostname, err = String(hostname).isHostnameVariable(); err != nil {
		return nil, err
	}

	for _, _ip := range ip {
		if hip, err := IP(_ip).validForHostname(h.hostname); err == nil {
			h.ip = append(h.ip, hip)
		}
	}

	if len(h.ip) == 0 {
		addrs, err := net.InterfaceAddrs()

		if err != nil {
			log.Traceln(errors.Host, err)
			return nil, err
		}

		for _, addr := range addrs {
			ip := strings.SplitN(addr.String(), "/", 2)
			if len(ip) >= 1 {
				if hip, err := IP(net.ParseIP(ip[0])).validForHostname(h.GetHostname()); err == nil {
					h.ip = append(h.ip, hip)
				}
			}
		}
	}

	if err := Port(port).isValid(); err != nil {
		return nil, err
	}

	h.port = Port(port)

	for _, t := range txt {
		ht, err := String(t).isTxtVariable()
		if err != nil {
			return nil, err
		} else if len(ht) != 0 {
			h.txt = append(h.txt, ht)
		}
	}

	return h, nil
}

func (h *host) GetInstance() Instance {
	return h.instance
}

func (h *host) GetService() Service {
	return h.service
}

func (h *host) GetDomain() Domain {
	return h.domain
}

func (h *host) GetHostname() Hostname {
	return h.hostname
}

func (h *host) GetIPs() []HostIP {
	return h.ip
}

func (h *host) HasIPs() bool {
	if len(h.GetIPs()) == 0 {
		return false
	}
	for _, ip := range h.GetIPs() {
		if _, t := ip.Type(); t == IPv4 || t == IPv6 {
			return true
		}
	}
	return false
}

func (h *host) GetPort() HostPort {
	return h.port
}

func (h *host) GetTXTs() []string {
	s := []string{}
	for _, t := range h.txt {
		s = append(s, t.String())
	}
	return s
}

func (h *host) GetServiceAddr() HostString {
	return String(fmt.Sprintf("%s.%s.", h.GetService(), h.GetDomain()))
}

func (h *host) GetInstanceAddr() HostString {
	return String(fmt.Sprintf("%s.%s", h.GetInstance().EncodedInstance(), h.GetServiceAddr()))
}

func (h *host) GetHostnameAddr() HostString {
	return String(fmt.Sprintf("%s.%s.", h.GetHostname(), h.GetDomain()))
}

func (h *host) GetDiscoveryAddr() HostString {
	return String(fmt.Sprintf("_services._dns-sd._udp.%s.", h.GetDomain()))
}

func (h *host) String() string {
	ips := []string{}
	for _, ip := range h.GetIPs() {
		ips = append(ips, ip.String())
	}

	return fmt.Sprintf(`
Host:	%s
	+ Instance:	%s
	+ Service:	%s
	+ Domain:	%s
	+ IP(s):
		* %s
	+ Port:		%d
	+ Txt(s):	
		* %s
	Addr:
		+ Instance:	%s
		+ Service:	%s
		+ Discovery:	%s
	Service-protocol:	%s`,
		h.GetHostname(),
		h.GetInstance(),
		h.GetService(),
		h.GetDomain(),
		strings.Join(ips, "\n\t\t* "),
		h.port,
		strings.Join(h.GetTXTs(), "\n\t\t* "),
		h.GetInstanceAddr(),
		h.GetServiceAddr(),
		h.GetDiscoveryAddr(),
		h.GetService().RootType()[1:],
	)
}
