package multistream

import (
	
	
	
	
	
	
)

// ErrNotSupported is the error returned when the muxer doesn't support
// the protocols tried for the handshake.
type ErrNotSupported[ StringLike] struct {

	// Slice of protocols that were not supported by the muxer
	Protos []
}

func ( ErrNotSupported[]) () string {
	return fmt.Sprintf("protocols not supported: %v", .Protos)
}

func ( ErrNotSupported[]) ( error) bool {
	,  := .(ErrNotSupported[])
	return 
}

type ErrUnrecognizedResponse[ StringLike] struct {
	Actual   
	Expected 
}

func ( ErrUnrecognizedResponse[]) () string {
	return fmt.Sprintf("unrecognized response. expected: %s (or na). got: %s", .Expected, .Actual)
}

// ErrNoProtocols is the error returned when the no protocols have been
// specified.
var ErrNoProtocols = errors.New("no protocols specified")

// SelectProtoOrFail performs the initial multistream handshake
// to inform the muxer of the protocol that will be used to communicate
// on this ReadWriteCloser. It returns an error if, for example,
// the muxer does not know how to handle this protocol.
func [ StringLike]( ,  io.ReadWriteCloser) ( error) {
	defer func() {
		if  := recover();  != nil {
			fmt.Fprintf(os.Stderr, "caught panic: %s\n%s\n", , debug.Stack())
			 = fmt.Errorf("panic selecting protocol: %s", )
		}
	}()

	 := make(chan error, 1)
	go func() {
		var  bytes.Buffer
		if  := delitmWriteAll(&, []byte(ProtocolID), []byte());  != nil {
			 <- 
			return
		}
		,  := io.Copy(, &)
		 <- 
	}()
	// We have to read *both* errors.
	 := readMultistreamHeader()
	 := readProto(, )
	if  := <-;  != nil {
		return 
	}
	if  != nil {
		return 
	}
	if  != nil {
		return 
	}
	return nil
}

// SelectOneOf will perform handshakes with the protocols on the given slice
// until it finds one which is supported by the muxer.
func [ StringLike]( [],  io.ReadWriteCloser) ( ,  error) {
	defer func() {
		if  := recover();  != nil {
			fmt.Fprintf(os.Stderr, "caught panic: %s\n%s\n", , debug.Stack())
			 = fmt.Errorf("panic selecting one of protocols: %s", )
		}
	}()

	if len() == 0 {
		return "", ErrNoProtocols
	}

	// Use SelectProtoOrFail to pipeline the /multistream/1.0.0 handshake
	// with an attempt to negotiate the first protocol. If that fails, we
	// can continue negotiating the rest of the protocols normally.
	//
	// This saves us a round trip.
	switch  := SelectProtoOrFail([0], ); .(type) {
	case nil:
		return [0], nil
	case ErrNotSupported[]: // try others
	default:
		return "", 
	}
	,  = selectProtosOrFail([1:], )
	if ,  := .(ErrNotSupported[]);  {
		return "", ErrNotSupported[]{}
	}
	return , 
}

func selectProtosOrFail[ StringLike]( [],  io.ReadWriteCloser) (, error) {
	for ,  := range  {
		 := trySelect(, )
		switch err := .(type) {
		case nil:
			return , nil
		case ErrNotSupported[]:
		default:
			return "", 
		}
	}
	return "", ErrNotSupported[]{}
}

func readMultistreamHeader( io.Reader) error {
	,  := ReadNextToken[string]()
	if  != nil {
		return 
	}

	if  != ProtocolID {
		return errors.New("received mismatch in protocol id")
	}
	return nil
}

func trySelect[ StringLike]( ,  io.ReadWriteCloser) error {
	 := delimWriteBuffered(, []byte())
	if  != nil {
		return 
	}
	return readProto(, )
}

func readProto[ StringLike]( ,  io.Reader) error {
	,  := ReadNextToken[]()
	if  != nil {
		return 
	}

	switch  {
	case :
		return nil
	case "na":
		return ErrNotSupported[]{[]{}}
	default:
		return ErrUnrecognizedResponse[]{
			Actual:   ,
			Expected: ,
		}
	}
}