// Copyright 2015 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package expfmt

import (
	
	
	
	
	
	

	dto 
	

	
)

// Decoder types decode an input stream into metric families.
type Decoder interface {
	Decode(*dto.MetricFamily) error
}

// DecodeOptions contains options used by the Decoder and in sample extraction.
type DecodeOptions struct {
	// Timestamp is added to each value from the stream that has no explicit timestamp set.
	Timestamp model.Time
}

// ResponseFormat extracts the correct format from a HTTP response header.
// If no matching format can be found FormatUnknown is returned.
func ( http.Header) Format {
	 := .Get(hdrContentType)

	, ,  := mime.ParseMediaType()
	if  != nil {
		return FmtUnknown
	}

	const  = "text/plain"

	switch  {
	case ProtoType:
		if ,  := ["proto"];  &&  != ProtoProtocol {
			return FmtUnknown
		}
		if ,  := ["encoding"];  &&  != "delimited" {
			return FmtUnknown
		}
		return FmtProtoDelim

	case :
		if ,  := ["version"];  &&  != TextVersion {
			return FmtUnknown
		}
		return FmtText
	}

	return FmtUnknown
}

// NewDecoder returns a new decoder based on the given input format.
// If the input format does not imply otherwise, a text format decoder is returned.
func ( io.Reader,  Format) Decoder {
	switch .FormatType() {
	case TypeProtoDelim:
		return &protoDecoder{r: bufio.NewReader()}
	}
	return &textDecoder{r: }
}

// protoDecoder implements the Decoder interface for protocol buffers.
type protoDecoder struct {
	r protodelim.Reader
}

// Decode implements the Decoder interface.
func ( *protoDecoder) ( *dto.MetricFamily) error {
	 := protodelim.UnmarshalOptions{
		MaxSize: -1,
	}
	if  := .UnmarshalFrom(.r, );  != nil {
		return 
	}
	if !model.IsValidMetricName(model.LabelValue(.GetName())) {
		return fmt.Errorf("invalid metric name %q", .GetName())
	}
	for ,  := range .GetMetric() {
		if  == nil {
			continue
		}
		for ,  := range .GetLabel() {
			if  == nil {
				continue
			}
			if !model.LabelValue(.GetValue()).IsValid() {
				return fmt.Errorf("invalid label value %q", .GetValue())
			}
			if !model.LabelName(.GetName()).IsValid() {
				return fmt.Errorf("invalid label name %q", .GetName())
			}
		}
	}
	return nil
}

// textDecoder implements the Decoder interface for the text protocol.
type textDecoder struct {
	r    io.Reader
	fams map[string]*dto.MetricFamily
	err  error
}

// Decode implements the Decoder interface.
func ( *textDecoder) ( *dto.MetricFamily) error {
	if .err == nil {
		// Read all metrics in one shot.
		var  TextParser
		.fams, .err = .TextToMetricFamilies(.r)
		// If we don't get an error, store io.EOF for the end.
		if .err == nil {
			.err = io.EOF
		}
	}
	// Pick off one MetricFamily per Decode until there's nothing left.
	for ,  := range .fams {
		.Name = .Name
		.Help = .Help
		.Type = .Type
		.Metric = .Metric
		delete(.fams, )
		return nil
	}
	return .err
}

// SampleDecoder wraps a Decoder to extract samples from the metric families
// decoded by the wrapped Decoder.
type SampleDecoder struct {
	Dec  Decoder
	Opts *DecodeOptions

	f dto.MetricFamily
}

// Decode calls the Decode method of the wrapped Decoder and then extracts the
// samples from the decoded MetricFamily into the provided model.Vector.
func ( *SampleDecoder) ( *model.Vector) error {
	 := .Dec.Decode(&.f)
	if  != nil {
		return 
	}
	*,  = extractSamples(&.f, .Opts)
	return 
}

// ExtractSamples builds a slice of samples from the provided metric
// families. If an error occurs during sample extraction, it continues to
// extract from the remaining metric families. The returned error is the last
// error that has occurred.
func ( *DecodeOptions,  ...*dto.MetricFamily) (model.Vector, error) {
	var (
		     model.Vector
		 error
	)
	for ,  := range  {
		,  := extractSamples(, )
		if  != nil {
			 = 
			continue
		}
		 = append(, ...)
	}
	return , 
}

func extractSamples( *dto.MetricFamily,  *DecodeOptions) (model.Vector, error) {
	switch .GetType() {
	case dto.MetricType_COUNTER:
		return extractCounter(, ), nil
	case dto.MetricType_GAUGE:
		return extractGauge(, ), nil
	case dto.MetricType_SUMMARY:
		return extractSummary(, ), nil
	case dto.MetricType_UNTYPED:
		return extractUntyped(, ), nil
	case dto.MetricType_HISTOGRAM:
		return extractHistogram(, ), nil
	}
	return nil, fmt.Errorf("expfmt.extractSamples: unknown metric family type %v", .GetType())
}

func extractCounter( *DecodeOptions,  *dto.MetricFamily) model.Vector {
	 := make(model.Vector, 0, len(.Metric))

	for ,  := range .Metric {
		if .Counter == nil {
			continue
		}

		 := make(model.LabelSet, len(.Label)+1)
		for ,  := range .Label {
			[model.LabelName(.GetName())] = model.LabelValue(.GetValue())
		}
		[model.MetricNameLabel] = model.LabelValue(.GetName())

		 := &model.Sample{
			Metric: model.Metric(),
			Value:  model.SampleValue(.Counter.GetValue()),
		}

		if .TimestampMs != nil {
			.Timestamp = model.TimeFromUnixNano(*.TimestampMs * 1000000)
		} else {
			.Timestamp = .Timestamp
		}

		 = append(, )
	}

	return 
}

func extractGauge( *DecodeOptions,  *dto.MetricFamily) model.Vector {
	 := make(model.Vector, 0, len(.Metric))

	for ,  := range .Metric {
		if .Gauge == nil {
			continue
		}

		 := make(model.LabelSet, len(.Label)+1)
		for ,  := range .Label {
			[model.LabelName(.GetName())] = model.LabelValue(.GetValue())
		}
		[model.MetricNameLabel] = model.LabelValue(.GetName())

		 := &model.Sample{
			Metric: model.Metric(),
			Value:  model.SampleValue(.Gauge.GetValue()),
		}

		if .TimestampMs != nil {
			.Timestamp = model.TimeFromUnixNano(*.TimestampMs * 1000000)
		} else {
			.Timestamp = .Timestamp
		}

		 = append(, )
	}

	return 
}

func extractUntyped( *DecodeOptions,  *dto.MetricFamily) model.Vector {
	 := make(model.Vector, 0, len(.Metric))

	for ,  := range .Metric {
		if .Untyped == nil {
			continue
		}

		 := make(model.LabelSet, len(.Label)+1)
		for ,  := range .Label {
			[model.LabelName(.GetName())] = model.LabelValue(.GetValue())
		}
		[model.MetricNameLabel] = model.LabelValue(.GetName())

		 := &model.Sample{
			Metric: model.Metric(),
			Value:  model.SampleValue(.Untyped.GetValue()),
		}

		if .TimestampMs != nil {
			.Timestamp = model.TimeFromUnixNano(*.TimestampMs * 1000000)
		} else {
			.Timestamp = .Timestamp
		}

		 = append(, )
	}

	return 
}

func extractSummary( *DecodeOptions,  *dto.MetricFamily) model.Vector {
	 := make(model.Vector, 0, len(.Metric))

	for ,  := range .Metric {
		if .Summary == nil {
			continue
		}

		 := .Timestamp
		if .TimestampMs != nil {
			 = model.TimeFromUnixNano(*.TimestampMs * 1000000)
		}

		for ,  := range .Summary.Quantile {
			 := make(model.LabelSet, len(.Label)+2)
			for ,  := range .Label {
				[model.LabelName(.GetName())] = model.LabelValue(.GetValue())
			}
			// BUG(matt): Update other names to "quantile".
			[model.LabelName(model.QuantileLabel)] = model.LabelValue(fmt.Sprint(.GetQuantile()))
			[model.MetricNameLabel] = model.LabelValue(.GetName())

			 = append(, &model.Sample{
				Metric:    model.Metric(),
				Value:     model.SampleValue(.GetValue()),
				Timestamp: ,
			})
		}

		 := make(model.LabelSet, len(.Label)+1)
		for ,  := range .Label {
			[model.LabelName(.GetName())] = model.LabelValue(.GetValue())
		}
		[model.MetricNameLabel] = model.LabelValue(.GetName() + "_sum")

		 = append(, &model.Sample{
			Metric:    model.Metric(),
			Value:     model.SampleValue(.Summary.GetSampleSum()),
			Timestamp: ,
		})

		 = make(model.LabelSet, len(.Label)+1)
		for ,  := range .Label {
			[model.LabelName(.GetName())] = model.LabelValue(.GetValue())
		}
		[model.MetricNameLabel] = model.LabelValue(.GetName() + "_count")

		 = append(, &model.Sample{
			Metric:    model.Metric(),
			Value:     model.SampleValue(.Summary.GetSampleCount()),
			Timestamp: ,
		})
	}

	return 
}

func extractHistogram( *DecodeOptions,  *dto.MetricFamily) model.Vector {
	 := make(model.Vector, 0, len(.Metric))

	for ,  := range .Metric {
		if .Histogram == nil {
			continue
		}

		 := .Timestamp
		if .TimestampMs != nil {
			 = model.TimeFromUnixNano(*.TimestampMs * 1000000)
		}

		 := false

		for ,  := range .Histogram.Bucket {
			 := make(model.LabelSet, len(.Label)+2)
			for ,  := range .Label {
				[model.LabelName(.GetName())] = model.LabelValue(.GetValue())
			}
			[model.LabelName(model.BucketLabel)] = model.LabelValue(fmt.Sprint(.GetUpperBound()))
			[model.MetricNameLabel] = model.LabelValue(.GetName() + "_bucket")

			if math.IsInf(.GetUpperBound(), +1) {
				 = true
			}

			 = append(, &model.Sample{
				Metric:    model.Metric(),
				Value:     model.SampleValue(.GetCumulativeCount()),
				Timestamp: ,
			})
		}

		 := make(model.LabelSet, len(.Label)+1)
		for ,  := range .Label {
			[model.LabelName(.GetName())] = model.LabelValue(.GetValue())
		}
		[model.MetricNameLabel] = model.LabelValue(.GetName() + "_sum")

		 = append(, &model.Sample{
			Metric:    model.Metric(),
			Value:     model.SampleValue(.Histogram.GetSampleSum()),
			Timestamp: ,
		})

		 = make(model.LabelSet, len(.Label)+1)
		for ,  := range .Label {
			[model.LabelName(.GetName())] = model.LabelValue(.GetValue())
		}
		[model.MetricNameLabel] = model.LabelValue(.GetName() + "_count")

		 := &model.Sample{
			Metric:    model.Metric(),
			Value:     model.SampleValue(.Histogram.GetSampleCount()),
			Timestamp: ,
		}
		 = append(, )

		if ! {
			// Append an infinity bucket sample.
			 := make(model.LabelSet, len(.Label)+2)
			for ,  := range .Label {
				[model.LabelName(.GetName())] = model.LabelValue(.GetValue())
			}
			[model.LabelName(model.BucketLabel)] = model.LabelValue("+Inf")
			[model.MetricNameLabel] = model.LabelValue(.GetName() + "_bucket")

			 = append(, &model.Sample{
				Metric:    model.Metric(),
				Value:     .Value,
				Timestamp: ,
			})
		}
	}

	return 
}