package acl

import (
	"fmt"
	"strconv"
	"strings"
)

// Basic represents basic part of the NeoFS container's ACL. It includes
// common (pretty simple) access rules for operations inside the container.
// See NeoFS Specification for details.
//
// One can find some similarities with the traditional Unix permission, such as
//
//	division into scopes: user, group, others
//	op-permissions: read, write, etc.
//	sticky bit
//
// However, these similarities should only be used for better understanding,
// in general these mechanisms are different.
//
// Instances can be created using built-in var declaration, but look carefully
// at the default values, and how individual permissions are regulated.
// Some frequently used values are presented in exported variables.
//
// Basic instances are comparable: values can be compared directly using
// == operator.
//
// Note that type conversion from- and to numerical types is not recommended,
// use corresponding constants and/or methods instead.
type Basic uint32

// FromBits decodes Basic from the numerical representation.
//
// See also Bits.
func (x *Basic) FromBits(bits uint32) {
	*x = Basic(bits)
}

// Bits returns numerical encoding of Basic.
//
// See also FromBits.
func (x Basic) Bits() uint32 {
	return uint32(x)
}

// common bit sections.
const (
	opAmount  = 7
	bitsPerOp = 4

	bitPosFinal  = opAmount * bitsPerOp
	bitPosSticky = bitPosFinal + 1
)

// per-op bit order.
const (
	opBitPosBearer uint8 = iota
	opBitPosOthers
	opBitPosContainer
	opBitPosOwner
)

// DisableExtension makes Basic FINAL. FINAL indicates the ACL non-extendability
// in the related container.
//
// See also Extendable.
func (x *Basic) DisableExtension() {
	setBit((*uint32)(x), bitPosFinal)
}

// Extendable checks if Basic is NOT made FINAL using DisableExtension.
//
// Zero Basic is extendable.
func (x Basic) Extendable() bool {
	return !isBitSet(uint32(x), bitPosFinal)
}

// MakeSticky makes Basic STICKY. STICKY indicates that only the owner of any
// particular object is allowed to operate on it.
//
// See also Sticky.
func (x *Basic) MakeSticky() {
	setBit((*uint32)(x), bitPosSticky)
}

// Sticky checks if Basic is made STICKY using MakeSticky.
//
// Zero Basic is NOT STICKY.
func (x Basic) Sticky() bool {
	return isBitSet(uint32(x), bitPosSticky)
}

// checks if op is used by the storage nodes within replication mechanism.
func isReplicationOp(op Op) bool {
	switch op {
	default:
		return false
	case
		OpObjectGet,
		OpObjectHead,
		OpObjectPut,
		OpObjectSearch,
		OpObjectHash:
		return true
	}
}

// AllowOp allows the parties with the given role to the given operation.
// Op MUST be one of the Op enumeration. Role MUST be one of:
//
//	RoleOwner
//	RoleContainer
//	RoleOthers
//
// and if role is RoleContainer, op MUST NOT be:
//
//	OpObjectGet
//	OpObjectHead
//	OpObjectPut
//	OpObjectSearch
//	OpObjectHash
//
// See also IsOpAllowed.
func (x *Basic) AllowOp(op Op, role Role) {
	var bitPos uint8

	switch role {
	default:
		panic(fmt.Sprintf("unable to set rules for unsupported role %v", role))
	case RoleInnerRing:
		panic("basic ACL MUST NOT be modified for Inner Ring")
	case RoleOwner:
		bitPos = opBitPosOwner
	case RoleContainer:
		if isReplicationOp(op) {
			panic("basic ACL for container replication ops MUST NOT be modified")
		}

		bitPos = opBitPosContainer
	case RoleOthers:
		bitPos = opBitPosOthers
	}

	setOpBit((*uint32)(x), op, bitPos)
}

// IsOpAllowed checks if parties with the given role are allowed to the given op
// according to the Basic rules. Op MUST be one of the Op enumeration.
// Role MUST be one of the Role enumeration.
//
// Members with RoleContainer role have exclusive default access to the
// operations of the data replication mechanism:
//
//	OpObjectGet
//	OpObjectHead
//	OpObjectPut
//	OpObjectSearch
//	OpObjectHash
//
// RoleInnerRing members are allowed to data audit ops only:
//
//	OpObjectGet
//	OpObjectHead
//	OpObjectHash
//	OpObjectSearch
//
// Zero Basic prevents any role from accessing any operation in the absence
// of default rights.
//
// See also AllowOp.
func (x Basic) IsOpAllowed(op Op, role Role) bool {
	var bitPos uint8

	switch role {
	default:
		panic(fmt.Sprintf("role is unsupported %v", role))
	case RoleInnerRing:
		switch op {
		case
			OpObjectGet,
			OpObjectHead,
			OpObjectHash,
			OpObjectSearch:
			return true
		default:
			return false
		}
	case RoleOwner:
		bitPos = opBitPosOwner
	case RoleContainer:
		if isReplicationOp(op) {
			return true
		}

		bitPos = opBitPosContainer
	case RoleOthers:
		bitPos = opBitPosOthers
	}

	return isOpBitSet(uint32(x), op, bitPos)
}

// AllowBearerRules allows bearer to provide extended ACL rules for the given
// operation. Bearer rules doesn't depend on container ACL extensibility.
//
// See also AllowedBearerRules.
func (x *Basic) AllowBearerRules(op Op) {
	setOpBit((*uint32)(x), op, opBitPosBearer)
}

// AllowedBearerRules checks if bearer rules are allowed using AllowBearerRules.
// Op MUST be one of the Op enumeration.
//
// Zero Basic disallows bearer rules for any op.
func (x Basic) AllowedBearerRules(op Op) bool {
	return isOpBitSet(uint32(x), op, opBitPosBearer)
}

// EncodeToString encodes Basic into hexadecimal string.
//
// See also DecodeString.
func (x Basic) EncodeToString() string {
	return strconv.FormatUint(uint64(x), 16)
}

// Names of the frequently used Basic values.
const (
	NamePrivate              = "private"
	NamePrivateExtended      = "eacl-private"
	NamePublicRO             = "public-read"
	NamePublicROExtended     = "eacl-public-read"
	NamePublicRW             = "public-read-write"
	NamePublicRWExtended     = "eacl-public-read-write"
	NamePublicAppend         = "public-append"
	NamePublicAppendExtended = "eacl-public-append"
)

// Frequently used Basic values. Bitmasks are taken from the NeoFS Specification.
const (
	Private              = Basic(0x1C8C8CCC) // private
	PrivateExtended      = Basic(0x0C8C8CCC) // eacl-private
	PublicRO             = Basic(0x1FBF8CFF) // public-read
	PublicROExtended     = Basic(0x0FBF8CFF) // eacl-public-read
	PublicRW             = Basic(0x1FBFBFFF) // public-read-write
	PublicRWExtended     = Basic(0x0FBFBFFF) // eacl-public-read-write
	PublicAppend         = Basic(0x1FBF9FFF) // public-append
	PublicAppendExtended = Basic(0x0FBF9FFF) // eacl-public-append
)

// DecodeString decodes string calculated using EncodeToString. Also supports
// human-readable names (Name* constants).
func (x *Basic) DecodeString(s string) (e error) {
	switch s {
	case NamePrivate:
		*x = Private
	case NamePrivateExtended:
		*x = PrivateExtended
	case NamePublicRO:
		*x = PublicRO
	case NamePublicROExtended:
		*x = PublicROExtended
	case NamePublicRW:
		*x = PublicRW
	case NamePublicRWExtended:
		*x = PublicRWExtended
	case NamePublicAppend:
		*x = PublicAppend
	case NamePublicAppendExtended:
		*x = PublicAppendExtended
	default:
		s = strings.TrimPrefix(strings.ToLower(s), "0x")

		v, err := strconv.ParseUint(s, 16, 32)
		if err != nil {
			return fmt.Errorf("parse hex: %w", err)
		}

		*x = Basic(v)
	}

	return nil
}