package ssdp

import (
	
	
	
	
	
	
	
	
	

	
	
)

// Service is discovered service.
type Service struct {
	// Type is a property of "ST"
	Type string

	// USN is a property of "USN"
	USN string

	// Location is a property of "LOCATION"
	Location string

	// Server is a property of "SERVER"
	Server string

	rawHeader http.Header
	maxAge    *int
}

var rxMaxAge = regexp.MustCompile(`\bmax-age\s*=\s*(\d+)\b`)

func extractMaxAge( string,  int) int {
	if  := rxMaxAge.FindStringSubmatch();  != nil {
		,  := strconv.ParseInt([1], 10, 32)
		if  == nil {
			return int()
		}
	}
	return 
}

// MaxAge extracts "max-age" value from "CACHE-CONTROL" property.
func ( *Service) () int {
	if .maxAge == nil {
		.maxAge = new(int)
		*.maxAge = extractMaxAge(.rawHeader.Get("CACHE-CONTROL"), -1)
	}
	return *.maxAge
}

// Header returns all properties in response of search.
func ( *Service) () http.Header {
	return .rawHeader
}

const (
	// All is a search type to search all services and devices.
	All = "ssdp:all"

	// RootDevice is a search type to search UPnP root devices.
	RootDevice = "upnp:rootdevice"
)

// Search searches services by SSDP.
func ( string,  int,  string,  ...Option) ([]Service, error) {
	,  := opts2config()
	if  != nil {
		return nil, 
	}
	// dial multicast UDP packet.
	,  := multicast.Listen(&multicast.AddrResolver{Addr: }, .multicastConfig.options()...)
	if  != nil {
		return nil, 
	}
	defer .Close()
	ssdplog.Printf("search on %s", .LocalAddr().String())

	// send request.
	,  := multicast.SendAddr()
	if  != nil {
		return nil, 
	}
	,  := buildSearch(, , )
	if  != nil {
		return nil, 
	}
	if ,  := .WriteTo(multicast.BytesDataProvider(), );  != nil {
		return nil, 
	}

	// wait response.
	var  []Service
	 := func( net.Addr,  []byte) error {
		,  := parseService()
		if  != nil {
			ssdplog.Printf("invalid search response from %s: %s", .String(), )
			return nil
		}
		 = append(, *)
		ssdplog.Printf("search response from %s: %s", .String(), .USN)
		return nil
	}
	 := time.Second * time.Duration()
	if  := .ReadPackets(, );  != nil {
		return nil, 
	}

	return , 
}

func buildSearch( net.Addr,  string,  int) ([]byte, error) {
	 := new(bytes.Buffer)
	// FIXME: error should be checked.
	.WriteString("M-SEARCH * HTTP/1.1\r\n")
	fmt.Fprintf(, "HOST: %s\r\n", .String())
	fmt.Fprintf(, "MAN: %q\r\n", "ssdp:discover")
	fmt.Fprintf(, "MX: %d\r\n", )
	fmt.Fprintf(, "ST: %s\r\n", )
	.WriteString("\r\n")
	return .Bytes(), nil
}

var (
	errWithoutHTTPPrefix = errors.New("without HTTP prefix")
)

var endOfHeader = []byte{'\r', '\n', '\r', '\n'}

func parseService( []byte) (*Service, error) {
	if !bytes.HasPrefix(, []byte("HTTP")) {
		return nil, errWithoutHTTPPrefix
	}
	// Complement newlines on tail of header for buggy SSDP responses.
	if !bytes.HasSuffix(, endOfHeader) {
		// why we should't use append() for this purpose:
		// https://play.golang.org/p/IM1pONW9lqm
		 = bytes.Join([][]byte{, endOfHeader}, nil)
	}
	,  := http.ReadResponse(bufio.NewReader(bytes.NewReader()), nil)
	if  != nil {
		return nil, 
	}
	defer .Body.Close()
	return &Service{
		Type:      .Header.Get("ST"),
		USN:       .Header.Get("USN"),
		Location:  .Header.Get("LOCATION"),
		Server:    .Header.Get("SERVER"),
		rawHeader: .Header,
	}, nil
}