/*
 Copyright (c) Facebook, Inc. and its affiliates.

 This source code is licensed under the MIT license found in the
 LICENSE file in the root directory of this source tree.
*/

package tacquito

import (
	"context"
)

// Writer is an abstraction used for adding Writers to the response object
type Writer interface {
	Write(ctx context.Context, p []byte) (int, error)
}

// response implements the Response interface.  when testing handlers, provide your own
// mock of this struct via the interface. crypt operations are not exposed for testing.
type response struct {
	loggerProvider
	ctx     context.Context
	crypter *crypter
	next    Handler
	// header is the corresponding header that was used to create this response
	header Header
	// slice of writers to write back the response
	writers []Writer
}

// Reply will write the provided EncoderDecoder to the underlying net.Conn.  This method handles
// all header values based on the underlying EncoderDecoder.  If you want total control on the
// packet that is written, use Send instead.
func (r *response) Reply(v EncoderDecoder) (int, error) {
	seqNo := int(r.header.SeqNo)
	// some special conditions for different body types
	switch t := v.(type) {
	case *AuthenReply:
		if t.Status == AuthenStatusRestart {
			seqNo = 1
		} else {
			seqNo++
		}
	default:
		seqNo++
	}
	header := NewHeader(
		SetHeaderVersion(r.header.Version),
		SetHeaderType(r.header.Type),
		SetHeaderSeqNo(seqNo),
		SetHeaderFlag(r.header.Flags),
		SetHeaderSessionID(r.header.SessionID),
	)
	b, err := v.MarshalBinary()
	if err != nil {
		r.Errorf(r.ctx, "unable to marshal packet; %v", err)
		return 0, err
	}
	r.header = *header
	p := NewPacket(
		SetPacketHeader(header),
		SetPacketBody(b),
	)
	if pbytes, err := p.MarshalBinary(); err == nil {
		for _, mw := range r.writers {
			_, err := mw.Write(r.ctx, pbytes)
			if err != nil {
				r.Errorf(r.ctx, "unable to write to response writer; %v", err)
			}
		}
	}
	return r.Write(p)
}

// Write will write the packet to the underlying net.Conn.  If you are expecting another packet
// to return from the client after writing a response, call Next(handler) to provide a next Handler.
func (r *response) Write(p *Packet) (int, error) {
	return r.crypter.write(p)
}

// Next sets the incoming handler to next. This is only used for exchange sequences within the authenticate
// packet types
func (r *response) Next(next Handler) {
	r.next = next
}

func (r *response) RegisterWriter(mw Writer) {
	r.writers = append(r.writers, mw)
}

func (r *response) Context(ctx context.Context) {
	r.ctx = ctx
}

// Response controls what we send back to the client.  Calls to Write should be considered final on the
// packet back to the client.  You may not call Exchange after Write.
type Response interface {
	Reply(v EncoderDecoder) (int, error)
	Write(p *Packet) (int, error)
	Next(next Handler)
	RegisterWriter(Writer)
	// Context sets context of response to ctx
	Context(ctx context.Context)
}

// Request provides access to the config for this net.Conn and also the packet itself
type Request struct {
	Header  Header
	Body    []byte
	Context context.Context
}

// Fields will extract all fields from any packet type and attempt to include any optional
// ContextKey values
func (r Request) Fields(keys ...ContextKey) map[string]string {
	allFields := r.Header.Fields()

	// add optional context values
	if r.Context != nil {
		for _, key := range keys {
			v, ok := r.Context.Value(key).(string)
			if ok {
				allFields[string(key)] = v
			}
		}
	}

	// merge will add our header fields to the body
	// the rfc doesn't contain fields that collide
	merge := func(a, b map[string]string) {
		for k, v := range b {
			a[k] = v
		}
	}
	switch r.Header.Type {
	case Authenticate:
		var as AuthenStart
		if err := Unmarshal(r.Body, &as); err == nil {
			merge(allFields, as.Fields())
			return allFields
		}
		var ac AuthenContinue
		if err := Unmarshal(r.Body, &ac); err == nil {
			merge(allFields, ac.Fields())
			return allFields
		}
		var ar AuthenReply
		if err := Unmarshal(r.Body, &ar); err == nil {
			merge(allFields, ar.Fields())
			return allFields
		}

	case Authorize:
		var ar AuthorRequest
		if err := Unmarshal(r.Body, &ar); err == nil {
			merge(allFields, ar.Fields())
			return allFields
		}
		var arr AuthorReply
		if err := Unmarshal(r.Body, &arr); err == nil {
			merge(allFields, arr.Fields())
			return allFields
		}

	case Accounting:
		var ar AcctRequest
		if err := Unmarshal(r.Body, &ar); err == nil {
			merge(allFields, ar.Fields())
			return allFields
		}
		var arr AcctReply
		if err := Unmarshal(r.Body, &arr); err == nil {
			merge(allFields, arr.Fields())
			return allFields
		}
	}
	// unknown packet
	return nil
}

// Handler form the basis for the state machine during client server exchanges.
type Handler interface {
	Handle(response Response, request Request)
}

// HandlerFunc is an adapter that allows higher order functions to be used as Handler interfaces
type HandlerFunc func(response Response, request Request)

// Handle satisfies the Handler interface
func (h HandlerFunc) Handle(response Response, request Request) {
	h(response, request)
}