package jess

import (
	"fmt"
	"strings"
)

// Security requirements of a letter.
const (
	Confidentiality uint8 = iota
	Integrity
	RecipientAuthentication
	SenderAuthentication
)

// Requirements describe security properties.
type Requirements struct {
	all []uint8
}

// newEmptyRequirements returns an empty requirements instance.
func newEmptyRequirements() *Requirements {
	return &Requirements{}
}

// NewRequirements returns an attribute instance with all requirements.
func NewRequirements() *Requirements {
	return &Requirements{
		all: []uint8{
			Confidentiality,
			Integrity,
			RecipientAuthentication,
			SenderAuthentication,
		},
	}
}

// Empty returns whether the requirements are empty.
func (requirements *Requirements) Empty() bool {
	return len(requirements.all) == 0
}

// Has returns whether the requirements contain the given attribute.
func (requirements *Requirements) Has(attribute uint8) bool {
	for _, attr := range requirements.all {
		if attr == attribute {
			return true
		}
	}
	return false
}

// Add adds an attribute.
func (requirements *Requirements) Add(attribute uint8) *Requirements {
	if !requirements.Has(attribute) {
		requirements.all = append(requirements.all, attribute)
	}
	return requirements
}

// Remove removes an attribute.
func (requirements *Requirements) Remove(attribute uint8) *Requirements {
	for i, attr := range requirements.all {
		if attr == attribute {
			requirements.all = append(requirements.all[:i], requirements.all[i+1:]...)
			return requirements
		}
	}
	return requirements
}

// CheckComplianceTo checks if the requirements are compliant to the given required requirements.
func (requirements *Requirements) CheckComplianceTo(requirement *Requirements) error {
	var missing *Requirements
	for _, attr := range requirement.all {
		if !requirements.Has(attr) {
			if missing == nil {
				missing = newEmptyRequirements()
			}
			missing.Add(attr)
		}
	}
	if missing != nil {
		return fmt.Errorf("missing security requirements: %s", missing.String())
	}
	return nil
}

// String returns a string representation of the requirements.
func (requirements *Requirements) String() string {
	var names []string
	for _, attr := range requirements.all {
		switch attr {
		case Confidentiality:
			names = append(names, "Confidentiality")
		case Integrity:
			names = append(names, "Integrity")
		case RecipientAuthentication:
			names = append(names, "RecipientAuthentication")
		case SenderAuthentication:
			names = append(names, "SenderAuthentication")
		}
	}
	return strings.Join(names, ", ")
}

// ShortString returns a short string representation of the requirements.
func (requirements *Requirements) ShortString() string {
	var s string
	if requirements.Has(Confidentiality) {
		s += "C"
	}
	if requirements.Has(Integrity) {
		s += "I"
	}
	if requirements.Has(RecipientAuthentication) {
		s += "R"
	}
	if requirements.Has(SenderAuthentication) {
		s += "S"
	}
	return s
}

// SerializeToNoSpec returns the requirements as a negated "No" string.
func (requirements *Requirements) SerializeToNoSpec() string {
	var s string
	if !requirements.Has(Confidentiality) {
		s += "C"
	}
	if !requirements.Has(Integrity) {
		s += "I"
	}
	if !requirements.Has(RecipientAuthentication) {
		s += "R"
	}
	if !requirements.Has(SenderAuthentication) {
		s += "S"
	}
	return s
}

// ParseRequirementsFromNoSpec parses the requirements from a negated "No" string.
func ParseRequirementsFromNoSpec(no string) (*Requirements, error) {
	requirements := NewRequirements()
	for _, id := range no {
		switch id {
		case 'C':
			requirements.Remove(Confidentiality)
		case 'I':
			requirements.Remove(Integrity)
		case 'R':
			requirements.Remove(RecipientAuthentication)
		case 'S':
			requirements.Remove(SenderAuthentication)
		default:
			return nil, fmt.Errorf("unknown attribute identifier: %c", id)
		}
	}
	return requirements, nil
}