package tpro

import (
	"encoding/binary"
	"errors"
	"io"
)

const (
	// PTHeartBeat 心跳包
	PTHeartBeat PackType = iota
	// PTRequest 请求包
	PTRequest
	// PTResponse
	PTResponse
	// PTPush
	PTPush

	ProtoMagic byte = 0xdd

	PortoCompressed = 1

	MagicPos        = 0
	TypePos         = 1
	CompressPos     = 1
	TPSeqIdLength   = 4
	TPProtoIdLength = 2
	MaxBodyLength   = 0xffff
	MinLength       = 2
	MTLength        = 2  // magic+type的长度
	TPMsgLength     = 2  // 协议中纪录Msg长度的值的长度
	MaxHeadLength   = 10 // 最大头部长度
	TPMagicLength   = 1  // 协议中magic长度
	TPTypeLength    = 1
)

var (
	ErrWrongPackMagic   = errors.New("0xdd proto:package magic header error")
	ErrWrongPackType    = errors.New("0xdd proto:package type error")
	ErrPackTooShort     = errors.New("0xdd proto:pack length is too short")
	ErrHeadTooShort     = errors.New("0xdd proto:header length is too short")
	ErrBodyTooLong      = errors.New("0xdd proto:body length is too long")
	ErrWrongBodyLength  = errors.New("0xdd proto:bodyLength is not find")
	ErrWrongMsgLength   = errors.New("0xdd proto:msg does not match expectations")
	ErrWrongProtoFormat = errors.New("0xdd proto:proto format is error")
)

var bytePool *BytePool

// PackType 包类型
type PackType int

// ---------

func init() {
	// 初始化字节对象池
	bytePool = NewBytePool(10, 67000, 2)
}

// ------------
// Header
type Header struct {
	Type         PackType
	IsCompressed bool
	SeqId        uint32
	ProtoID      uint16
	endPos       int // 实际头部大小
}

// EncodeToBuf 头部编码
func (h *Header) EncodeToBuf() ([]byte, error) {
	var buf = make([]byte, MaxHeadLength) // 头部最大不超过10
	buf[MagicPos] = ProtoMagic
	h.EncodePackageType(buf, TypePos, h.Type)
	h.EncodeCompressed(buf, CompressPos, h.IsCompressed)
	h.endPos = MinLength
	switch h.Type {
	case PTResponse:
		binary.BigEndian.PutUint32(buf[h.endPos:], h.SeqId)
		h.endPos += TPSeqIdLength
	case PTPush:
		binary.BigEndian.PutUint16(buf[h.endPos:], h.ProtoID)
		h.endPos += TPProtoIdLength
	case PTRequest:
		binary.BigEndian.PutUint32(buf[h.endPos:], h.SeqId)
		h.endPos += TPSeqIdLength
		binary.BigEndian.PutUint16(buf[h.endPos:], h.ProtoID)
		h.endPos += TPProtoIdLength
	case PTHeartBeat: // 心跳包没有啥需要操作的

	default: // 其实不可能出现这里的情况
		return nil, ErrWrongPackType
	}
	buf = buf[:h.endPos]
	return buf, nil
}

// Decode 解析头部
func (h *Header) Decode(buf []byte, bufLen int) error {
	if bufLen < MinLength {
		return ErrHeadTooShort
	}
	if buf[MagicPos] != ProtoMagic { // 判断是否是0xdd协议
		return ErrWrongPackMagic
	}
	h.DecodePackageType(buf, TypePos) // 设置类型
	h.endPos = MinLength
	switch h.Type {
	case PTPush:
		if bufLen >= MinLength+TPProtoIdLength { // 推送包头至少有4字节
			h.ProtoID = binary.BigEndian.Uint16(buf[h.endPos:])
			h.endPos += TPProtoIdLength
		} else {
			return ErrHeadTooShort
		}
	case PTResponse:
		if bufLen >= MinLength+TPSeqIdLength { // response包头至少有8个byte
			h.SeqId = binary.BigEndian.Uint32(buf[h.endPos:])
			h.endPos += TPSeqIdLength
		} else {
			return ErrHeadTooShort
		}
	case PTRequest:
		if bufLen >= MinLength+TPSeqIdLength+TPProtoIdLength { // 请求包头至少有8字节
			h.SeqId = binary.BigEndian.Uint32(buf[h.endPos:])
			h.endPos += TPSeqIdLength
			h.ProtoID = binary.BigEndian.Uint16(buf[h.endPos:])
			h.endPos += TPProtoIdLength
		} else {
			return ErrHeadTooShort
		}
	case PTHeartBeat: // 心跳包这里无需操作什么
	default:
		return ErrWrongPackType
	}
	h.DecodeCompressed(buf, CompressPos)
	return nil
}

func (h *Header) EncodePackageType(buf []byte, index int, pt PackType) {
	buf[index] = (buf[index] | 0xC0) & (byte(pt) << 6)
}

func (h *Header) DecodePackageType(buf []byte, index int) {
	b := buf[index]
	h.Type = PackType((b & 0xC0) >> 6)
}

func (h *Header) EncodeCompressed(buf []byte, index int, compressed bool) {
	if compressed {
		buf[index] = buf[index] | 0x20
	} else {
		buf[index] = buf[index] & 0xdf // 0xdf=11011111
	}
}

func (h *Header) DecodeCompressed(buf []byte, index int) {
	b := buf[index]
	h.IsCompressed = (b & 0x20 >> 5) == PortoCompressed
}

// --------------------

type Body struct {
	Length uint16
	Msg    []byte
}

// -----------------------

type ProtoContext struct { // 暂时的协议名字
	Header
	Body
}

var base = uint32(0)

// 编码数据进io流
func (c *ProtoContext) EncodeToStream(stream io.Writer) error {
	pLen := len(c.Msg)
	if pLen > MaxBodyLength { // msg最大不能超过65535和字节
		return ErrBodyTooLong
	}
	c.Length = uint16(pLen)

	var buf []byte
	var err error
	if buf, err = c.Header.EncodeToBuf(); err != nil { // 头部编码
		return err
	}
	var result []byte
	var cLen uint32
	if c.Header.Type == PTHeartBeat {
		result = buf[0:MTLength]
		cLen = MTLength
	} else {
		cLen = uint32(c.endPos) + uint32(c.Length) + TPMsgLength // 转成uint32 防止溢出
		result = bytePool.Get(int(cLen))                         // 从[]byte池取所需大小的[]byte
		copy(result[:c.endPos], buf[:c.endPos])
		binary.BigEndian.PutUint16(result[c.endPos:], c.Length)
		copy(result[c.endPos+TPMsgLength:cLen], c.Msg) // TODO 解压缩
		defer bytePool.Put(result)
	}
	if _, err := stream.Write(result[0:cLen]); err != nil {
		return err
	}
	return nil
}

// body编码
func (c *ProtoContext) Encode() ([]byte, error) {
	pLen := len(c.Msg)
	if pLen > MaxBodyLength { // msg最大不能超过65535和字节
		return nil, ErrBodyTooLong
	}
	c.Length = uint16(pLen)

	var buf []byte
	var err error
	if buf, err = c.Header.EncodeToBuf(); err != nil {
		return nil, err
	}
	if c.Header.Type == PTHeartBeat {
		return buf[0:MTLength], nil
	}
	cLen := uint32(c.endPos) + uint32(c.Length) + TPMsgLength // 防止溢出
	result := make([]byte, cLen)
	copy(result[0:c.endPos], buf[0:c.endPos])
	binary.BigEndian.PutUint16(result[c.endPos:], c.Length)
	copy(result[c.endPos+TPMsgLength:cLen], c.Msg)

	return result[:cLen], nil
}

func (c *ProtoContext) SetStringMsg(msg string) {
	c.Msg = []byte(msg) // 消耗似乎有点大
}

func (c *ProtoContext) Decode(buf []byte) error {
	var bufLen = len(buf)
	if err := c.Header.Decode(buf, bufLen); err != nil {
		return err
	}
	c.Length = binary.BigEndian.Uint16(buf[c.Header.endPos:])
	packageSize := c.Header.endPos + TPMsgLength + int(c.Length)
	if bufLen >= packageSize {
		c.Msg = buf[c.Header.endPos+TPMsgLength : packageSize]
	} else {
		return ErrWrongMsgLength
	}
	return nil
}

// TODO 替换成全常量
func NewBytesForStream(stream io.Reader) ([]byte, bool, error) {
	var buf = bytePool.Get(MaxHeadLength) // 头部最大不超过10
	defer bytePool.Put(buf)               // 用完就放回去
	// 读头, 可能读到流结尾或者错误，抛给上层处理
	if _, err := io.ReadFull(stream, buf[0:TPMagicLength]); err != nil {
		return nil, false, err
	}

	if buf[MagicPos] != ProtoMagic {
		return nil, false, ErrWrongProtoFormat // 非本协议请求 TODO 有没可能塞回去？
	}
	// 读类型
	if _, err := io.ReadFull(stream, buf[TPMagicLength:TPMagicLength+TPTypeLength]); err != nil {
		return nil, false, err
	}
	var headSize = 0
	packType := PackType((buf[1] & 0xC0) >> 6)
	switch packType { // 根据不同的类型判断头的大小
	case PTPush:
		headSize = TPMagicLength + TPTypeLength + TPProtoIdLength
	case PTRequest:
		headSize = TPMagicLength + TPTypeLength + TPProtoIdLength + TPSeqIdLength
	case PTResponse:
		headSize = TPMagicLength + TPTypeLength + TPSeqIdLength
	case PTHeartBeat:
		return nil, true, nil
	}
	// 读数据长度
	if _, err := io.ReadFull(stream, buf[TPMagicLength+TPTypeLength:headSize+TPMagicLength+TPTypeLength]); err != nil {
		return nil, false, err
	}
	l := binary.BigEndian.Uint16(buf[headSize:]) // 解析数据长度
	headMsgLength := headSize + TPMsgLength      // 头部加消息标识长度
	var request = make([]byte, headMsgLength+int(l))
	if l > 0 { // linux系统下不会因为这个而阻塞
		if _, err := io.ReadFull(stream, request[headMsgLength:headMsgLength+int(l)]); err != nil {
			return nil, false, err
		}
	}
	copy(request[0:headMsgLength], buf[0:headMsgLength]) // 复制头部内容
	return request, false, nil
}

// debug启动模式 关闭池化
func NewDebugBytesForStream(stream io.Reader) ([]byte, bool, error) {
	var buf = bytePool.Get(MaxHeadLength) // 头部最大不超过10
	// defer bytePool.Put(buf)               // 用完就放回去
	// 读头,可能读到流结尾或者错误，抛给上层处理
	if _, err := io.ReadFull(stream, buf[0:TPMagicLength]); err != nil {
		return buf, false, err
	}
	if buf[MagicPos] != ProtoMagic {
		return buf, false, ErrWrongProtoFormat // 非本协议请求 TODO 有没可能塞回去？
	}
	// 读类型
	if _, err := io.ReadFull(stream, buf[TPMagicLength:TPMagicLength+TPTypeLength]); err != nil {
		return buf, false, err
	}
	var headSize = 0
	packType := PackType((buf[1] & 0xC0) >> 6)
	switch packType { // 根据不同的类型判断头的大小
	case PTPush:
		headSize = TPMagicLength + TPTypeLength + TPProtoIdLength
	case PTRequest:
		headSize = TPMagicLength + TPTypeLength + TPProtoIdLength + TPSeqIdLength
	case PTResponse:
		headSize = TPMagicLength + TPTypeLength + TPSeqIdLength
	case PTHeartBeat:
		return buf, true, nil
	}
	// 读数据长度
	if _, err := io.ReadFull(stream, buf[TPMagicLength+TPTypeLength:headSize+TPMagicLength+TPTypeLength]); err != nil {
		return buf, false, err
	}
	l := binary.BigEndian.Uint16(buf[headSize:]) // 解析数据长度
	headMsgLength := headSize + TPMsgLength      // 头部加消息标识长度
	var request = make([]byte, headMsgLength+int(l))
	copy(request[0:headMsgLength], buf[0:headMsgLength]) // 复制头部内容
	if l > 0 {                                           // linux系统下不会因为这个而阻塞
		if _, err := io.ReadFull(stream, request[headMsgLength:headMsgLength+int(l)]); err != nil {
			return buf, false, err
		}
	}
	return request, false, nil
}

// TODO 替换成全常量
func NewPContextForStream(stream io.Reader) (*ProtoContext, error) { // TODO 搞个有内鬼 终止生娃的实现(读取到不认识的协议，将读取的信息塞回去 这个需要调用方支持，提供数据塞回接口)
	var buf = bytePool.Get(MaxHeadLength) // 头部最大不超过10
	defer bytePool.Put(buf)               // 用完就放回去
	// 读头
	if _, err := io.ReadFull(stream, buf[0:TPMagicLength]); err != nil {
		return nil, err
	}
	if buf[MagicPos] != ProtoMagic {
		return nil, ErrWrongProtoFormat // 非本协议请求 TODO 有没可能塞回去？
	}

	if _, err := io.ReadFull(stream, buf[TPMagicLength:TPMagicLength+TPTypeLength]); err != nil {
		return nil, err
	}
	var headSize = 0
	packType := PackType((buf[1] & 0xC0) >> 6)
	switch packType { // 根据不同的类型判断头的大小
	case PTPush:
		headSize = TPMagicLength + TPTypeLength + TPProtoIdLength
	case PTRequest:
		headSize = TPMagicLength + TPTypeLength + TPProtoIdLength + TPSeqIdLength
	case PTResponse:
		headSize = TPMagicLength + TPTypeLength + TPSeqIdLength
	case PTHeartBeat:
		return &ProtoContext{Header: Header{Type: PTHeartBeat}}, nil
	}
	if _, err := io.ReadFull(stream, buf[TPMagicLength+TPTypeLength:headSize+TPMagicLength+TPTypeLength]); err != nil {
		return nil, err
	}
	l := binary.BigEndian.Uint16(buf[headSize:]) // 解析数据长度
	headMsgLength := headSize + TPMsgLength
	var request = make([]byte, headMsgLength+int(l))
	if l > 0 { // linux系统下不会因为这个而阻塞
		if _, err := io.ReadFull(stream, request[headMsgLength:headMsgLength+int(l)]); err != nil {
			return nil, err
		}
	}
	copy(request[0:headMsgLength], buf[0:headMsgLength]) // 复制头部内容
	result := &ProtoContext{}
	if err := result.Decode(request); err != nil {
		return nil, err
	}
	return result, nil
}

func NewPushContext(protoId uint16, compressed bool, msg []byte) *ProtoContext {
	return &ProtoContext{
		Header: Header{
			Type:         PTPush,
			IsCompressed: compressed,
			ProtoID:      protoId,
		},
		Body: Body{
			Length: uint16(len(msg)),
			Msg:    msg,
		},
	}
}

// NewRequestContext 新建一个请求协议实例
func NewRequestContext(protoId uint16, seqId uint32, compressed bool, msg []byte) *ProtoContext {
	return &ProtoContext{
		Header: Header{
			Type:         PTRequest,
			IsCompressed: compressed,
			ProtoID:      protoId,
			SeqId:        seqId,
		},
		Body: Body{
			Length: uint16(len(msg)),
			Msg:    msg,
		},
	}
}

func NewHeartBeat() *ProtoContext {
	return &ProtoContext{
		Header: Header{
			Type: PTHeartBeat,
		},
		Body: Body{},
	}
}
