///////////////////////////////////////////////////////////////////////////////
// Copyright © 2020 xx network SEZC                                          //
//                                                                           //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file                                                              //
///////////////////////////////////////////////////////////////////////////////

package groupChat

import (
	"github.com/golang/protobuf/proto"
	"github.com/pkg/errors"
	jww "github.com/spf13/jwalterweatherman"
	gs "gitlab.com/elixxir/client/groupChat/groupStore"
	"gitlab.com/elixxir/client/interfaces/message"
	"gitlab.com/elixxir/client/stoppable"
	"gitlab.com/elixxir/crypto/group"
)

// Error message.
const (
	sendMessageTypeErr       = "message not of type GroupCreationRequest"
	protoUnmarshalErr        = "failed to unmarshal request: %+v"
	deserializeMembershipErr = "failed to deserialize membership: %+v"
)

// receiveRequest starts the group request reception worker that waits for new
// group requests to arrive.
func (m Manager) receiveRequest(rawMsgs chan message.Receive, stop *stoppable.Single) {
	jww.DEBUG.Print("Starting group message request reception worker.")

	for {
		select {
		case <-stop.Quit():
			jww.DEBUG.Print("Stopping group message request reception worker.")
			stop.ToStopped()
			return
		case sendMsg := <-rawMsgs:
			jww.DEBUG.Print("Group message request received send message.")

			// Generate the group from the request message
			g, err := m.readRequest(sendMsg)
			if err != nil {
				jww.WARN.Printf("Failed to read message as group request: %+v",
					err)
				continue
			}

			// Call request callback with the new group if it does not already
			// exist
			if _, exists := m.GetGroup(g.ID); !exists {
				go m.requestFunc(g)
			}
		}
	}
}

// readRequest returns the group describes in the group request message. An
// error is returned if the request is of the wrong type or cannot be read.
func (m *Manager) readRequest(msg message.Receive) (gs.Group, error) {
	// Return an error if the message is not of the right type
	if msg.MessageType != message.GroupCreationRequest {
		return gs.Group{}, errors.New(sendMessageTypeErr)
	}

	// Unmarshal the request message
	request := &Request{}
	err := proto.Unmarshal(msg.Payload, request)
	if err != nil {
		return gs.Group{}, errors.Errorf(protoUnmarshalErr, err)
	}

	// Deserialize membership list
	membership, err := group.DeserializeMembership(request.Members)
	if err != nil {
		return gs.Group{}, errors.Errorf(deserializeMembershipErr, err)
	}

	// Get the relationship with the group leader
	partner, err := m.store.E2e().GetPartner(membership[0].ID)
	if err != nil {
		return gs.Group{}, errors.Errorf(getPrivKeyErr, err)
	}

	// Replace leader's public key with the one from the partnership
	leaderPubKey := membership[0].DhKey.DeepCopy()
	membership[0].DhKey = partner.GetPartnerOriginPublicKey()

	// Generate the DH keys with each group member
	privKey := partner.GetMyOriginPrivateKey()
	grp := m.store.E2e().GetGroup()
	dkl := gs.GenerateDhKeyList(m.gs.GetUser().ID, privKey, membership, grp)

	// Restore the original public key for the leader so that the membership
	// digest generated later is correct
	membership[0].DhKey = leaderPubKey

	// Copy preimages
	var idPreimage group.IdPreimage
	copy(idPreimage[:], request.IdPreimage)
	var keyPreimage group.KeyPreimage
	copy(keyPreimage[:], request.KeyPreimage)

	// Create group ID and key
	groupID := group.NewID(idPreimage, membership)
	groupKey := group.NewKey(keyPreimage, membership)

	// Return the new group
	return gs.NewGroup(request.Name, groupID, groupKey, idPreimage, keyPreimage,
		request.Message, membership, dkl), nil
}