// Code generated by smithy-go-codegen DO NOT EDIT.

package lexruntimev2

import (
	"context"
	awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware"
	"github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/eventstreamapi"
	"github.com/aws/aws-sdk-go-v2/aws/signer/v4"
	"github.com/aws/aws-sdk-go-v2/service/lexruntimev2/types"
	"github.com/aws/smithy-go/middleware"
	smithysync "github.com/aws/smithy-go/sync"
	smithyhttp "github.com/aws/smithy-go/transport/http"
	"sync"
	"time"
)

// Starts an HTTP/2 bidirectional event stream that enables you to send audio,
// text, or DTMF input in real time. After your application starts a conversation,
// users send input to Amazon Lex V2 as a stream of events. Amazon Lex V2 processes
// the incoming events and responds with streaming text or audio events. Audio
// input must be in the following format: audio/lpcm sample-rate=8000
// sample-size-bits=16 channel-count=1; is-big-endian=false. If the optional
// post-fulfillment response is specified, the messages are returned as follows.
// For more information, see PostFulfillmentStatusSpecification
// (https://docs.aws.amazon.com/lexv2/latest/dg/API_PostFulfillmentStatusSpecification.html).
//
// *
// Success message - Returned if the Lambda function completes successfully and the
// intent state is fulfilled or ready fulfillment if the message is present.
//
// *
// Failed message - The failed message is returned if the Lambda function throws an
// exception or if the Lambda function returns a failed intent state without a
// message.
//
// * Timeout message - If you don't configure a timeout message and a
// timeout, and the Lambda function doesn't return within 30 seconds, the timeout
// message is returned. If you configure a timeout, the timeout message is returned
// when the period times out.
//
// For more information, see Completion message
// (https://docs.aws.amazon.com/lexv2/latest/dg/streaming-progress.html#progress-complete.html).
// If the optional update message is configured, it is played at the specified
// frequency while the Lambda function is running and the update message state is
// active. If the fulfillment update message is not active, the Lambda function
// runs with a 30 second timeout. For more information, see Update message
// (https://docs.aws.amazon.com/lexv2/latest/dg/streaming-progress.html#progress-update.html)
// The StartConversation operation is supported only in the following SDKs:
//
// * AWS
// SDK for C++
// (https://docs.aws.amazon.com/goto/SdkForCpp/runtime.lex.v2-2020-08-07/StartConversation)
//
// *
// AWS SDK for Java V2
// (https://docs.aws.amazon.com/goto/SdkForJavaV2/runtime.lex.v2-2020-08-07/StartConversation)
//
// *
// AWS SDK for Ruby V3
// (https://docs.aws.amazon.com/goto/SdkForRubyV3/runtime.lex.v2-2020-08-07/StartConversation)
func (c *Client) StartConversation(ctx context.Context, params *StartConversationInput, optFns ...func(*Options)) (*StartConversationOutput, error) {
	if params == nil {
		params = &StartConversationInput{}
	}

	result, metadata, err := c.invokeOperation(ctx, "StartConversation", params, optFns, c.addOperationStartConversationMiddlewares)
	if err != nil {
		return nil, err
	}

	out := result.(*StartConversationOutput)
	out.ResultMetadata = metadata
	return out, nil
}

type StartConversationInput struct {

	// The alias identifier in use for the bot that processes the request.
	//
	// This member is required.
	BotAliasId *string

	// The identifier of the bot to process the request.
	//
	// This member is required.
	BotId *string

	// The locale where the session is in use.
	//
	// This member is required.
	LocaleId *string

	// The identifier of the user session that is having the conversation.
	//
	// This member is required.
	SessionId *string

	// The conversation type that you are using the Amazon Lex V2. If the conversation
	// mode is AUDIO you can send both audio and DTMF information. If the mode is TEXT
	// you can only send text.
	ConversationMode types.ConversationMode

	noSmithyDocumentSerde
}

type StartConversationOutput struct {
	eventStream *StartConversationEventStream

	// Metadata pertaining to the operation's result.
	ResultMetadata middleware.Metadata

	noSmithyDocumentSerde
}

// GetStream returns the type to interact with the event stream.
func (o *StartConversationOutput) GetStream() *StartConversationEventStream {
	return o.eventStream
}

func (c *Client) addOperationStartConversationMiddlewares(stack *middleware.Stack, options Options) (err error) {
	err = stack.Serialize.Add(&awsRestjson1_serializeOpStartConversation{}, middleware.After)
	if err != nil {
		return err
	}
	err = stack.Deserialize.Add(&awsRestjson1_deserializeOpStartConversation{}, middleware.After)
	if err != nil {
		return err
	}
	if err = addEventStreamStartConversationMiddleware(stack, options); err != nil {
		return err
	}
	if err = smithyhttp.AddRequireMinimumProtocol(stack, 2, 0); err != nil {
		return err
	}
	if err = addSetLoggerMiddleware(stack, options); err != nil {
		return err
	}
	if err = awsmiddleware.AddClientRequestIDMiddleware(stack); err != nil {
		return err
	}
	if err = addResolveEndpointMiddleware(stack, options); err != nil {
		return err
	}
	if err = v4.AddStreamingEventsPayload(stack); err != nil {
		return err
	}
	if err = v4.AddContentSHA256HeaderMiddleware(stack); err != nil {
		return err
	}
	if err = addRetryMiddlewares(stack, options); err != nil {
		return err
	}
	if err = addHTTPSignerV4Middleware(stack, options); err != nil {
		return err
	}
	if err = awsmiddleware.AddRawResponseToMetadata(stack); err != nil {
		return err
	}
	if err = awsmiddleware.AddRecordResponseTiming(stack); err != nil {
		return err
	}
	if err = addClientUserAgent(stack); err != nil {
		return err
	}
	if err = eventstreamapi.AddInitializeStreamWriter(stack); err != nil {
		return err
	}
	if err = addOpStartConversationValidationMiddleware(stack); err != nil {
		return err
	}
	if err = stack.Initialize.Add(newServiceMetadataMiddleware_opStartConversation(options.Region), middleware.Before); err != nil {
		return err
	}
	if err = addRequestIDRetrieverMiddleware(stack); err != nil {
		return err
	}
	if err = addResponseErrorMiddleware(stack); err != nil {
		return err
	}
	if err = addRequestResponseLogging(stack, options); err != nil {
		return err
	}
	return nil
}

func newServiceMetadataMiddleware_opStartConversation(region string) *awsmiddleware.RegisterServiceMetadata {
	return &awsmiddleware.RegisterServiceMetadata{
		Region:        region,
		ServiceID:     ServiceID,
		SigningName:   "lex",
		OperationName: "StartConversation",
	}
}

// StartConversationEventStream provides the event stream handling for the StartConversation operation.
//
// For testing and mocking the event stream this type should be initialized via
// the NewStartConversationEventStream constructor function. Using the functional options
// to pass in nested mock behavior.
type StartConversationEventStream struct {
	// StartConversationRequestEventStreamWriter is the EventStream writer for the
	// StartConversationRequestEventStream events. This value is automatically set by
	// the SDK when the API call is made Use this member when unit testing your code
	// with the SDK to mock out the EventStream Writer.
	//
	// Must not be nil.
	Writer StartConversationRequestEventStreamWriter

	// StartConversationResponseEventStreamReader is the EventStream reader for the
	// StartConversationResponseEventStream events. This value is automatically set by
	// the SDK when the API call is made Use this member when unit testing your code
	// with the SDK to mock out the EventStream Reader.
	//
	// Must not be nil.
	Reader StartConversationResponseEventStreamReader

	done      chan struct{}
	closeOnce sync.Once
	err       *smithysync.OnceErr
}

// NewStartConversationEventStream initializes an StartConversationEventStream.
// This function should only be used for testing and mocking the StartConversationEventStream
// stream within your application.
//
// The Writer member must be set before writing events to the stream.
//
// The Reader member must be set before reading events from the stream.
func NewStartConversationEventStream(optFns ...func(*StartConversationEventStream)) *StartConversationEventStream {
	es := &StartConversationEventStream{
		done: make(chan struct{}),
		err:  smithysync.NewOnceErr(),
	}
	for _, fn := range optFns {
		fn(es)
	}
	return es
}

// Send writes the event to the stream blocking until the event is written.
// Returns an error if the event was not written.
func (es *StartConversationEventStream) Send(ctx context.Context, event types.StartConversationRequestEventStream) error {
	return es.Writer.Send(ctx, event)
}

// Events returns a channel to read events from.
func (es *StartConversationEventStream) Events() <-chan types.StartConversationResponseEventStream {
	return es.Reader.Events()
}

// Close closes the stream. This will also cause the stream to be closed.
// Close must be called when done using the stream API. Not calling Close
// may result in resource leaks.
//
// Will close the underlying EventStream writer and reader, and no more events can be
// sent or received.
func (es *StartConversationEventStream) Close() error {
	es.closeOnce.Do(es.safeClose)
	return es.Err()
}

func (es *StartConversationEventStream) safeClose() {
	close(es.done)

	t := time.NewTicker(time.Second)
	defer t.Stop()
	writeCloseDone := make(chan error)
	go func() {
		if err := es.Writer.Close(); err != nil {
			es.err.SetError(err)
		}
		close(writeCloseDone)
	}()
	select {
	case <-t.C:
	case <-writeCloseDone:
	}

	es.Reader.Close()
}

// Err returns any error that occurred while reading or writing EventStream Events
// from the service API's response. Returns nil if there were no errors.
func (es *StartConversationEventStream) Err() error {
	if err := es.err.Err(); err != nil {
		return err
	}

	if err := es.Writer.Err(); err != nil {
		return err
	}

	if err := es.Reader.Err(); err != nil {
		return err
	}

	return nil
}

func (es *StartConversationEventStream) waitStreamClose() {
	type errorSet interface {
		ErrorSet() <-chan struct{}
	}

	var inputErrCh <-chan struct{}
	if v, ok := es.Writer.(errorSet); ok {
		inputErrCh = v.ErrorSet()
	}

	var outputErrCh <-chan struct{}
	if v, ok := es.Reader.(errorSet); ok {
		outputErrCh = v.ErrorSet()
	}
	var outputClosedCh <-chan struct{}
	if v, ok := es.Reader.(interface{ Closed() <-chan struct{} }); ok {
		outputClosedCh = v.Closed()
	}

	select {
	case <-es.done:
	case <-inputErrCh:
		es.err.SetError(es.Writer.Err())
		es.Close()

	case <-outputErrCh:
		es.err.SetError(es.Reader.Err())
		es.Close()

	case <-outputClosedCh:
		if err := es.Reader.Err(); err != nil {
			es.err.SetError(es.Reader.Err())
		}
		es.Close()

	}
}
