package entity

import (
	"errors"
	"strings"
)

const (
	// Separator used to separate the namespace and the ID
	sep = ":"
	// NamespaceUser is the recommended namespace for user entities
	NamespaceUser = "user"
	// NamespacePost is the recommended namespace for post entities
	NamespacePost = "post"
	// NamespaceComment is the recommended namespace for comment entities
	NamespaceComment = "comment"
	// NamespaceShare is the recommended namespace for share entities
	NamespaceShare = "share"
	// NamespaceFollow is the recommended namespace for follow entities
	NamespaceFollow = "follow"
	// NamespaceFriend is the recommended namespace for friend entities
	NamespaceFriend = "friend"
)

// Entity is a namespaced ID
type Entity struct {
	namespace string
	id        string
}

// Decode takes a string and returns the entity that it represents.
// If the string does not have a namespace it will return an error
func Decode(value string) (Entity, error) {
	entity := Entity{}
	err := entity.decodeString(value)
	return entity, err
}

// EncodeAll takes an array of entities and encodes them all
func EncodeAll(entities []Entity) []string {
	encodedEntities := make([]string, len(entities))
	for i, entity := range entities {
		encodedEntities[i] = entity.Encode()
	}
	return encodedEntities
}

// New creates an entity from the given namespace and ID
func New(namespace string, id string) Entity {
	return Entity{
		namespace: namespace,
		id:        id,
	}
}

// Namespace identifies the owner of the entity
func (e Entity) Namespace() string {
	return e.namespace
}

// ID uniquely identifies the entity within the specified namespace
func (e Entity) ID() string {
	return e.id
}

// String returns a display-friendly version of the entity.  Use Encode instead if meant to be decoded later
func (e Entity) String() string {
	return e.namespace + sep + e.id
}

// Encode returns a encoded Entity ready to be transmitted as a string
func (e Entity) Encode() string {
	return e.namespace + sep + e.id
}

// Common function used by both Decode and UnmarshalText to decode an entity
func (e *Entity) decodeString(value string) error {
	index := strings.Index(value, sep)
	if index == -1 {
		return errors.New("entity: invalid entity " + value)
	}
	e.namespace = value[:index]
	e.id = value[index+1:]
	return nil
}

// MarshalText implements TextMarshaler to support JSON encoding
func (e Entity) MarshalText() ([]byte, error) {
	return []byte(e.Encode()), nil
}

// UnmarshalText implements TextUnmarshaler to support JSON decoding
// This needs to be a pointer because it modifies the object itself
func (e *Entity) UnmarshalText(text []byte) error {
	value := string(text)
	return e.decodeString(value)
}
