package ecs

// ECS system by jcerise, github.com/jcerise/gogue

import (
	"lab.zaar.be/thefish/alchemyst-go/util/appctx"
	"sort"
)

type Controller struct {
	systems map[string]System
	sortedSystems map[int][]System
	priorityKeys []int
	nextEntityID Entity
	components map[string][]Entity
	entities map[Entity]map[string]Component
	deadEntities []int

	// The component map will keep track of what components are available
	componentMap map[string]Component
}

// NewController is a convenience/constructor method to properly initialize a new processor
func NewController() *Controller {
	controller := Controller{}
	controller.systems = make(map[string]System)
	controller.sortedSystems = make(map[int][]System)
	controller.priorityKeys = []int{}
	controller.nextEntityID = 0
	controller.components = make(map[string][]Entity)
	controller.entities = make(map[Entity]map[string]Component)
	controller.deadEntities = []int{}
	controller.componentMap = make(map[string]Component)

	return &controller
}

// Create a new entity in the world. An entity is simply a unique integer.
// If any components are provided, they will be associated with the created entity
func (c *Controller) CreateEntity(components []Component) Entity {
	c.nextEntityID += 1

	if len(components) > 0 {
		for _, v := range components {
			c.AddComponent(c.nextEntityID, v)
		}
	}

	c.entities[c.nextEntityID] = make(map[string]Component)

	return c.nextEntityID
}

// DeleteEntity removes an entity, all component instances attached to that entity, and any components associations with
// that entity
func (c *Controller) DeleteEntity(entity Entity) {
	// First, delete all the component associations for the entity to be removed
	for k, _ := range c.entities[entity] {
		c.RemoveComponent(entity, k)
	}

	// Then, delete the entity itself. The components have already been removed and disassociated with it, so a simple
	// delete will do here
	delete(c.entities, entity)
}

// MapComponent registers a component with the controller. This map of components gives the controller access to the
// valid components for a game system, and allows for dynamic loading of components from the data loader.
func (c *Controller) MapComponentClass(componentName string, component Component) {
	// TODO: Possible to overwrite existing components with old name...
	c.componentMap[componentName] = component
}

// GetMappedComponentClass returns a component class based on the name it was registered under. This allows for dyanamic
// mapping of components to entities, for example, from the data loader.
func (c *Controller) GetMappedComponentClass(componentName string) Component {
	if _, ok := c.componentMap[componentName]; ok {
		return c.componentMap[componentName]
	} else {
		// TODO: Add better (read: actual) error handling here
		appctx.Logger().Warn().Msgf("Component[%s] not registered on Controller.\n", componentName)
		return nil
	}
}

// AddComponent adds a component to an entity. The component is added to the global list of components for the
// processor, and also directly associated with the entity itself. This allows for flexible checking of components,
// as you can check which entites are associated with a component, and vice versa.
func (c *Controller) AddComponent(entity Entity, component Component) {
	// First, get the type of the component
	componentType := component.Type()

	// Record that the component type is associated with the entity.
	c.components[componentType] = append(c.components[componentType], entity)

	// Now, check to see if the entity is already tracked in the controller entity list. If it is not, add it, and
	// associate the component with it
	if _, ok := c.entities[entity]; !ok {
		c.entities[entity] = make(map[string]Component)
	}

	c.entities[entity][componentType] = component
}

// HasComponent checks a given entity to see if it has a given component associated with it
func (c *Controller) HasComponent(entity Entity, componentType string) bool {
	if _, ok := c.entities[entity][componentType]; ok {
		return true
	} else {
		return false
	}
}

// GetComponent returns the component instance for a component type, if one exists for the provided entity
func (c *Controller) GetComponent(entity Entity, componentType string) Component {
	// Check the given entity has the provided component
	if c.HasComponent(entity, componentType) {
		return c.entities[entity][componentType]
	}

	return nil
}

// GetEntity gets a specific entity, and all of its component instances
func (c *Controller) GetEntity(entity Entity) map[string]Component {
	for i, _ := range c.entities {
		if i == entity {
			return c.entities[entity]
		}
	}

	return nil
}

// GetEntities returns a map of all entities and their component instances
func (c *Controller) GetEntities() map[Entity]map[string]Component {
	return c.entities
}

// GetEntitiesWithComponent returns a list of all entities with a given component attached
// TODO: Allow for passing a list of components
func (c *Controller) GetEntitiesWithComponent(componentType string) []Entity {
	entitiesWithComponent := make([]Entity, 0)
	for entity := range c.entities {
		if c.HasComponent(entity, componentType) {
			entitiesWithComponent = append(entitiesWithComponent, entity)
		}
	}

	return entitiesWithComponent
}

// UpdateComponent updates a component on an entity with a new version of the same component
func (c *Controller) UpdateComponent(entity Entity, componentType string, newComponent Component) Entity {
	// First, remove the component in question (Don't actually update things, but rather remove and replace)
	c.RemoveComponent(entity, componentType)

	// Next, replace the removed component with the updated one
	c.AddComponent(entity, newComponent)

	return entity
}

// DeleteComponent will delete a component instance from an entity, based on component type. It will also remove the
// association between the component and the entity, and remove the component from the processor completely if no
// other entities are using it.
func (c *Controller) RemoveComponent(entity Entity, componentType string) Entity {
	// Find the index of the entity to operate on in the components slice
	index := -1
	for i, v := range c.components[componentType] {
		if (v == entity) {
			index = i
		}
	}

	// If the component was found on the entity, remove the association between the component and the entity
	if index != -1 {
		c.components[componentType] = append(c.components[componentType][:index], c.components[componentType][index+1:]...)
		// If this was the last entity associated with the component, remove the component entry as well
		if len(c.components[componentType]) == 0 {
			delete(c.components, componentType)
		}
	}

	// Now, remove the component instance from the entity
	delete(c.entities[entity], componentType)

	return entity
}


// AddSystem registers a system to the controller. A priority can be provided, and systems will be processed in
// numeric order, low to high. If multiple systems are registered as the same priority, they will be randomly run within
// that priority group.
func (c *Controller) AddSystem(system System, priority int) {
	systemType := system.SystemType()

	if _, ok := c.systems[systemType]; !ok {
		// A system of this type has not been added yet, so add it to the systems list
		c.systems[systemType] = system

		// Now, append the system to a special list that will be used for sorting by priority
		if !IntInSlice(priority, c.priorityKeys) {
			c.priorityKeys = append(c.priorityKeys, priority)
		}
		c.sortedSystems[priority] = append(c.sortedSystems[priority], system)
		sort.Ints(c.priorityKeys)
	} else {
		appctx.Logger().Warn().Msgf("A system of type %v was already added to the controller %v!", systemType, c)
	}
}

func (c *Controller) GetSystem(systemType string) System {
	if system, ok := c.systems[systemType]; ok {
		return system
	}
	return nil
}

// Process kicks off system processing for all systems attached to the controller. Systems will be processed in the
// order they are found, or if they have a priority, in priority order. If there is a mix of systems with priority and
// without, systems with priority will be processed first (in order).
func (c *Controller) Process(excludedSystems []string) {
	for _, key := range c.priorityKeys {
		for _, system := range c.sortedSystems[key] {
			systemType := system.SystemType()

			// Check if the current system type was marked as excluded on this call. If it was, not process it.
			if !TypeInSlice(systemType, excludedSystems) {
				system.Process()
			}
		}
	}
}

// HasSystem checks the controller to see if it has a given system associated with it
func (c *Controller) HasSystem(systemType string) bool {
	if _, ok := c.systems[systemType]; ok {
		return true
	} else {
		return false
	}
}

// ProcessSystem allows for on demand processing of individual systems, rather than processing all at once via Process
func (c *Controller) ProcessSystem(systemType string) {
	if c.HasSystem(systemType) {
		system := c.systems[systemType]
		system.Process()
	}
}