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

import (
	"io"
)

import (
	gxbytes "github.com/dubbogo/gost/bytes"

	perrors "github.com/pkg/errors"
)

// binaryTag check whether the given tag is a binary tag
func binaryTag(tag byte) bool {
	return (tag >= BC_BINARY_DIRECT && tag <= INT_DIRECT_MAX) ||
		(tag >= BC_BINARY_SHORT && tag <= byte(0x37)) ||
		tag == BC_BINARY_CHUNK ||
		tag == BC_BINARY
}

/////////////////////////////////////////
// Binary, []byte
/////////////////////////////////////////

// # 8-bit binary data split into 64k chunks
// ::= x41(A) b1 b0 <binary-data> binary # non-final chunk
// ::= x42(B) b1 b0 <binary-data>        # final chunk
// ::= [x20-x2f] <binary-data>           # binary data of length 0-15
// ::= [x34-x37] <binary-data>           # binary data of length 0-1023
func encBinary(b []byte, v []byte) []byte {
	var (
		length  uint16
		vLength int
	)

	if v == nil {
		return encByte(b, BC_NULL)
	}

	vLength = len(v)
	for {
		if vLength > CHUNK_SIZE {
			length = CHUNK_SIZE
			b = encByte(b, BC_BINARY_CHUNK, byte(length>>8), byte(length))
		} else {
			length = uint16(vLength)
			if vLength <= int(BINARY_DIRECT_MAX) {
				b = encByte(b, byte(int(BC_BINARY_DIRECT)+vLength))
			} else if vLength <= int(BINARY_SHORT_MAX) {
				b = encByte(b, byte(int(BC_BINARY_SHORT)+vLength>>8), byte(vLength))
			} else {
				b = encByte(b, BC_BINARY, byte(vLength>>8), byte(vLength))
			}
		}

		b = append(b, v[:length]...)
		v = v[length:]
		vLength = len(v)

		if vLength == 0 {
			break
		}
	}

	return b
}

/////////////////////////////////////////
// Binary, []byte
/////////////////////////////////////////

// # 8-bit binary data split into 64k chunks
// ::= x41('A') b1 b0 <binary-data> binary # non-final chunk
// ::= x42('B') b1 b0 <binary-data>        # final chunk
// ::= [x20-x2f] <binary-data>  # binary data of length 0-15
// ::= [x34-x37] <binary-data>  # binary data of length 0-1023
func (d *Decoder) getBinaryLength(tag byte) (int, error) {
	var (
		err error
		buf [2]byte
	)

	if tag >= BC_BINARY_DIRECT && tag <= INT_DIRECT_MAX { // [0x20, 0x2f]
		return int(tag - BC_BINARY_DIRECT), nil
	}

	if tag >= BC_BINARY_SHORT && tag <= byte(0x37) { // [0x34, 0x37]
		_, err = io.ReadFull(d.reader, buf[:1])
		if err != nil {
			return 0, perrors.WithStack(err)
		}

		return int(tag-BC_BINARY_SHORT)<<8 + int(buf[0]), nil
	}

	if tag != BC_BINARY_CHUNK && tag != BC_BINARY {
		return 0, perrors.Errorf("illegal binary tag:%d", tag)
	}

	_, err = io.ReadFull(d.reader, buf[:2])
	if err != nil {
		return 0, perrors.WithStack(err)
	}

	return int(buf[0])<<8 + int(buf[1]), nil
}

func (d *Decoder) decBinary(flag int32) ([]byte, error) {
	var (
		err    error
		tag    byte
		length int
	)

	if flag != TAG_READ {
		tag = byte(flag)
	} else {
		tag, err = d.readBufByte()
		if err != nil {
			return nil, perrors.WithStack(err)
		}
	}

	if tag == BC_NULL {
		return []byte(""), nil
	}

	data := make([]byte, 0, 128)
	bufp := gxbytes.AcquireBytes(65536)
	defer gxbytes.ReleaseBytes(bufp)
	buf := *bufp

	for {
		length, err = d.getBinaryLength(tag)
		if err != nil {
			return nil, perrors.WithStack(err)
		}

		_, err = io.ReadFull(d.reader, buf[:length])
		if err != nil {
			return nil, perrors.WithStack(err)
		}

		data = append(data, buf[:length]...)

		if tag != BC_BINARY_CHUNK {
			break
		}

		tag, err = d.readBufByte()
		if err != nil {
			return nil, perrors.WithStack(err)
		}
	}
	return data, nil
}
