package ssdp

import (
	
	
	
	
	
	
	

	
	
)

type message struct {
	to   net.Addr
	data multicast.DataProvider
}

// Advertiser is a server to advertise a service.
type Advertiser struct {
	st      string
	usn     string
	locProv LocationProvider
	server  string
	maxAge  int

	conn *multicast.Conn
	ch   chan *message
	wg   sync.WaitGroup
	wgS  sync.WaitGroup

	// addHost is an optional flag to add HOST header for M-SEARCH response.
	// It is to support SmartThings.
	// See https://github.com/koron/go-ssdp/issues/30 for details
	addHost bool
}

// Advertise starts advertisement of service.
// location should be a string or a ssdp.LocationProvider.
func (,  string,  any,  string,  int,  ...Option) (*Advertiser, error) {
	,  := toLocationProvider()
	if  != nil {
		return nil, 
	}
	,  := opts2config()
	if  != nil {
		return nil, 
	}
	,  := multicast.Listen(multicast.RecvAddrResolver, .multicastConfig.options()...)
	if  != nil {
		return nil, 
	}
	ssdplog.Printf("SSDP advertise on: %s", .LocalAddr().String())
	 := &Advertiser{
		st:      ,
		usn:     ,
		locProv: ,
		server:  ,
		maxAge:  ,
		conn:    ,
		ch:      make(chan *message),
		addHost: .advertiseConfig.addHost,
	}
	.wg.Add(2)
	.wgS.Add(1)
	go func() {
		.sendMain()
		.wgS.Done()
		.wg.Done()
	}()
	go func() {
		.recvMain()
		.wg.Done()
	}()
	return , nil
}

func ( *Advertiser) () error {
	// TODO: update listening interfaces of a.conn
	 := .conn.ReadPackets(0, func( net.Addr,  []byte) error {
		if  := .handleRaw(, );  != nil {
			ssdplog.Printf("failed to handle message: %s", )
		}
		return nil
	})
	if  != nil &&  != io.EOF {
		return 
	}
	return nil
}

func ( *Advertiser) () {
	for  := range .ch {
		,  := .conn.WriteTo(.data, .to)
		if  != nil {
			ssdplog.Printf("failed to send: %s", )
		}
	}
}

func ( *Advertiser) ( net.Addr,  []byte) error {
	if !bytes.HasPrefix(, []byte("M-SEARCH ")) {
		// unexpected method.
		return nil
	}
	,  := http.ReadRequest(bufio.NewReader(bytes.NewReader()))
	if  != nil {
		return 
	}
	var (
		 = .Header.Get("MAN")
		  = .Header.Get("ST")
	)
	if  != `"ssdp:discover"` {
		return fmt.Errorf("unexpected MAN: %s", )
	}
	if  != All &&  != RootDevice &&  != .st {
		// skip when ST is not matched/expected.
		return nil
	}
	ssdplog.Printf("received M-SEARCH MAN=%s ST=%s from %s", , , .String())
	// build and send a response.
	var  string
	if .addHost {
		,  := multicast.SendAddr()
		if  != nil {
			return 
		}
		 = .String()
	}
	 := buildOK(.st, .usn, .locProv.Location(, nil), .server, .maxAge, )
	.ch <- &message{to: , data: multicast.BytesDataProvider()}
	return nil
}

func buildOK(, , ,  string,  int,  string) []byte {
	// bytes.Buffer#Write() is never fail, so we can omit error checks.
	 := new(bytes.Buffer)
	.WriteString("HTTP/1.1 200 OK\r\n")
	fmt.Fprintf(, "EXT: \r\n")
	fmt.Fprintf(, "ST: %s\r\n", )
	fmt.Fprintf(, "USN: %s\r\n", )
	if  != "" {
		fmt.Fprintf(, "LOCATION: %s\r\n", )
	}
	if  != "" {
		fmt.Fprintf(, "SERVER: %s\r\n", )
	}
	fmt.Fprintf(, "CACHE-CONTROL: max-age=%d\r\n", )
	if  != "" {
		fmt.Fprintf(, "HOST: %s\r\n", )
	}
	.WriteString("\r\n")
	return .Bytes()
}

// Close stops advertisement.
func ( *Advertiser) () error {
	if .conn != nil {
		// closing order is very important. be careful to change:
		// stop sending loop by closing the channel and wait it.
		close(.ch)
		.wgS.Wait()
		// stop receiving loop by closing the connection.
		.conn.Close()
		.wg.Wait()
		.conn = nil
	}
	return nil
}

// Alive announces ssdp:alive message.
func ( *Advertiser) () error {
	,  := multicast.SendAddr()
	if  != nil {
		return 
	}
	 := &aliveDataProvider{
		host:     ,
		nt:       .st,
		usn:      .usn,
		location: .locProv,
		server:   .server,
		maxAge:   .maxAge,
	}
	.ch <- &message{to: , data: }
	ssdplog.Printf("sent alive")
	return nil
}

// Bye announces ssdp:byebye message.
func ( *Advertiser) () error {
	,  := multicast.SendAddr()
	if  != nil {
		return 
	}
	,  := buildBye(, .st, .usn)
	if  != nil {
		return 
	}
	.ch <- &message{to: , data: multicast.BytesDataProvider()}
	ssdplog.Printf("sent bye")
	return nil
}