// Copyright 2019 The ebml-go authors.
//
// Licensed 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 ebml

import (
	"bytes"
	"encoding/binary"
	"errors"
	"io"
	"math"
	"strings"
	"time"
)

const (
	// DateEpochInUnixtime is the Unixtime of EBML date epoch.
	DateEpochInUnixtime = 978307200
	// SizeUnknown is the longest unknown size value.
	SizeUnknown = 0xffffffffffffff
)

// ErrInvalidFloatSize means that a element size is invalid for float type. Float must be 4 or 8 bytes.
var ErrInvalidFloatSize = errors.New("invalid float size")

// ErrInvalidType means that a value is not convertible to the element data.
var ErrInvalidType = errors.New("invalid type")

// ErrUnsupportedElementID means that a value is out of range of EBML encoding.
var ErrUnsupportedElementID = errors.New("unsupported Element ID")

// ErrOutOfRange means that a value is out of range of the data type.
var ErrOutOfRange = errors.New("out of range")

// valueDecoder is a value decoder sharing internal buffer.
// Member functions must not called concurrently.
type valueDecoder struct {
	bs [1]byte
}

func (d *valueDecoder) decode(t DataType, r io.Reader, n uint64) (interface{}, error) {
	switch t {
	case DataTypeInt:
		return d.readInt(r, n)
	case DataTypeUInt:
		return d.readUInt(r, n)
	case DataTypeDate:
		return d.readDate(r, n)
	case DataTypeFloat:
		return d.readFloat(r, n)
	case DataTypeBinary:
		return d.readBinary(r, n)
	case DataTypeString:
		return d.readString(r, n)
	case DataTypeBlock:
		return d.readBlock(r, n)
	}
	panic("invalid data type")
}

func (d *valueDecoder) readDataSize(r io.Reader) (uint64, int, error) {
	v, n, err := d.readVUInt(r)
	if v == (uint64(0xFFFFFFFFFFFFFFFF) >> uint(64-n*7)) {
		return SizeUnknown, n, err
	}
	return v, n, err
}

func (d *valueDecoder) readVUInt(r io.Reader) (uint64, int, error) {
	bytesRead, err := r.Read(d.bs[:])
	switch err {
	case nil:
	case io.EOF:
		return 0, bytesRead, io.ErrUnexpectedEOF
	default:
		return 0, bytesRead, err
	}

	var vc int
	var value uint64

	b := d.bs[0]
	switch {
	case b&0x80 == 0x80:
		vc = 0
		value = uint64(b & 0x7F)
	case b&0xC0 == 0x40:
		vc = 1
		value = uint64(b & 0x3F)
	case b&0xE0 == 0x20:
		vc = 2
		value = uint64(b & 0x1F)
	case b&0xF0 == 0x10:
		vc = 3
		value = uint64(b & 0x0F)
	case b&0xF8 == 0x08:
		vc = 4
		value = uint64(b & 0x07)
	case b&0xFC == 0x04:
		vc = 5
		value = uint64(b & 0x03)
	case b&0xFE == 0x02:
		vc = 6
		value = uint64(b & 0x01)
	case b == 0x01:
		vc = 7
		value = 0
	}

	for {
		if vc == 0 {
			return value, bytesRead, nil
		}

		n, err := r.Read(d.bs[:])
		switch err {
		case nil:
		case io.EOF:
			return 0, bytesRead, io.ErrUnexpectedEOF
		default:
			return 0, bytesRead, err
		}
		bytesRead += n
		value = value<<8 | uint64(d.bs[0])
		vc--
	}
}

func (d *valueDecoder) readVInt(r io.Reader) (int64, int, error) {
	u, n, err := d.readVUInt(r)
	if err != nil {
		return 0, n, err
	}
	v := int64(u)
	switch n {
	case 1:
		v -= 0x3F
	case 2:
		v -= 0x1FFF
	case 3:
		v -= 0x0FFFFF
	case 4:
		v -= 0x07FFFFFF
	case 5:
		v -= 0x03FFFFFFFF
	case 6:
		v -= 0x01FFFFFFFFFF
	case 7:
		v -= 0x00FFFFFFFFFFFF
	default:
		v -= 0x007FFFFFFFFFFFFF
	}
	return v, n, nil
}

func (d *valueDecoder) readBinary(r io.Reader, n uint64) (interface{}, error) {
	bs := make([]byte, n)

	switch _, err := io.ReadFull(r, bs); err {
	case nil:
		return bs, nil
	case io.EOF:
		return bs, io.ErrUnexpectedEOF
	default:
		return []byte{}, err
	}
}

func (d *valueDecoder) readString(r io.Reader, n uint64) (interface{}, error) {
	bs, err := d.readBinary(r, n)
	if err != nil {
		return "", err
	}
	s := string(bs.([]byte))
	// Remove trailing null characters
	ss := strings.Split(s, "\x00")
	return ss[0], nil
}

func (d *valueDecoder) readInt(r io.Reader, n uint64) (interface{}, error) {
	v, err := d.readUInt(r, n)
	if err != nil {
		return 0, err
	}
	v64 := v.(uint64)
	if n != 8 && (v64&(1<<(n*8-1))) != 0 {
		// negative value
		for i := n; i < 8; i++ {
			v64 |= 0xFF << (i * 8)
		}
	}
	return int64(v64), nil
}

func (d *valueDecoder) readUInt(r io.Reader, n uint64) (interface{}, error) {
	bs := make([]byte, n)

	switch _, err := io.ReadFull(r, bs); err {
	case nil:
	case io.EOF:
		return 0, io.ErrUnexpectedEOF
	default:
		return 0, err
	}

	var v uint64
	for _, b := range bs {
		v = v<<8 | uint64(b)
	}
	return v, nil
}

func (d *valueDecoder) readDate(r io.Reader, n uint64) (interface{}, error) {
	i, err := d.readInt(r, n)
	if err != nil {
		return time.Unix(0, 0), err
	}
	return time.Unix(DateEpochInUnixtime, i.(int64)), nil
}

func (d *valueDecoder) readFloat(r io.Reader, n uint64) (interface{}, error) {
	bs := make([]byte, n)

	switch _, err := io.ReadFull(r, bs); err {
	case nil:
	case io.EOF:
		return bs, io.ErrUnexpectedEOF
	default:
		return []byte{}, err
	}

	switch n {
	case 4:
		return float64(math.Float32frombits(binary.BigEndian.Uint32(bs))), nil
	case 8:
		return math.Float64frombits(binary.BigEndian.Uint64(bs)), nil
	default:
		return 0.0, wrapErrorf(ErrInvalidFloatSize, "reading %d bytes float", n)
	}
}

func (d *valueDecoder) readBlock(r io.Reader, n uint64) (interface{}, error) {
	b, err := UnmarshalBlock(r, int64(n))
	if err != nil {
		return nil, err
	}
	return *b, nil
}

var perTypeEncoder = map[DataType]func(interface{}, uint64) ([]byte, error){
	DataTypeInt:    encodeInt,
	DataTypeUInt:   encodeUInt,
	DataTypeDate:   encodeDate,
	DataTypeFloat:  encodeFloat,
	DataTypeBinary: encodeBinary,
	DataTypeString: encodeString,
	DataTypeBlock:  encodeBlock,
}

func encodeDataSize(v, n uint64) []byte {
	switch {
	case v < 0x80-1 && n < 2:
		return []byte{byte(v) | 0x80}
	case v < 0x4000-1 && n < 3:
		return []byte{byte(v>>8) | 0x40, byte(v)}
	case v < 0x200000-1 && n < 4:
		return []byte{byte(v>>16) | 0x20, byte(v >> 8), byte(v)}
	case v < 0x10000000-1 && n < 5:
		return []byte{byte(v>>24) | 0x10, byte(v >> 16), byte(v >> 8), byte(v)}
	case v < 0x800000000-1 && n < 6:
		return []byte{byte(v>>32) | 0x8, byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}
	case v < 0x40000000000-1 && n < 7:
		return []byte{byte(v>>40) | 0x4, byte(v >> 32), byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}
	case v < 0x2000000000000-1 && n < 8:
		return []byte{byte(v>>48) | 0x2, byte(v >> 40), byte(v >> 32), byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}
	case v < SizeUnknown:
		return []byte{0x1, byte(v >> 48), byte(v >> 40), byte(v >> 32), byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}
	default:
		return []byte{0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
	}
}

func encodeElementID(v uint64) ([]byte, error) {
	switch {
	case v < 0x80:
		return []byte{byte(v) | 0x80}, nil
	case v < 0x4000:
		return []byte{byte(v>>8) | 0x40, byte(v)}, nil
	case v < 0x200000:
		return []byte{byte(v>>16) | 0x20, byte(v >> 8), byte(v)}, nil
	case v < 0x10000000:
		return []byte{byte(v>>24) | 0x10, byte(v >> 16), byte(v >> 8), byte(v)}, nil
	case v < 0x800000000:
		return []byte{byte(v>>32) | 0x8, byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}, nil
	case v < 0x40000000000:
		return []byte{byte(v>>40) | 0x4, byte(v >> 32), byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}, nil
	case v < 0x2000000000000:
		return []byte{byte(v>>48) | 0x2, byte(v >> 40), byte(v >> 32), byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}, nil
	}
	return nil, ErrUnsupportedElementID
}

func encodeVInt(v int64) ([]byte, error) {
	switch {
	case -0x3F <= v && v <= 0x3F:
		v += 0x3F
		return encodeDataSize(uint64(v), 1), nil
	case -0x1FFF <= v && v <= 0x1FFF:
		v += 0x1FFF
		return encodeDataSize(uint64(v), 2), nil
	case -0xFFFFF <= v && v <= 0xFFFFF:
		v += 0xFFFFF
		return encodeDataSize(uint64(v), 3), nil
	case -0x7FFFFFF <= v && v <= 0x7FFFFFF:
		v += 0x7FFFFFF
		return encodeDataSize(uint64(v), 4), nil
	case -0x3FFFFFFFF <= v && v <= 0x3FFFFFFFF:
		v += 0x3FFFFFFFF
		return encodeDataSize(uint64(v), 5), nil
	case -0x1FFFFFFFFFF <= v && v <= 0x1FFFFFFFFFF:
		v += 0x1FFFFFFFFFF
		return encodeDataSize(uint64(v), 6), nil
	case -0xFFFFFFFFFFFF <= v && v <= 0xFFFFFFFFFFFF:
		v += 0xFFFFFFFFFFFF
		return encodeDataSize(uint64(v), 7), nil
	case -0x7FFFFFFFFFFFFF <= v && v <= 0x7FFFFFFFFFFFFF:
		v += 0x7FFFFFFFFFFFFF
		return encodeDataSize(uint64(v), 8), nil
	default:
		return nil, ErrOutOfRange
	}
}

func encodeBinary(i interface{}, n uint64) ([]byte, error) {
	v, ok := i.([]byte)
	if !ok {
		return []byte{}, ErrInvalidType
	}
	if uint64(len(v)) >= n {
		return v, nil
	}
	return append(v, bytes.Repeat([]byte{0x00}, int(n)-len(v))...), nil
}

func encodeString(i interface{}, n uint64) ([]byte, error) {
	v, ok := i.(string)
	if !ok {
		return []byte{}, wrapErrorf(ErrInvalidType, "writing %T as string", i)
	}
	if uint64(len(v)) >= n {
		return []byte(v), nil
	}
	return append([]byte(v), bytes.Repeat([]byte{0x00}, int(n)-len(v))...), nil
}

func encodeInt(i interface{}, n uint64) ([]byte, error) {
	var v int64
	switch v2 := i.(type) {
	case int:
		v = int64(v2)
	case int8:
		v = int64(v2)
	case int16:
		v = int64(v2)
	case int32:
		v = int64(v2)
	case int64:
		v = v2
	default:
		return []byte{}, wrapErrorf(ErrInvalidType, "writing %T as int", i)
	}
	return encodeUInt(uint64(v), n)
}

func encodeUInt(i interface{}, n uint64) ([]byte, error) {
	var v uint64
	switch v2 := i.(type) {
	case uint:
		v = uint64(v2)
	case uint8:
		v = uint64(v2)
	case uint16:
		v = uint64(v2)
	case uint32:
		v = uint64(v2)
	case uint64:
		v = v2
	default:
		return []byte{}, wrapErrorf(ErrInvalidType, "writing %T as uint", i)
	}
	switch {
	case v < 0x100 && n < 2:
		return []byte{byte(v)}, nil
	case v < 0x10000 && n < 3:
		return []byte{byte(v >> 8), byte(v)}, nil
	case v < 0x1000000 && n < 4:
		return []byte{byte(v >> 16), byte(v >> 8), byte(v)}, nil
	case v < 0x100000000 && n < 5:
		return []byte{byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}, nil
	case v < 0x10000000000 && n < 6:
		return []byte{byte(v >> 32), byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}, nil
	case v < 0x1000000000000 && n < 7:
		return []byte{byte(v >> 40), byte(v >> 32), byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}, nil
	case v < 0x100000000000000 && n < 8:
		return []byte{byte(v >> 48), byte(v >> 40), byte(v >> 32), byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}, nil
	default:
		return []byte{byte(v >> 56), byte(v >> 48), byte(v >> 40), byte(v >> 32), byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}, nil
	}
}

func encodeDate(i interface{}, n uint64) ([]byte, error) {
	v, ok := i.(time.Time)
	if !ok {
		return []byte{}, wrapErrorf(ErrInvalidType, "writing %T as date", i)
	}
	dtns := v.Sub(time.Unix(DateEpochInUnixtime, 0)).Nanoseconds()
	return encodeInt(int64(dtns), n)
}

func encodeFloat32(i float32) ([]byte, error) {
	b := make([]byte, 4)
	binary.BigEndian.PutUint32(b, math.Float32bits(i))
	return b, nil
}

func encodeFloat64(i float64) ([]byte, error) {
	b := make([]byte, 8)
	binary.BigEndian.PutUint64(b, math.Float64bits(i))
	return b, nil
}

func encodeFloat(i interface{}, n uint64) ([]byte, error) {
	switch v := i.(type) {
	case float64:
		switch n {
		case 0:
			return encodeFloat64(v)
		case 4:
			return encodeFloat32(float32(v))
		case 8:
			return encodeFloat64(v)
		default:
			return []byte{}, wrapErrorf(ErrInvalidFloatSize, "writing %d bytes float", n)
		}
	case float32:
		switch n {
		case 0:
			return encodeFloat32(v)
		case 4:
			return encodeFloat32(v)
		case 8:
			return encodeFloat64(float64(v))
		default:
			return []byte{}, wrapErrorf(ErrInvalidFloatSize, "writing %d bytes float", n)
		}
	default:
		return []byte{}, wrapErrorf(ErrInvalidType, "writing %T as float", i)
	}
}

func encodeBlock(i interface{}, n uint64) ([]byte, error) {
	v, ok := i.(Block)
	if !ok {
		return []byte{}, wrapErrorf(ErrInvalidType, "writing %T as block", i)
	}
	var b bytes.Buffer
	if err := MarshalBlock(&v, &b); err != nil {
		return []byte{}, err
	}
	return b.Bytes(), nil
}
