/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package nmp

import (
	"encoding/binary"
	"encoding/hex"
	"fmt"

	log "github.com/sirupsen/logrus"
	"github.com/ugorji/go/codec"

	"mynewt.apache.org/newtmgr/nmxact/nmxutil"
)

const NMP_HDR_SIZE = 8

type NmpHdr struct {
	Op    uint8 /* 3 bits of opcode */
	Flags uint8
	Len   uint16
	Group uint16
	Seq   uint8
	Id    uint8
}

type NmpMsg struct {
	Hdr  NmpHdr
	Body interface{}
}

// Combine req + rsp.
type NmpReq interface {
	Hdr() *NmpHdr
	SetHdr(hdr *NmpHdr)

	Msg() *NmpMsg
}

type NmpRsp interface {
	Hdr() *NmpHdr
	SetHdr(msg *NmpHdr)

	Msg() *NmpMsg
}

type NmpBase struct {
	hdr NmpHdr `codec:"-"`
}

func (b *NmpBase) Hdr() *NmpHdr {
	return &b.hdr
}

func (b *NmpBase) SetHdr(h *NmpHdr) {
	b.hdr = *h
}

func MsgFromReq(r NmpReq) *NmpMsg {
	return &NmpMsg{
		*r.Hdr(),
		r,
	}
}

func NewNmpMsg() *NmpMsg {
	return &NmpMsg{}
}

func DecodeNmpHdr(data []byte) (*NmpHdr, error) {
	if len(data) < NMP_HDR_SIZE {
		return nil, fmt.Errorf(
			"Newtmgr request buffer too small %d bytes", len(data))
	}

	hdr := &NmpHdr{}

	hdr.Op = uint8(data[0])
	hdr.Flags = uint8(data[1])
	hdr.Len = binary.BigEndian.Uint16(data[2:4])
	hdr.Group = binary.BigEndian.Uint16(data[4:6])
	hdr.Seq = uint8(data[6])
	hdr.Id = uint8(data[7])

	return hdr, nil
}

func (hdr *NmpHdr) Bytes() []byte {
	buf := make([]byte, 0, NMP_HDR_SIZE)

	buf = append(buf, byte(hdr.Op))
	buf = append(buf, byte(hdr.Flags))

	u16b := make([]byte, 2)
	binary.BigEndian.PutUint16(u16b, hdr.Len)
	buf = append(buf, u16b...)

	binary.BigEndian.PutUint16(u16b, hdr.Group)
	buf = append(buf, u16b...)

	buf = append(buf, byte(hdr.Seq))
	buf = append(buf, byte(hdr.Id))

	return buf
}

func BodyBytes(body interface{}) ([]byte, error) {
	data := make([]byte, 0)

	enc := codec.NewEncoderBytes(&data, new(codec.CborHandle))
	if err := enc.Encode(body); err != nil {
		return nil, fmt.Errorf("Failed to encode message %s", err.Error())
	}

	log.Debugf("Encoded %+v to:\n%s", body, hex.Dump(data))

	return data, nil
}

func EncodeNmpPlain(nmr *NmpMsg) ([]byte, error) {
	bb, err := BodyBytes(nmr.Body)
	if err != nil {
		return nil, err
	}

	nmr.Hdr.Len = uint16(len(bb))

	hb := nmr.Hdr.Bytes()
	data := append(hb, bb...)

	log.Debugf("Encoded:\n%s", hex.Dump(data))

	return data, nil
}

func fillNmpReqWithSeq(req NmpReq, op uint8, group uint16, id uint8, seq uint8) {
	hdr := NmpHdr{
		Op:    op,
		Flags: 0,
		Len:   0,
		Group: group,
		Seq:   seq,
		Id:    id,
	}

	req.SetHdr(&hdr)
}

func fillNmpReq(req NmpReq, op uint8, group uint16, id uint8) {
	fillNmpReqWithSeq(req, op, group, id, nmxutil.NextNmpSeq())
}
