/*
 * 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 impl

import (
	"bufio"
	"bytes"
	"fmt"
	"time"
)

import (
	"github.com/pkg/errors"
)

type PackageType int

// enum part
const (
	PackageError              = PackageType(0x01)
	PackageRequest            = PackageType(0x02)
	PackageResponse           = PackageType(0x04)
	PackageHeartbeat          = PackageType(0x08)
	PackageRequest_TwoWay     = PackageType(0x10)
	PackageResponse_Exception = PackageType(0x20)
	PackageType_BitSize       = 0x2f
)

type DubboHeader struct {
	SerialID       byte
	Type           PackageType
	ID             int64
	BodyLen        int
	ResponseStatus byte
}

// Service defines service instance
type Service struct {
	Path      string
	Interface string
	Group     string
	Version   string
	Method    string
	Timeout   time.Duration // request timeout
}

type DubboPackage struct {
	Header  DubboHeader
	Service Service
	Body    any
	Err     error
	Codec   *ProtocolCodec
}

func (p DubboPackage) String() string {
	return fmt.Sprintf("HessianPackage: Header-%v, Path-%v, Body-%v", p.Header, p.Service, p.Body)
}

func (p *DubboPackage) ReadHeader() error {
	return p.Codec.ReadHeader(&p.Header)
}

func (p *DubboPackage) Marshal() (*bytes.Buffer, error) {
	if p.Codec == nil {
		return nil, errors.New("Codec is nil")
	}
	pkg, err := p.Codec.Encode(*p)
	if err != nil {
		return nil, errors.WithStack(err)
	}
	return bytes.NewBuffer(pkg), nil
}

func (p *DubboPackage) Unmarshal() error {
	if p.Codec == nil {
		return errors.New("Codec is nil")
	}
	return p.Codec.Decode(p)
}

func (p DubboPackage) IsHeartBeat() bool {
	return p.Header.Type&PackageHeartbeat != 0
}

func (p DubboPackage) IsRequest() bool {
	return p.Header.Type&(PackageRequest_TwoWay|PackageRequest) != 0
}

func (p DubboPackage) IsResponse() bool {
	return p.Header.Type == PackageResponse
}

func (p DubboPackage) IsResponseWithException() bool {
	flag := PackageResponse | PackageResponse_Exception
	return p.Header.Type&flag == flag
}

func (p DubboPackage) GetBodyLen() int {
	return p.Header.BodyLen
}

func (p DubboPackage) GetLen() int {
	return HEADER_LENGTH + p.Header.BodyLen
}

func (p DubboPackage) GetBody() any {
	return p.Body
}

func (p *DubboPackage) SetBody(body any) {
	p.Body = body
}

func (p *DubboPackage) SetHeader(header DubboHeader) {
	p.Header = header
}

func (p *DubboPackage) SetService(svc Service) {
	p.Service = svc
}

func (p *DubboPackage) SetID(id int64) {
	p.Header.ID = id
}

func (p DubboPackage) GetHeader() DubboHeader {
	return p.Header
}

func (p DubboPackage) GetService() Service {
	return p.Service
}

func (p *DubboPackage) SetResponseStatus(status byte) {
	p.Header.ResponseStatus = status
}

func (p *DubboPackage) SetSerializer(serializer Serializer) {
	p.Codec.SetSerializer(serializer)
}

func NewDubboPackage(data *bytes.Buffer) *DubboPackage {
	var codec *ProtocolCodec
	if data == nil {
		codec = NewDubboCodec(nil)
	} else {
		codec = NewDubboCodec(bufio.NewReaderSize(data, len(data.Bytes())))
	}
	return &DubboPackage{
		Header:  DubboHeader{},
		Service: Service{},
		Body:    nil,
		Err:     nil,
		Codec:   codec,
	}
}
