package entity

import (
	"errors"
)

const ENTITYSTRING = "entity"
const RELATIONSTRING = "relation"
const GROUPBYSTRING = "groupBy"

/*
sliceContains checks if a slice contains the input
	JSONQuery: IncomingQueryJSON, the query in JSON to be validated
	Return: []error, a list of specific transgression in this specific JSON query'
*/
func ValidateStruct(JSONQuery IncomingQueryJSON) []error {
	ret := make([]error, 0)
	minEntityID, maxEntityID := getMinAndMaxEntityID(JSONQuery.Entities)
	minRelationID, maxRelationID := getMinAndMaxRelationID(JSONQuery.Relations)
	minGroupByID, maxGroupByID := getMinAndMaxGroupByID(JSONQuery.GroupBys)
	ret = append(ret, getIllegalToFromInRelation(JSONQuery, minEntityID, maxEntityID, minGroupByID, maxGroupByID)...)
	if len(JSONQuery.GroupBys) != 0 {
		ret = append(ret, getIllegalToFromInGroupBy(JSONQuery, minEntityID, maxEntityID, minRelationID, maxRelationID)...)
	}
	if len(ret) == 0 {
		ret = append(ret, separatedChainExists(JSONQuery)...)
	}

	return ret
}

func getIllegalToFromInRelation(JSONQuery IncomingQueryJSON, minEntityID int, maxEntityID int, minGroupByID int, maxGroupByID int) []error {
	ret := make([]error, 0)
	for _, rel := range JSONQuery.Relations {
		toEntityValid := relationToValid(rel, ENTITYSTRING, minEntityID, maxEntityID)
		toGroupByValid := relationToValid(rel, GROUPBYSTRING, minGroupByID, maxGroupByID)
		fromEntityValid := relationFromValid(rel, ENTITYSTRING, minEntityID, maxEntityID)
		fromGroupByValid := relationFromValid(rel, GROUPBYSTRING, minGroupByID, maxGroupByID)
		if !(toEntityValid || toGroupByValid) {
			err := errors.New("relation has invalid TO type and/or ID")
			ret = append(ret, err)
		}
		if !(fromEntityValid || fromGroupByValid) {
			err := errors.New("relation has invalid FROM type and/or ID")
			ret = append(ret, err)
		}
	}
	return ret
}

func getIllegalToFromInGroupBy(JSONQuery IncomingQueryJSON, minEntityID int, maxEntityID int, minRelationID int, maxRelationID int) []error {
	ret := make([]error, 0)
	for _, groupBy := range JSONQuery.GroupBys {
		groupEntityValid := groupByGroupValid(groupBy, ENTITYSTRING, minEntityID, maxEntityID)
		groupRelationValid := groupByGroupValid(groupBy, RELATIONSTRING, minRelationID, maxRelationID)
		byEntityValid := groupByByValid(groupBy, ENTITYSTRING, minEntityID, maxEntityID)
		byRelationValid := groupByByValid(groupBy, RELATIONSTRING, minRelationID, maxRelationID)
		if !(groupEntityValid || groupRelationValid) {
			err := errors.New("relation has invalid TO type and/or ID")
			ret = append(ret, err)
		}
		if !(byEntityValid || byRelationValid) {
			err := errors.New("relation has invalid FROM type and/or ID")
			ret = append(ret, err)
		}
	}
	return ret
}

/*
Checks if a relation.ToType and relation.ToID are valid
	Return: bool, whether the ToType and ToID are valid
*/
func relationToValid(rel QueryRelationStruct, typeString string, minID int, maxID int) bool {
	if rel.ToType == typeString && rel.ToID >= minID && rel.ToID <= maxID {
		return true
	}
	return false
}

/*
Checks if a relation.FromType and relation.FromID are valid
	Return: bool, whether the FromType and FromID are valid
*/
func relationFromValid(rel QueryRelationStruct, typeString string, minID int, maxID int) bool {
	if rel.FromType == typeString && rel.FromID >= minID && rel.FromID <= maxID {
		return true
	}
	return false
}

func groupByGroupValid(groupBy QueryGroupByStruct, typeString string, minID int, maxID int) bool {
	if groupBy.GroupType == typeString && groupBy.GroupID >= minID && groupBy.GroupID <= maxID {
		return true
	}
	return false
}

func groupByByValid(groupBy QueryGroupByStruct, typeString string, minID int, maxID int) bool {
	if groupBy.ByType == typeString && groupBy.ByID >= minID && groupBy.ByID <= maxID {
		return true
	}
	return false
}

func getMinAndMaxEntityID(entities []QueryEntityStruct) (int, int) {
	min := 65535
	max := -65535
	for _, e := range entities {
		if e.ID < min {
			min = e.ID
		}
		if e.ID > max {
			max = e.ID
		}
	}
	// If the min/max values didn't change the query would be invalid, and all consequent validationsteps will fail
	return min, max
}

func getMinAndMaxRelationID(relations []QueryRelationStruct) (int, int) {
	min := 65535
	max := -65535
	for _, e := range relations {
		if e.ID < min {
			min = e.ID
		}
		if e.ID > max {
			max = e.ID
		}
	}
	// If the min/max values didn't change the query would be invalid, and all consequent validationsteps will fail

	return min, max
}

func getMinAndMaxGroupByID(groupBys []QueryGroupByStruct) (int, int) {
	min := 65535
	max := -65535
	for _, e := range groupBys {
		if e.ID < min {
			min = e.ID
		}
		if e.ID > max {
			max = e.ID
		}
	}
	// If the min/max values didn't change the query would be invalid, and all consequent validationsteps will fail

	return min, max
}

// Get chains that are not connected to the main chain
func separatedChainExists(JSONQuery IncomingQueryJSON) []error {
	ret := make([]error, 0)
	entityCount := len(JSONQuery.Entities)
	relationCount := len(JSONQuery.Relations)
	// A relation always has 2 entities if it's on its own
	// If there is a chain (e-r-e-r-e) then there will be one more entity than relation
	// If there is two seperate chains you will always end up adding at least 2 entities more than relations
	// (e-r-e-r-e) and (e-r-e) has 3 relations but 5 entities, thus we know they are separate
	if relationCount != entityCount-1 {
		err := errors.New("separated chain found")
		ret = append(ret, err)
	}
	return ret
}