package service

import (
	crand "crypto/rand"

	msg "github.com/jbenet/go-ipfs/net/message"
	pb "github.com/jbenet/go-ipfs/net/service/internal/pb"
	peer "github.com/jbenet/go-ipfs/peer"

	proto "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto"
)

const (
	// IDSize is the size of the ID in bytes.
	IDSize int = 4
)

// RequestID is a field that identifies request-response flows.
type RequestID []byte

// Request turns a RequestID into a Request (unsetting first bit)
func (r RequestID) Request() RequestID {
	if r == nil {
		return nil
	}
	r2 := make([]byte, len(r))
	copy(r2, r)
	r2[0] = r[0] & 0x7F // unset first bit for request
	return RequestID(r2)
}

// Response turns a RequestID into a Response (setting first bit)
func (r RequestID) Response() RequestID {
	if r == nil {
		return nil
	}
	r2 := make([]byte, len(r))
	copy(r2, r)
	r2[0] = r[0] | 0x80 // set first bit for response
	return RequestID(r2)
}

// IsRequest returns whether a RequestID identifies a request
func (r RequestID) IsRequest() bool {
	if r == nil {
		return false
	}
	return !r.IsResponse()
}

// IsResponse returns whether a RequestID identifies a response
func (r RequestID) IsResponse() bool {
	if r == nil {
		return false
	}
	return bool(r[0]&0x80 == 0x80)
}

// RandomRequestID creates and returns a new random request ID
func RandomRequestID() (RequestID, error) {
	buf := make([]byte, IDSize)
	_, err := crand.Read(buf)
	return RequestID(buf).Request(), err
}

// RequestMap is a map of Requests. the key = (peer.ID concat RequestID).
type RequestMap map[string]*Request

// Request objects are used to multiplex request-response flows.
type Request struct {

	// ID is the RequestID identifying this Request-Response Flow.
	ID RequestID

	// PeerID identifies the peer from whom to expect the response.
	PeerID peer.ID

	// Response is the channel of incoming responses.
	Response chan msg.NetMessage
}

// NewRequest creates a request for given peer.ID
func NewRequest(pid peer.ID) (*Request, error) {
	id, err := RandomRequestID()
	if err != nil {
		return nil, err
	}

	return &Request{
		ID:       id,
		PeerID:   pid,
		Response: make(chan msg.NetMessage, 1),
	}, nil
}

// Key returns the RequestKey for this request. Use with maps.
func (r *Request) Key() string {
	return RequestKey(r.PeerID, r.ID)
}

// RequestKey is the peer.ID concatenated with the RequestID. Use with maps.
func RequestKey(pid peer.ID, rid RequestID) string {
	return string(pid) + string(rid.Request()[:])
}

func wrapData(data []byte, rid RequestID) ([]byte, error) {
	// Marshal
	pbm := new(pb.PBRequest)
	pbm.Data = data
	pbm.Tag = rid
	b, err := proto.Marshal(pbm)
	if err != nil {
		return nil, err
	}

	return b, nil
}

func unwrapData(data []byte) ([]byte, RequestID, error) {
	// Unmarshal
	pbm := new(pb.PBRequest)
	err := proto.Unmarshal(data, pbm)
	if err != nil {
		return nil, nil, err
	}

	return pbm.GetData(), pbm.GetTag(), nil
}
