package eacl

import (
	"strconv"

	v2acl "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
)

// Filter defines check conditions if request header is matched or not. Matched
// header means that request should be processed according to ContainerEACL action.
//
// Filter is compatible with v2 acl.EACLRecord.Filter message.
type Filter struct {
	from    FilterHeaderType
	matcher Match
	key     filterKey
	value   stringEncoder
}

type staticStringer string

type u64Stringer uint64

type filterKey struct {
	typ filterKeyType

	str string
}

// enumeration of reserved filter keys.
type filterKeyType int

const (
	_ filterKeyType = iota
	fKeyObjVersion
	fKeyObjID
	fKeyObjContainerID
	fKeyObjOwnerID
	fKeyObjCreationEpoch
	fKeyObjPayloadLength
	fKeyObjPayloadHash
	fKeyObjType
	fKeyObjHomomorphicHash
	fKeyObjLast // helper, used in tests
)

func (s staticStringer) EncodeToString() string {
	return string(s)
}

func (u u64Stringer) EncodeToString() string {
	return strconv.FormatUint(uint64(u), 10)
}

// Value returns filtered string value.
func (f Filter) Value() string {
	return f.value.EncodeToString()
}

// Matcher returns filter Match type.
func (f Filter) Matcher() Match {
	return f.matcher
}

// Key returns key to the filtered header.
func (f Filter) Key() string {
	return f.key.String()
}

// From returns FilterHeaderType that defined which header will be filtered.
func (f Filter) From() FilterHeaderType {
	return f.from
}

// ToV2 converts Filter to v2 acl.EACLRecord.Filter message.
//
// Nil Filter converts to nil.
func (f *Filter) ToV2() *v2acl.HeaderFilter {
	if f == nil {
		return nil
	}

	filter := new(v2acl.HeaderFilter)
	filter.SetValue(f.value.EncodeToString())
	filter.SetKey(f.key.String())
	filter.SetMatchType(f.matcher.ToV2())
	filter.SetHeaderType(f.from.ToV2())

	return filter
}

func (k filterKey) String() string {
	switch k.typ {
	default:
		return k.str
	case fKeyObjVersion:
		return v2acl.FilterObjectVersion
	case fKeyObjID:
		return v2acl.FilterObjectID
	case fKeyObjContainerID:
		return v2acl.FilterObjectContainerID
	case fKeyObjOwnerID:
		return v2acl.FilterObjectOwnerID
	case fKeyObjCreationEpoch:
		return v2acl.FilterObjectCreationEpoch
	case fKeyObjPayloadLength:
		return v2acl.FilterObjectPayloadLength
	case fKeyObjPayloadHash:
		return v2acl.FilterObjectPayloadHash
	case fKeyObjType:
		return v2acl.FilterObjectType
	case fKeyObjHomomorphicHash:
		return v2acl.FilterObjectHomomorphicHash
	}
}

func (k *filterKey) fromString(s string) {
	switch s {
	default:
		k.typ, k.str = 0, s
	case v2acl.FilterObjectVersion:
		k.typ, k.str = fKeyObjVersion, ""
	case v2acl.FilterObjectID:
		k.typ, k.str = fKeyObjID, ""
	case v2acl.FilterObjectContainerID:
		k.typ, k.str = fKeyObjContainerID, ""
	case v2acl.FilterObjectOwnerID:
		k.typ, k.str = fKeyObjOwnerID, ""
	case v2acl.FilterObjectCreationEpoch:
		k.typ, k.str = fKeyObjCreationEpoch, ""
	case v2acl.FilterObjectPayloadLength:
		k.typ, k.str = fKeyObjPayloadLength, ""
	case v2acl.FilterObjectPayloadHash:
		k.typ, k.str = fKeyObjPayloadHash, ""
	case v2acl.FilterObjectType:
		k.typ, k.str = fKeyObjType, ""
	case v2acl.FilterObjectHomomorphicHash:
		k.typ, k.str = fKeyObjHomomorphicHash, ""
	}
}

// NewFilter creates, initializes and returns blank Filter instance.
//
// Defaults:
//   - header type: HeaderTypeUnknown;
//   - matcher: MatchUnknown;
//   - key: "";
//   - value: "".
func NewFilter() *Filter {
	return NewFilterFromV2(new(v2acl.HeaderFilter))
}

// NewFilterFromV2 converts v2 acl.EACLRecord.Filter message to Filter.
func NewFilterFromV2(filter *v2acl.HeaderFilter) *Filter {
	f := new(Filter)

	if filter == nil {
		return f
	}

	f.from = FilterHeaderTypeFromV2(filter.GetHeaderType())
	f.matcher = MatchFromV2(filter.GetMatchType())
	f.key.fromString(filter.GetKey())
	f.value = staticStringer(filter.GetValue())

	return f
}

// Marshal marshals Filter into a protobuf binary form.
func (f *Filter) Marshal() ([]byte, error) {
	return f.ToV2().StableMarshal(nil), nil
}

// Unmarshal unmarshals protobuf binary representation of Filter.
func (f *Filter) Unmarshal(data []byte) error {
	fV2 := new(v2acl.HeaderFilter)
	if err := fV2.Unmarshal(data); err != nil {
		return err
	}

	*f = *NewFilterFromV2(fV2)

	return nil
}

// MarshalJSON encodes Filter to protobuf JSON format.
func (f *Filter) MarshalJSON() ([]byte, error) {
	return f.ToV2().MarshalJSON()
}

// UnmarshalJSON decodes Filter from protobuf JSON format.
func (f *Filter) UnmarshalJSON(data []byte) error {
	fV2 := new(v2acl.HeaderFilter)
	if err := fV2.UnmarshalJSON(data); err != nil {
		return err
	}

	*f = *NewFilterFromV2(fV2)

	return nil
}

// equalFilters compares Filter with each other.
func equalFilters(f1, f2 Filter) bool {
	return f1.From() == f2.From() &&
		f1.Matcher() == f2.Matcher() &&
		f1.Key() == f2.Key() &&
		f1.Value() == f2.Value()
}