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

package cloudwatch

import (
	"context"
	"fmt"
	awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware"
	"github.com/aws/aws-sdk-go-v2/service/cloudwatch/types"
	"github.com/aws/smithy-go/middleware"
	smithyhttp "github.com/aws/smithy-go/transport/http"
	"time"
)

// You can use the GetMetricData API to retrieve CloudWatch metric values. The
// operation can also include a CloudWatch Metrics Insights query, and one or more
// metric math functions.
//
// A GetMetricData operation that does not include a query can retrieve as many as
// 500 different metrics in a single request, with a total of as many as 100,800
// data points. You can also optionally perform metric math expressions on the
// values of the returned statistics, to create new time series that represent new
// insights into your data. For example, using Lambda metrics, you could divide the
// Errors metric by the Invocations metric to get an error rate time series. For
// more information about metric math expressions, see [Metric Math Syntax and Functions]in the Amazon CloudWatch
// User Guide.
//
// If you include a Metrics Insights query, each GetMetricData operation can
// include only one query. But the same GetMetricData operation can also retrieve
// other metrics. Metrics Insights queries can query only the most recent three
// hours of metric data. For more information about Metrics Insights, see [Query your metrics with CloudWatch Metrics Insights].
//
// Calls to the GetMetricData API have a different pricing structure than calls to
// GetMetricStatistics . For more information about pricing, see [Amazon CloudWatch Pricing].
//
// Amazon CloudWatch retains metric data as follows:
//
//   - Data points with a period of less than 60 seconds are available for 3
//     hours. These data points are high-resolution metrics and are available only for
//     custom metrics that have been defined with a StorageResolution of 1.
//
//   - Data points with a period of 60 seconds (1-minute) are available for 15
//     days.
//
//   - Data points with a period of 300 seconds (5-minute) are available for 63
//     days.
//
//   - Data points with a period of 3600 seconds (1 hour) are available for 455
//     days (15 months).
//
// Data points that are initially published with a shorter period are aggregated
// together for long-term storage. For example, if you collect data using a period
// of 1 minute, the data remains available for 15 days with 1-minute resolution.
// After 15 days, this data is still available, but is aggregated and retrievable
// only with a resolution of 5 minutes. After 63 days, the data is further
// aggregated and is available with a resolution of 1 hour.
//
// If you omit Unit in your request, all data that was collected with any unit is
// returned, along with the corresponding units that were specified when the data
// was reported to CloudWatch. If you specify a unit, the operation returns only
// data that was collected with that unit specified. If you specify a unit that
// does not match the data collected, the results of the operation are null.
// CloudWatch does not perform unit conversions.
//
// # Using Metrics Insights queries with metric math
//
// You can't mix a Metric Insights query and metric math syntax in the same
// expression, but you can reference results from a Metrics Insights query within
// other Metric math expressions. A Metrics Insights query without a GROUP BY
// clause returns a single time-series (TS), and can be used as input for a metric
// math expression that expects a single time series. A Metrics Insights query with
// a GROUP BY clause returns an array of time-series (TS[]), and can be used as
// input for a metric math expression that expects an array of time series.
//
// [Metric Math Syntax and Functions]: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/using-metric-math.html#metric-math-syntax
// [Query your metrics with CloudWatch Metrics Insights]: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/query_with_cloudwatch-metrics-insights.html
// [Amazon CloudWatch Pricing]: https://aws.amazon.com/cloudwatch/pricing/
func (c *Client) GetMetricData(ctx context.Context, params *GetMetricDataInput, optFns ...func(*Options)) (*GetMetricDataOutput, error) {
	if params == nil {
		params = &GetMetricDataInput{}
	}

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

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

type GetMetricDataInput struct {

	// The time stamp indicating the latest data to be returned.
	//
	// The value specified is exclusive; results include data points up to the
	// specified time stamp.
	//
	// For better performance, specify StartTime and EndTime values that align with
	// the value of the metric's Period and sync up with the beginning and end of an
	// hour. For example, if the Period of a metric is 5 minutes, specifying 12:05 or
	// 12:30 as EndTime can get a faster response from CloudWatch than setting 12:07
	// or 12:29 as the EndTime .
	//
	// This member is required.
	EndTime *time.Time

	// The metric queries to be returned. A single GetMetricData call can include as
	// many as 500 MetricDataQuery structures. Each of these structures can specify
	// either a metric to retrieve, a Metrics Insights query, or a math expression to
	// perform on retrieved data.
	//
	// This member is required.
	MetricDataQueries []types.MetricDataQuery

	// The time stamp indicating the earliest data to be returned.
	//
	// The value specified is inclusive; results include data points with the
	// specified time stamp.
	//
	// CloudWatch rounds the specified time stamp as follows:
	//
	//   - Start time less than 15 days ago - Round down to the nearest whole minute.
	//   For example, 12:32:34 is rounded down to 12:32:00.
	//
	//   - Start time between 15 and 63 days ago - Round down to the nearest 5-minute
	//   clock interval. For example, 12:32:34 is rounded down to 12:30:00.
	//
	//   - Start time greater than 63 days ago - Round down to the nearest 1-hour
	//   clock interval. For example, 12:32:34 is rounded down to 12:00:00.
	//
	// If you set Period to 5, 10, or 30, the start time of your request is rounded
	// down to the nearest time that corresponds to even 5-, 10-, or 30-second
	// divisions of a minute. For example, if you make a query at (HH:mm:ss) 01:05:23
	// for the previous 10-second period, the start time of your request is rounded
	// down and you receive data from 01:05:10 to 01:05:20. If you make a query at
	// 15:07:17 for the previous 5 minutes of data, using a period of 5 seconds, you
	// receive data timestamped between 15:02:15 and 15:07:15.
	//
	// For better performance, specify StartTime and EndTime values that align with
	// the value of the metric's Period and sync up with the beginning and end of an
	// hour. For example, if the Period of a metric is 5 minutes, specifying 12:05 or
	// 12:30 as StartTime can get a faster response from CloudWatch than setting 12:07
	// or 12:29 as the StartTime .
	//
	// This member is required.
	StartTime *time.Time

	// This structure includes the Timezone parameter, which you can use to specify
	// your time zone so that the labels of returned data display the correct time for
	// your time zone.
	LabelOptions *types.LabelOptions

	// The maximum number of data points the request should return before paginating.
	// If you omit this, the default of 100,800 is used.
	MaxDatapoints *int32

	// Include this value, if it was returned by the previous GetMetricData operation,
	// to get the next set of data points.
	NextToken *string

	// The order in which data points should be returned. TimestampDescending returns
	// the newest data first and paginates when the MaxDatapoints limit is reached.
	// TimestampAscending returns the oldest data first and paginates when the
	// MaxDatapoints limit is reached.
	//
	// If you omit this parameter, the default of TimestampDescending is used.
	ScanBy types.ScanBy

	noSmithyDocumentSerde
}

type GetMetricDataOutput struct {

	// Contains a message about this GetMetricData operation, if the operation results
	// in such a message. An example of a message that might be returned is Maximum
	// number of allowed metrics exceeded . If there is a message, as much of the
	// operation as possible is still executed.
	//
	// A message appears here only if it is related to the global GetMetricData
	// operation. Any message about a specific metric returned by the operation appears
	// in the MetricDataResult object returned for that metric.
	Messages []types.MessageData

	// The metrics that are returned, including the metric name, namespace, and
	// dimensions.
	MetricDataResults []types.MetricDataResult

	// A token that marks the next batch of returned results.
	NextToken *string

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

	noSmithyDocumentSerde
}

func (c *Client) addOperationGetMetricDataMiddlewares(stack *middleware.Stack, options Options) (err error) {
	if err := stack.Serialize.Add(&setOperationInputMiddleware{}, middleware.After); err != nil {
		return err
	}
	err = stack.Serialize.Add(&awsAwsquery_serializeOpGetMetricData{}, middleware.After)
	if err != nil {
		return err
	}
	err = stack.Deserialize.Add(&awsAwsquery_deserializeOpGetMetricData{}, middleware.After)
	if err != nil {
		return err
	}
	if err := addProtocolFinalizerMiddlewares(stack, options, "GetMetricData"); err != nil {
		return fmt.Errorf("add protocol finalizers: %v", err)
	}

	if err = addlegacyEndpointContextSetter(stack, options); err != nil {
		return err
	}
	if err = addSetLoggerMiddleware(stack, options); err != nil {
		return err
	}
	if err = addClientRequestID(stack); err != nil {
		return err
	}
	if err = addComputeContentLength(stack); err != nil {
		return err
	}
	if err = addResolveEndpointMiddleware(stack, options); err != nil {
		return err
	}
	if err = addComputePayloadSHA256(stack); err != nil {
		return err
	}
	if err = addRetry(stack, options); err != nil {
		return err
	}
	if err = addRawResponseToMetadata(stack); err != nil {
		return err
	}
	if err = addRecordResponseTiming(stack); err != nil {
		return err
	}
	if err = addSpanRetryLoop(stack, options); err != nil {
		return err
	}
	if err = addClientUserAgent(stack, options); err != nil {
		return err
	}
	if err = smithyhttp.AddErrorCloseResponseBodyMiddleware(stack); err != nil {
		return err
	}
	if err = smithyhttp.AddCloseResponseBodyMiddleware(stack); err != nil {
		return err
	}
	if err = addSetLegacyContextSigningOptionsMiddleware(stack); err != nil {
		return err
	}
	if err = addTimeOffsetBuild(stack, c); err != nil {
		return err
	}
	if err = addUserAgentRetryMode(stack, options); err != nil {
		return err
	}
	if err = addOpGetMetricDataValidationMiddleware(stack); err != nil {
		return err
	}
	if err = stack.Initialize.Add(newServiceMetadataMiddleware_opGetMetricData(options.Region), middleware.Before); err != nil {
		return err
	}
	if err = addRecursionDetection(stack); 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
	}
	if err = addDisableHTTPSMiddleware(stack, options); err != nil {
		return err
	}
	if err = addSpanInitializeStart(stack); err != nil {
		return err
	}
	if err = addSpanInitializeEnd(stack); err != nil {
		return err
	}
	if err = addSpanBuildRequestStart(stack); err != nil {
		return err
	}
	if err = addSpanBuildRequestEnd(stack); err != nil {
		return err
	}
	return nil
}

// GetMetricDataPaginatorOptions is the paginator options for GetMetricData
type GetMetricDataPaginatorOptions struct {
	// The maximum number of data points the request should return before paginating.
	// If you omit this, the default of 100,800 is used.
	Limit int32

	// Set to true if pagination should stop if the service returns a pagination token
	// that matches the most recent token provided to the service.
	StopOnDuplicateToken bool
}

// GetMetricDataPaginator is a paginator for GetMetricData
type GetMetricDataPaginator struct {
	options   GetMetricDataPaginatorOptions
	client    GetMetricDataAPIClient
	params    *GetMetricDataInput
	nextToken *string
	firstPage bool
}

// NewGetMetricDataPaginator returns a new GetMetricDataPaginator
func NewGetMetricDataPaginator(client GetMetricDataAPIClient, params *GetMetricDataInput, optFns ...func(*GetMetricDataPaginatorOptions)) *GetMetricDataPaginator {
	if params == nil {
		params = &GetMetricDataInput{}
	}

	options := GetMetricDataPaginatorOptions{}
	if params.MaxDatapoints != nil {
		options.Limit = *params.MaxDatapoints
	}

	for _, fn := range optFns {
		fn(&options)
	}

	return &GetMetricDataPaginator{
		options:   options,
		client:    client,
		params:    params,
		firstPage: true,
		nextToken: params.NextToken,
	}
}

// HasMorePages returns a boolean indicating whether more pages are available
func (p *GetMetricDataPaginator) HasMorePages() bool {
	return p.firstPage || (p.nextToken != nil && len(*p.nextToken) != 0)
}

// NextPage retrieves the next GetMetricData page.
func (p *GetMetricDataPaginator) NextPage(ctx context.Context, optFns ...func(*Options)) (*GetMetricDataOutput, error) {
	if !p.HasMorePages() {
		return nil, fmt.Errorf("no more pages available")
	}

	params := *p.params
	params.NextToken = p.nextToken

	var limit *int32
	if p.options.Limit > 0 {
		limit = &p.options.Limit
	}
	params.MaxDatapoints = limit

	optFns = append([]func(*Options){
		addIsPaginatorUserAgent,
	}, optFns...)
	result, err := p.client.GetMetricData(ctx, &params, optFns...)
	if err != nil {
		return nil, err
	}
	p.firstPage = false

	prevToken := p.nextToken
	p.nextToken = result.NextToken

	if p.options.StopOnDuplicateToken &&
		prevToken != nil &&
		p.nextToken != nil &&
		*prevToken == *p.nextToken {
		p.nextToken = nil
	}

	return result, nil
}

// GetMetricDataAPIClient is a client that implements the GetMetricData operation.
type GetMetricDataAPIClient interface {
	GetMetricData(context.Context, *GetMetricDataInput, ...func(*Options)) (*GetMetricDataOutput, error)
}

var _ GetMetricDataAPIClient = (*Client)(nil)

func newServiceMetadataMiddleware_opGetMetricData(region string) *awsmiddleware.RegisterServiceMetadata {
	return &awsmiddleware.RegisterServiceMetadata{
		Region:        region,
		ServiceID:     ServiceID,
		OperationName: "GetMetricData",
	}
}
