package twirp

import "golang.org/x/net/context"

// ServerHooks is a container for callbacks that can instrument a
// Twirp-generated server. These callbacks all accept a context and
// return a context. They can use this to mutate the context as it
// threads through the system, appending values or deadlines to it.
type ServerHooks struct {
	// RequestReceived is called as soon as a request enters the twirp
	// server at the earliest available moment.
	RequestReceived func(context.Context) context.Context

	// RequestRouted is called when a request has been routed to a
	// particular method of the twirp server.
	RequestRouted func(context.Context) context.Context

	// ResponsePrepared is called when a request has been handled and a
	// response is ready to be sent to the client.
	ResponsePrepared func(context.Context) context.Context

	// ResponseSent is called when all bytes of a response have been
	// written.
	ResponseSent func(context.Context) context.Context

	// Error hook is called when a request responds with an Error,
	// either by the service implementation or by twirp itself.
	// The Error is passed as argument to the hook.
	Error func(context.Context, Error) context.Context
}

// NewServerHooks initializes a ServerHooks with no-op callbacks. They
// pass through their context unchanged.
func NewServerHooks() *ServerHooks {
	return &ServerHooks{
		RequestReceived: func(ctx context.Context) context.Context {
			return ctx
		},
		RequestRouted: func(ctx context.Context) context.Context {
			return ctx
		},
		ResponsePrepared: func(ctx context.Context) context.Context {
			return ctx
		},
		ResponseSent: func(ctx context.Context) context.Context {
			return ctx
		},
		Error: func(ctx context.Context, _ Error) context.Context {
			return ctx
		},
	}
}

// ChainHooks creates a new *ServerHooks which chains the callbacks in
// each of the constituent hooks passed in. Each hook function will be
// called in the order of the ServerHooks values passed in.
func ChainHooks(hooks ...*ServerHooks) *ServerHooks {
	if len(hooks) == 0 {
		return NewServerHooks()
	}
	if len(hooks) == 1 {
		return hooks[0]
	}
	return &ServerHooks{
		RequestReceived: func(ctx context.Context) context.Context {
			for _, h := range hooks {
				if h.RequestReceived != nil {
					ctx = h.RequestReceived(ctx)
				}
			}
			return ctx
		},
		RequestRouted: func(ctx context.Context) context.Context {
			for _, h := range hooks {
				if h.RequestRouted != nil {
					ctx = h.RequestRouted(ctx)
				}
			}
			return ctx
		},
		ResponsePrepared: func(ctx context.Context) context.Context {
			for _, h := range hooks {
				if h.ResponsePrepared != nil {
					ctx = h.ResponsePrepared(ctx)
				}
			}
			return ctx
		},
		ResponseSent: func(ctx context.Context) context.Context {
			for _, h := range hooks {
				if h.ResponseSent != nil {
					ctx = h.ResponseSent(ctx)
				}
			}
			return ctx
		},
		Error: func(ctx context.Context, twerr Error) context.Context {
			for _, h := range hooks {
				if h.Error != nil {
					ctx = h.Error(ctx, twerr)
				}
			}
			return ctx
		},
	}
}
