package tools

import "strings"

// Tool describes a cryptographic tool and is split into information and logic parts.
type Tool struct {
	// Info is a globally shared instance of generic tool information.
	Info *ToolInfo

	// StaticLogic holds a static (and possibly even nil) value of the tool logic in order to access certain handling methods.
	StaticLogic ToolLogic

	// Factory returns an initialized (but not yet set up) instance of ToolLogic.
	// Setup is done after initialization by overriding Setup().
	Factory func() ToolLogic
}

// ToolInfo holds generic information about a tool.
type ToolInfo struct {
	Name string

	Purpose uint8
	Options []uint8

	KeySize       int // in bytes
	NonceSize     int // in bytes
	SecurityLevel int // approx. attack complexity as 2^n

	Comment string
	Author  string
}

// Tool Purposes.
const (
	// Key Management and Creation, as well as Authenticity.

	// PurposeKeyDerivation declares key derivation capabilities.
	PurposeKeyDerivation uint8 = iota + 1

	// PurposePassDerivation declares password derivation capabilties (make a secure key out of a password).
	// Provides SenderAuthentication, ReceiverAuthentication requirements.
	PurposePassDerivation

	// PurposeKeyExchange declares (DH-style) key exchange capabilities.
	// A trusted key of the receiver must be supplied.
	// Provides ReceiverAuthentication attribute.
	PurposeKeyExchange

	// PurposeKeyEncapsulation declares key encapsulation capabilities (key is encrypted with the receivers public key)
	// A trusted key of the receiver must be supplied.
	// Provides ReceiverAuthentication attribute.
	PurposeKeyEncapsulation

	// PurposeSigning declares signing capabilities.
	// The receiver must already have the public key.
	// Provides SenderAuthentication attribute. Theoretically also provides integrity, but as signing is done after everything else, it will not be able to detect a wrong key during decryption.
	PurposeSigning

	// Confidentiality and Integrity.

	// PurposeIntegratedCipher declares that the tool provides both encryption and integrity verification capabilities.
	// Provies Confidentiality and Integrity requirements.
	PurposeIntegratedCipher

	// PurposeCipher declares that the tool provides encryption capabilities.
	// Provies Confidentiality attribute.
	PurposeCipher

	// PurposeMAC declares that the tool provides integrity verification capabilities.
	// Provies Integrity attribute.
	PurposeMAC
)

// Tool Options.
const (
	// Operation Types.

	// OptionStreaming declares that the tool can work with streaming data and might be given a io.Reader and io.Writer instead of just a []byte slice.
	// TODO: Implementation pending.
	OptionStreaming uint8 = iota + 1

	// Needs.

	// OptionNeedsManagedHasher declares that the tool requires a hashing algorithm to work. It will automatically hash everything that needs to be authenticated and may be shared with other algorithms.
	OptionNeedsManagedHasher

	// OptionNeedsDedicatedHasher declares that the tool requires a hashing algorithm to work. It will get its own instance and will have to do all the work itself.
	OptionNeedsDedicatedHasher

	// OptionNeedsSecurityLevel declares that the tool requires a specified security level. This will be derived from the rest of the used tools, or must be specified by the user directly.
	OptionNeedsSecurityLevel

	// OptionNeedsDefaultKeySize declares that the tool requires a default key size for operation. This will be derived from the rest of the used tools, or must be specified by the user directly.
	OptionNeedsDefaultKeySize

	// OptionHasState declares that the tool has an internal state and requires the setup and reset routines to be run before/after usage. KeyDerivation tools do not have to declare this, their state is handled separately.
	OptionHasState
)

// HasOption returns whether the *ToolInfo has the given option.
func (ti *ToolInfo) HasOption(option uint8) bool {
	for _, optionEntry := range ti.Options {
		if option == optionEntry {
			return true
		}
	}
	return false
}

// With uses the original ToolInfo as a template for a new ToolInfo and returns the new ToolInfo.
func (ti *ToolInfo) With(changes *ToolInfo) *ToolInfo {
	if changes.Name == "" {
		changes.Name = ti.Name
	}
	if changes.Purpose == 0 {
		changes.Purpose = ti.Purpose
	}
	if len(changes.Options) == 0 {
		changes.Options = ti.Options
	}
	if changes.KeySize == 0 {
		changes.KeySize = ti.KeySize
	}
	if changes.NonceSize == 0 {
		changes.NonceSize = ti.NonceSize
	}
	if changes.SecurityLevel == 0 {
		changes.SecurityLevel = ti.SecurityLevel
	}
	if changes.Comment == "" {
		changes.Comment = ti.Comment
	}
	if changes.Author == "" {
		changes.Author = ti.Author
	}

	return changes
}

// FormatPurpose returns the name of the declared purpose.
func (ti *ToolInfo) FormatPurpose() string {
	switch ti.Purpose {
	case PurposeKeyDerivation:
		return "KeyDerivation"
	case PurposePassDerivation:
		return "PassDerivation"
	case PurposeKeyExchange:
		return "KeyExchange"
	case PurposeKeyEncapsulation:
		return "KeyEncapsulation"
	case PurposeSigning:
		return "Signing"
	case PurposeIntegratedCipher:
		return "IntegratedCipher"
	case PurposeCipher:
		return "Cipher"
	case PurposeMAC:
		return "MAC"
	default:
		return "UNKNOWN"
	}
}

// FormatOptions returns a list of names of the declared options.
func (ti *ToolInfo) FormatOptions() string {
	if len(ti.Options) == 0 {
		return ""
	}

	var s []string
	for _, optionID := range ti.Options {
		switch optionID {
		case OptionStreaming:
			s = append(s, "Streaming")
		case OptionNeedsManagedHasher:
			s = append(s, "NeedsManagedHasher")
		case OptionNeedsDedicatedHasher:
			s = append(s, "NeedsDedicatedHasher")
		case OptionNeedsSecurityLevel:
			s = append(s, "NeedsSecurityLevel")
		case OptionNeedsDefaultKeySize:
			s = append(s, "NeedsDefaultKeySize")
		case OptionHasState:
			s = append(s, "HasState")
		default:
			s = append(s, "UNKNOWN")
		}
	}
	return strings.Join(s, ", ")
}