package newrelic

import "net/http"

// SegmentStartTime is created by Transaction.StartSegmentNow and marks the
// beginning of a segment.  A segment with a zero-valued SegmentStartTime may
// safely be ended.
type SegmentStartTime struct{ segment }

// Segment is used to instrument functions, methods, and blocks of code.  The
// easiest way use Segment is the StartSegment function.
type Segment struct {
	StartTime SegmentStartTime
	Name      string
}

// DatastoreSegment is used to instrument calls to databases and object stores.
// Here is an example:
//
// 	ds := &newrelic.DatastoreSegment{
// 		StartTime:  newrelic.StartSegmentNow(txn),
// 		Product:    newrelic.DatastoreMySQL,
// 		Collection: "my_table",
// 		Operation:  "SELECT",
// 	}
// 	defer ds.End()
//
type DatastoreSegment struct {
	StartTime SegmentStartTime
	// Product is the datastore type.  See the constants in datastore.go.
	Product DatastoreProduct
	// Collection is the table or group.
	Collection string
	// Operation is the relevant action, e.g. "SELECT" or "GET".
	Operation string
	// ParameterizedQuery may be set to the query being performed.  It must
	// not contain any raw parameters, only placeholders.
	ParameterizedQuery string
	// QueryParameters may be used to provide query parameters.  Care should
	// be taken to only provide parameters which are not sensitive.
	// QueryParameters are ignored in high security mode.
	QueryParameters map[string]interface{}
	// Host is the name of the server hosting the datastore.
	Host string
	// PortPathOrID can represent either the port, path, or id of the
	// datastore being connected to.
	PortPathOrID string
	// DatabaseName is name of database where the current query is being
	// executed.
	DatabaseName string
}

// ExternalSegment is used to instrument external calls.  StartExternalSegment
// is recommended when you have access to an http.Request.
type ExternalSegment struct {
	StartTime SegmentStartTime
	Request   *http.Request
	Response  *http.Response
	// If you do not have access to the request, this URL field should be
	// used to indicate the endpoint.  NOTE: If non-empty, this field
	// is parsed using url.Parse and therefore it MUST include the protocol
	// (eg. "http://").
	URL string
}

// End finishes the segment.
func (s *Segment) End() error { return endSegment(s) }

// End finishes the datastore segment.
func (s *DatastoreSegment) End() error { return endDatastore(s) }

// End finishes the external segment.
func (s *ExternalSegment) End() error { return endExternal(s) }

// OutboundHeaders returns the headers that should be attached to the external
// request.
func (s *ExternalSegment) OutboundHeaders() http.Header {
	return outboundHeaders(s)
}

// StartSegmentNow helps avoid Transaction nil checks.
func StartSegmentNow(txn Transaction) SegmentStartTime {
	if nil != txn {
		return txn.StartSegmentNow()
	}
	return SegmentStartTime{}
}

// StartSegment makes it easy to instrument segments.  To time a function, do
// the following:
//
//	func timeMe(txn newrelic.Transaction) {
//		defer newrelic.StartSegment(txn, "timeMe").End()
//		// ... function code here ...
//	}
//
// To time a block of code, do the following:
//
//	segment := StartSegment(txn, "myBlock")
//	// ... code you want to time here ...
//	segment.End()
//
func StartSegment(txn Transaction, name string) *Segment {
	return &Segment{
		StartTime: StartSegmentNow(txn),
		Name:      name,
	}
}

// StartExternalSegment makes it easier to instrument external calls.
//
//    segment := newrelic.StartExternalSegment(txn, request)
//    resp, err := client.Do(request)
//    segment.Response = resp
//    segment.End()
//
// In addition to starting an external segment, StartExternalSegment also adds
// distributed tracing headers to the request.  Therefore, it is recommended
// over populating ExternalSegment structs manually.
//
// If the Transaction parameter is nil, StartExternalSegment will look for a
// Transaction in the request's context using FromContext.  Example:
//
//    request = newrelic.RequestWithTransactionContext(request, txn)
//    segment := newrelic.StartExternalSegment(nil, request)
//    resp, err := client.Do(request)
//    segment.Response = resp
//    segment.End()
//
func StartExternalSegment(txn Transaction, request *http.Request) *ExternalSegment {
	if nil == txn {
		txn = transactionFromRequestContext(request)
	}
	s := &ExternalSegment{
		StartTime: StartSegmentNow(txn),
		Request:   request,
	}

	if request != nil && request.Header != nil {
		for key, values := range s.OutboundHeaders() {
			for _, value := range values {
				request.Header.Add(key, value)
			}
		}
	}

	return s
}
