// Copyright © 2019 Weald Technology Trading
// 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 multicodec provides the ability to add and remove codec prefixes from data.
package multicodec

import (
	"encoding/binary"
	"fmt"
)

// AddCodec adds a codec prefix to a byte array.
// It returns a new byte array with the relevant codec prefixed.
func AddCodec(name string, data []byte) ([]byte, error) {
	id, err := ID(name)
	if err != nil {
		return nil, err
	}
	buf := make([]byte, binary.MaxVarintLen64)
	size := binary.PutUvarint(buf, id)
	res := make([]byte, size+len(data))
	copy(res, buf[0:size])
	copy(res[size:], data)
	return res, nil
}

// RemoveCodec removes a codec prefix from a byte array.
// It returns a slice of the input byte array without the codec prefix, along with the ID of the codec that has been removed.
func RemoveCodec(data []byte) ([]byte, uint64, error) {
	id, size := binary.Uvarint(data)
	if id == 0 {
		return nil, 0, fmt.Errorf("failed to find codec prefix to remove")
	}
	return data[size:], id, nil
}

// GetCodec returns the ID of the codec prefix from a byte array
func GetCodec(data []byte) (uint64, error) {
	id, _ := binary.Uvarint(data)
	if id == 0 {
		return 0, fmt.Errorf("failed to find codec prefix to remove")
	}
	return id, nil
}

// IsCodec returns true if the data is of the supplied codec prefix, otherwise false
func IsCodec(name string, data []byte) bool {
	id, err := GetCodec(data)
	if err != nil {
		return false
	}
	codecName, err := Name(id)
	if err != nil {
		// Failed to obtain name for ID
		return false
	}
	return codecName == name
}

// ID obtains the ID of a codec from its name
func ID(name string) (uint64, error) {
	codec, exists := codecs[name]
	if !exists {
		return 0, fmt.Errorf("unknown name %s", name)
	}
	return codec.id, nil
}

// MustID obtains the ID of a codec from its name, panicking if not present.
func MustID(name string) uint64 {
	codec, exists := codecs[name]
	if !exists {
		panic(fmt.Errorf("unknown name %s", name))
	}
	return codec.id
}

// Name obtains the name of a codec from its ID
func Name(id uint64) (string, error) {
	codec, exists := reverseCodecs[id]
	if !exists {
		return "", fmt.Errorf("unknown ID 0x%x", id)
	}
	return codec.name, nil
}

// MustName obtains the name of a codec from its ID, panicking if not present.
func MustName(id uint64) string {
	codec, exists := reverseCodecs[id]
	if !exists {
		panic(fmt.Errorf("unknown ID 0x%x", id))
	}
	return codec.name
}