package contextGroup

import (
	"fmt"
	"textualize/entities"
)

type ContextGroupCollection struct {
	Groups []entities.LinkedAreaList
}

var contextGroupCollectionInstance *ContextGroupCollection

func GetContextGroupCollection() *ContextGroupCollection {
	if contextGroupCollectionInstance == nil {
		contextGroupCollectionInstance = &ContextGroupCollection{}
	}
	return contextGroupCollectionInstance
}

func SetContextGroupCollection(collection ContextGroupCollection) *ContextGroupCollection {
	contextGroupCollectionInstance = &collection
	return contextGroupCollectionInstance
}

func SetContextGroupCollectionBySerialized(serialized []entities.SerializedLinkedProcessedArea) *ContextGroupCollection {
	newInstance := ContextGroupCollection{}

	newInstance.Groups = append(newInstance.Groups, entities.DeserializeLinkedAreaList(serialized))

	SetContextGroupCollection(newInstance)
	return &newInstance
}

func (collection *ContextGroupCollection) DoesGroupExistBetweenProcessedAreas(ancestorAreaId string, descendantAreaId string) bool {
	ancestorGroup, _ := collection.FindGroupByLinkedProcessedAreaId(ancestorAreaId)
	descendantGroup, _ := collection.FindGroupByLinkedProcessedAreaId(descendantAreaId)

	isAncestorInAnyInGroup := ancestorGroup != nil
	isDescendantInAnyInGroup := descendantGroup != nil
	areBothInAnyInGroup := isAncestorInAnyInGroup && isDescendantInAnyInGroup
	areBothInSameGroup := false
	if areBothInAnyInGroup {
		areBothInSameGroup = ancestorGroup.Id == descendantGroup.Id
	}

	return areBothInSameGroup
}

func (collection *ContextGroupCollection) DisconnectProcessedAreas(ancestorAreaId string, descendantAreaId string) bool {
	doesConnectionExist := collection.DoesGroupExistBetweenProcessedAreas(ancestorAreaId, descendantAreaId)

	if !doesConnectionExist {
		return false
	}

	ancestorGroup, _ := collection.FindGroupByLinkedProcessedAreaId(ancestorAreaId)

	wasRemoved := false
	for i, group := range collection.Groups {
		if group.Id == ancestorGroup.Id {
			collection.Groups = append(collection.Groups[:i], collection.Groups[i+1:]...)
			wasRemoved = true
			break
		}
	}
	return wasRemoved
}

func (collection *ContextGroupCollection) FindGroupById(id string) (*entities.LinkedAreaList, error) {
	found := false
	var foundGroup *entities.LinkedAreaList = nil
	for _, group := range collection.Groups {
		if group.Id == id {
			found = true
			foundGroup = &group
		}
	}
	if !found {
		return nil, fmt.Errorf("ContextGroupCollection.FindGroupById: Group with id %s not found", id)
	}
	return foundGroup, nil
}

func (collection *ContextGroupCollection) FindGroupByLinkedProcessedAreaId(id string) (*entities.LinkedAreaList, error) {
	found := false
	var foundGroup *entities.LinkedAreaList = nil
	for _, group := range collection.Groups {
		for n := group.First(); n != nil && !found; n = n.GetNext() {
			if n.Area.Id == id {
				found = true
				foundGroup = &group
			}
		}
	}
	if !found {
		return nil, fmt.Errorf("ContextGroupCollection.FindGroupByLinkedProcessedAreaId: Group with LinkedProcessedArea.Id %s not found", id)
	}
	return foundGroup, nil
}

func (collection *ContextGroupCollection) ConnectProcessedAreas(ancestorNode entities.ProcessedArea, descendantNode entities.ProcessedArea) bool {
	ancestorGroup, _ := collection.FindGroupByLinkedProcessedAreaId(ancestorNode.Id)
	descendantGroup, _ := collection.FindGroupByLinkedProcessedAreaId(descendantNode.Id)

	isAncestorInAnyInGroup := ancestorGroup != nil
	isDescendantInAnyInGroup := descendantGroup != nil
	isEitherInAnyInGroup := isAncestorInAnyInGroup || isDescendantInAnyInGroup
	areBothInAnyInGroup := isAncestorInAnyInGroup && isDescendantInAnyInGroup
	areBothInSameGroup := false
	if areBothInAnyInGroup {
		areBothInSameGroup = ancestorGroup.Id == descendantGroup.Id
	}

	if areBothInSameGroup {
		return true
	}

	if !isEitherInAnyInGroup {
		collection.createNewGroupAndConnectNodes(ancestorNode, descendantNode)
		return true
	}

	if isAncestorInAnyInGroup && !isDescendantInAnyInGroup {
		ancestorGroup.InsertAfter(ancestorNode.Id, descendantNode)
		return true
	}

	if !isAncestorInAnyInGroup && isDescendantInAnyInGroup {
		descendantGroup.InsertBefore(descendantNode.Id, ancestorNode)
		return true
	}

	return false
}

func (collection *ContextGroupCollection) createNewGroupAndConnectNodes(ancestorNode entities.ProcessedArea, descendantNode entities.ProcessedArea) {
	newGroup := entities.LinkedAreaList{
		Id:         ancestorNode.Id,
		DocumentId: ancestorNode.DocumentId,
		Head:       &entities.LinkedProcessedArea{Area: ancestorNode},
		Tail:       &entities.LinkedProcessedArea{Area: descendantNode},
	}
	newGroup.Head.Next = newGroup.Tail
	newGroup.Tail.Previous = newGroup.Head
	collection.Groups = append(collection.Groups, newGroup)
}
