package protocol

import (
	"bytes"
	"github.com/golang/protobuf/proto"
	"encoding/binary"
	"fmt"
	. "github.com/Philipp15b/go-steam/protocol/steamlang"
)

// TODO: Headers are always deserialized twice.

// Represents an incoming, partially unread message.
type Packet struct {
	EMsg        EMsg
	IsProto     bool
	TargetJobId JobId
	SourceJobId JobId
	Data        []byte
}

func NewPacket(data []byte) (*Packet, error) {
	var rawEMsg uint32
	err := binary.Read(bytes.NewReader(data), binary.LittleEndian, &rawEMsg)
	if err != nil {
		return nil, err
	}
	eMsg := NewEMsg(rawEMsg)
	buf := bytes.NewReader(data)
	if eMsg == EMsg_ChannelEncryptRequest || eMsg == EMsg_ChannelEncryptResult {
		header := NewMsgHdr()
		header.Msg = eMsg
		err = header.Deserialize(buf)
		if err != nil {
			return nil, err
		}
		return &Packet{
			EMsg:        eMsg,
			IsProto:     false,
			TargetJobId: JobId(header.TargetJobID),
			SourceJobId: JobId(header.SourceJobID),
			Data:        data,
		}, nil
	} else if IsProto(rawEMsg) {
		header := NewMsgHdrProtoBuf()
		header.Msg = eMsg
		err = header.Deserialize(buf)
		if err != nil {
			return nil, err
		}
		return &Packet{
			EMsg:        eMsg,
			IsProto:     true,
			TargetJobId: JobId(header.Proto.GetJobidTarget()),
			SourceJobId: JobId(header.Proto.GetJobidSource()),
			Data:        data,
		}, nil
	} else {
		header := NewExtendedClientMsgHdr()
		header.Msg = eMsg
		err = header.Deserialize(buf)
		if err != nil {
			return nil, err
		}
		return &Packet{
			EMsg:        eMsg,
			IsProto:     false,
			TargetJobId: JobId(header.TargetJobID),
			SourceJobId: JobId(header.SourceJobID),
			Data:        data,
		}, nil
	}
}

func (p *Packet) String() string {
	return fmt.Sprintf("Packet{EMsg = %v, Proto = %v, Len = %v, TargetJobId = %v, SourceJobId = %v}", p.EMsg, p.IsProto, len(p.Data), p.TargetJobId, p.SourceJobId)
}

func (p *Packet) ReadProtoMsg(body proto.Message) *ClientMsgProtobuf {
	header := NewMsgHdrProtoBuf()
	buf := bytes.NewBuffer(p.Data)
	header.Deserialize(buf)
	proto.Unmarshal(buf.Bytes(), body)
	return &ClientMsgProtobuf{ // protobuf messages have no payload
		Header: header,
		Body:   body,
	}
}

func (p *Packet) ReadClientMsg(body MessageBody) *ClientMsg {
	header := NewExtendedClientMsgHdr()
	buf := bytes.NewReader(p.Data)
	header.Deserialize(buf)
	body.Deserialize(buf)
	payload := make([]byte, buf.Len())
	buf.Read(payload)
	return &ClientMsg{
		Header:  header,
		Body:    body,
		Payload: payload,
	}
}

func (p *Packet) ReadMsg(body MessageBody) *Msg {
	header := NewMsgHdr()
	buf := bytes.NewReader(p.Data)
	header.Deserialize(buf)
	body.Deserialize(buf)
	payload := make([]byte, buf.Len())
	buf.Read(payload)
	return &Msg{
		Header:  header,
		Body:    body,
		Payload: payload,
	}
}