package httprange

import (
	
	
	
	
	
	
	
	
	

	

	
)

const (
	noneUnit                = "none"
	httpHeaderRange         = "Range"
	httpHeaderAcceptRanges  = "Accept-Ranges"
	httpHeaderContentRange  = "Content-Range"
	httpHeaderContentType   = "Content-Type"
	mimeMultipartByteranges = "multipart/byteranges"
)

// Specifier represents a Range header value. It consists of a unit and a range
// set (e.g. "bytes=0-99,100-200").
type Specifier string

// Unit returns the unit of the range.
func ( Specifier) () string {
	, ,  := strings.Cut(string(), "=")
	return 
}

// Range represents a single range response.
type Range struct {
	// Resp is the range specification for the content.
	Resp string
	// Reader is the reader for the range content.
	Reader io.Reader
}

// Ranger performs range requests and returns the requested ranges as a sequence.
type Ranger interface {
	// Range returns a non-empty sequence of ranges for the given specifier.
	// Iteration yields Range values until an error. The [Range.Reader] is
	// valid only until the next iteration.
	Range(ctx context.Context, spec Specifier) iter.Seq2[Range, error]
}

// HTTPRanger implements the [Ranger] interface for HTTP resources.
type HTTPRanger struct {
	// Request is a builder for HTTP GET requests.
	Request HTTPRequestBuilder

	// Client is the HTTP client instance to use.
	Client httpclient.Client
}

// Range performs a range request for the given specifier. It sends an HTTP
// request with a Range header and returns an iterator over the ranges in the
// response (a single range or a multipart/byteranges parts).
func ( *HTTPRanger) ( context.Context,  Specifier) iter.Seq2[Range, error] {
	return func( func(Range, error) bool) {
		,  := .Request.Build()
		if  != nil {
			_ = (Range{}, )
			return
		}

		.Header = setHeader(.Header, http.Header{
			httpHeaderRange: {string()},
		})

		,  := .Client.Do()
		if  != nil {
			_ = (Range{}, )
			return
		}

		 := func() { _ = .Body.Close() }
		 := context.AfterFunc(, )
		defer func() {
			if () {
				()
			}
		}()

		if  := parseHTTPResponse(
			,
			.Unit(),
			func( Range) bool {
				return (, nil)
			},
		);  != nil {
			_ = (Range{}, &HTTPResponseError{
				Response: ,
				cause:    ,
			})
		}
	}
}

func ( *http.Response,  string,  func(Range) bool) error {
	// Note that we do not support Accept-Ranges in a trailer section.
	if  := checkAcceptRangesHeader(.Header, );  != nil {
		return 
	}

	switch .StatusCode {
	case http.StatusPartialContent:
		// OK
	case http.StatusRequestedRangeNotSatisfiable:
		if .Header.Get(httpHeaderContentRange) == "" {
			return &UnsatisfiedRangeError{}
		}
		,  := parseContentRangeFromHeader(.Header, )
		if  != nil {
			return errors.Join(
				&UnsatisfiedRangeError{},
				,
			)
		}
		return &UnsatisfiedRangeError{Resp: }
	default:
		return &UnexpectedStatusCodeError{
			Status:     .Status,
			StatusCode: .StatusCode,
		}
	}

	if  := .Header.Get(httpHeaderContentType);  != "" {
		, ,  := mime.ParseMediaType()
		// NB we ignore errors in other content types.
		if  == mimeMultipartByteranges {
			if  != nil {
				return fmt.Errorf(
					"httprange: parse Content-Type header: %w",
					,
				)
			}
			 := multipart.NewReader(.Body, ["boundary"])
			return readMultipartRanges(, , )
		}
	}

	,  := parseContentRangeFromHeader(.Header, )
	if  != nil {
		return 
	}
	_ = (Range{
		Resp:   ,
		Reader: .Body,
	})
	return nil
}

func ( *multipart.Reader,  string,  func(Range) bool) error {
	for  := true; ;  = false {
		,  := .NextPart()
		if  == io.EOF {
			if  {
				return errors.New("httprange: empty multipart ranges")
			}
			return nil
		}
		if  != nil {
			return 
		}

		,  := parseContentRangeFromHeader(http.Header(.Header), )
		if  != nil {
			return 
		}
		if !(Range{Resp: , Reader: }) {
			return nil
		}
	}
}

func ( http.Header,  string) error {
	return checkUnitIsAccepted(.Values(httpHeaderAcceptRanges), )
}

func ( []string,  string) error {
	if len() == 0 {
		return nil // assume it is accepted
	}
	for ,  := range  {
		if equalFoldASCII(, noneUnit) {
			return errRangesNotSupported
		}
	}
	if !httpguts.HeaderValuesContainsToken(, ) {
		return &UnacceptedUnitError{
			AcceptRanges: ,
		}
	}
	return nil
}

func (,  string) bool {
	if len() != len() {
		return false
	}
	for  := range len() {
		if lowerASCII([]) != lowerASCII([]) {
			return false
		}
	}
	return true
}

// lowerASCII returns the ASCII lowercase version of b.
func ( byte) byte {
	if 'A' <=  &&  <= 'Z' {
		return  + ('a' - 'A')
	}
	return 
}