package ssdp

import (
	
	
	
	
	
	
	
)

const (
	ssdpDiscover   = `"ssdp:discover"`
	ntsAlive       = `ssdp:alive`
	ntsByebye      = `ssdp:byebye`
	ntsUpdate      = `ssdp:update`
	ssdpUDP4Addr   = "239.255.255.250:1900"
	ssdpSearchPort = 1900
	methodSearch   = "M-SEARCH"
	methodNotify   = "NOTIFY"

	// SSDPAll is a value for searchTarget that searches for all devices and services.
	SSDPAll = "ssdp:all"
	// UPNPRootDevice is a value for searchTarget that searches for all root devices.
	UPNPRootDevice = "upnp:rootdevice"
)

// HTTPUClient is the interface required to perform HTTP-over-UDP requests.
type HTTPUClient interface {
	Do(
		req *http.Request,
		timeout time.Duration,
		numSends int,
	) ([]*http.Response, error)
}

// HTTPUClientCtx is an optional interface that will be used to perform
// HTTP-over-UDP requests if the client implements it.
type HTTPUClientCtx interface {
	DoWithContext(
		req *http.Request,
		numSends int,
	) ([]*http.Response, error)
}

// SSDPRawSearchCtx performs a fairly raw SSDP search request, and returns the
// unique response(s) that it receives. Each response has the requested
// searchTarget, a USN, and a valid location. maxWaitSeconds states how long to
// wait for responses in seconds, and must be a minimum of 1 (the
// implementation waits an additional 100ms for responses to arrive), 2 is a
// reasonable value for this. numSends is the number of requests to send - 3 is
// a reasonable value for this.
func (
	 context.Context,
	 HTTPUClient,
	 string,
	 int,
	 int,
) ([]*http.Response, error) {
	,  := prepareRequest(, , )
	if  != nil {
		return nil, 
	}

	,  := .Do(, time.Duration()*time.Second+100*time.Millisecond, )
	if  != nil {
		return nil, 
	}
	return processSSDPResponses(, )
}

// RawSearch performs a fairly raw SSDP search request, and returns the
// unique response(s) that it receives. Each response has the requested
// searchTarget, a USN, and a valid location. If the provided context times out
// or is canceled, the search will be aborted. numSends is the number of
// requests to send - 3 is a reasonable value for this.
//
// The provided context should have a deadline, since the SSDP protocol
// requires the max wait time be included in search requests. If the context
// has no deadline, then a default deadline of 3 seconds will be applied.
func (
	 context.Context,
	 HTTPUClientCtx,
	 string,
	 int,
) ([]*http.Response, error) {
	// We need a timeout value to include in the SSDP request; get it by
	// checking the deadline on the context.
	var  int
	if ,  := .Deadline();  {
		 = int(.Sub(time.Now()) / time.Second)
	} else {
		// Pick a default timeout of 3 seconds if none was provided.
		 = 3

		var  func()
		,  = context.WithTimeout(, time.Duration()*time.Second)
		defer ()
	}

	,  := prepareRequest(, , )
	if  != nil {
		return nil, 
	}

	,  := .DoWithContext(, )
	if  != nil {
		return nil, 
	}
	return processSSDPResponses(, )
}

// prepareRequest checks the provided parameters and constructs a SSDP search
// request to be sent.
func prepareRequest( context.Context,  string,  int) (*http.Request, error) {
	if  < 1 {
		return nil, errors.New("ssdp: request timeout must be at least 1s")
	}

	 := (&http.Request{
		Method: methodSearch,
		// TODO: Support both IPv4 and IPv6.
		Host: ssdpUDP4Addr,
		URL:  &url.URL{Opaque: "*"},
		Header: http.Header{
			// Putting headers in here avoids them being title-cased.
			// (The UPnP discovery protocol uses case-sensitive headers)
			"HOST": []string{ssdpUDP4Addr},
			"MX":   []string{strconv.FormatInt(int64(), 10)},
			"MAN":  []string{ssdpDiscover},
			"ST":   []string{},
		},
	}).WithContext()
	return , nil
}

func processSSDPResponses(
	 string,
	 []*http.Response,
) ([]*http.Response, error) {
	 :=  != SSDPAll &&  != UPNPRootDevice

	 := make(map[string]bool)
	var  []*http.Response
	for ,  := range  {
		if .StatusCode != 200 {
			log.Printf("ssdp: got response status code %q in search response", .Status)
			continue
		}
		if  := .Header.Get("ST");  &&  !=  {
			continue
		}
		 := .Header.Get("USN")
		,  := .Location()
		if  != nil {
			// No usable location in search response - discard.
			continue
		}
		 := .String() + "\x00" + 
		if ,  := []; ! {
			[] = true
			 = append(, )
		}
	}

	return , nil
}

// SSDPRawSearch is the legacy version of SSDPRawSearchCtx, but uses
// context.Background() as the context.
func ( HTTPUClient,  string,  int,  int) ([]*http.Response, error) {
	return SSDPRawSearchCtx(context.Background(), , , , )
}