package aql

import (
	"fmt"
	"strconv"

	"git.science.uu.nl/graphpolaris/query-conversion/entity"
)

// We use consts to define string to prevent typos
const ENTITYSTRING = "entity"
const RELATIONSTRING = "relation"
const GROUPBYSTRING = "groupBy"
const FILTERSTRING = "filter"

var reldone map[int]bool
var entdone map[int]bool
var funcdone map[int]bool
var relfuncdone map[int]bool
var filterDone map[int]bool

func search(JSONQuery *entity.IncomingQueryJSON, index int) []entity.PdictList {
	var listoflists []entity.PdictList
	listoflists = []entity.PdictList{}
	reldone = make(map[int]bool)
	entdone = make(map[int]bool)
	funcdone = make(map[int]bool)
	relfuncdone = make(map[int]bool)
	filterDone = make(map[int]bool)
	var s entity.PdictList
	//printSlice(s)
	//layercounter = 0

	initent := makePdict(ENTITYSTRING, index)

	s = append(s, initent)
	listoflists = append(listoflists, s)
	listoflists = entToRel(JSONQuery, listoflists, initent)

	for i := range listoflists {
		for j := range listoflists[i] {
			fmt.Println(listoflists[i][j])
		}
		fmt.Println("")
	}
	fmt.Println(listoflists)
	return listoflists
}

/*
relToEnt Get the entities connected to a relation and recursivly constructs part of the hierarchy
Entities always get added IN FRONT OF their respective relation in the hierarchy
	JSONQuery: *entity.IncomingQueryJSON, the query in JSON format
	rel: pdict, the relation to find all connected entities for
*/
func relToEnt(JSONQuery *entity.IncomingQueryJSON, listoflists []entity.PdictList, rel entity.Pdict) []entity.PdictList {
	var newlist entity.PdictList
	layercounter := findCurrentLayer(listoflists, rel)
	// Loop over all entities
	// If an entity is already in the entdone dict we already added it to the hierarchy, so we don't have to add it again
	// If an entity matches either the from or to in a relation we can add it to the newlist
	for i := range JSONQuery.Entities {
		if _, ok := entdone[i]; !ok {
			if JSONQuery.Relations[rel.Pointer].FromID == i && JSONQuery.Relations[rel.Pointer].FromType == ENTITYSTRING {
				fromentity := makePdict(ENTITYSTRING, i)
				newlist = append(newlist, fromentity)
			} else if JSONQuery.Relations[rel.Pointer].ToID == i && JSONQuery.Relations[rel.Pointer].ToType == ENTITYSTRING {
				toentity := makePdict(ENTITYSTRING, i)
				newlist = append(newlist, toentity)
			}
		}
	}
	// This relation has found all its entities so we can set it's ID to true
	reldone[rel.Pointer] = true
	// If the newlist is empty, we can just skip the recursion
	// This is effectively our base case
	if len(newlist) != 0 {
		// If our layercounter is equal to 0 we are in the first "layer" of the hierarchy
		// Because we add the entities IN FRONT OF their respective relation we don't have to move the layercounter before prepending
		// If our layercounter is not equal to 0 we lower the layercounter and then add each item to the newly selected layer
		listoflists = aboveAppend(listoflists, layercounter, newlist)

		// After getting a list of entities we can only go towards a list of relation
		// So we recurse by calling EntToRel
		for i := range newlist {
			fmt.Println("EntToRel being called with index?: " + strconv.Itoa(newlist[i].Pointer))
			listoflists = entToRel(JSONQuery, listoflists, newlist[i])

		}
	}

	return listoflists

}

/*
entToRel Get the relations connected to a entity and recursivly constructs part of the hierarchy
Relation always get added BEHIND their respective entity in the hierarchy
	JSONQuery: *entity.IncomingQueryJSON, the query in JSON format
	ent: pdict, the entity to find all connected relations for
*/
func entToRel(JSONQuery *entity.IncomingQueryJSON, listoflists []entity.PdictList, ent entity.Pdict) []entity.PdictList {
	var newlist entity.PdictList
	layercounter := findCurrentLayer(listoflists, ent)
	// Loop over all relations
	// If a relation is already in the reldone dict we already added it to the hierarchy, so we don't have to add it again
	// If a relation matches either the from or to with the entity we can add it to the newlist
	for i := range JSONQuery.Relations {
		if _, ok := reldone[i]; !ok {
			if JSONQuery.Relations[i].FromID == ent.Pointer && JSONQuery.Relations[i].FromType == ENTITYSTRING {
				rel := makePdict(RELATIONSTRING, i)
				newlist = append(newlist, rel)
			} else if JSONQuery.Relations[i].ToID == ent.Pointer && JSONQuery.Relations[i].ToType == ENTITYSTRING {
				rel := makePdict(RELATIONSTRING, i)
				newlist = append(newlist, rel)
			}
		}
	}
	// This entity has found all its relations so we can set it's ID to true
	entdone[ent.Pointer] = true

	if len(newlist) != 0 {
		// If our layercounter is equal to the length of the hierarchy - 1 we are in the last "layer" of the hierarchy
		// Because we add the relations BEHIND their respective entities we don't have to move the layercounter before appending
		// TODO TAKE OUT UNNEEDED LAYERCOUNTER INCREMENTS AND DECREMENTS
		// If our layercounter is any other value we increase the layercounter and then add each item to the newly selected layer
		listoflists = belowAppend(listoflists, layercounter, newlist)

		// After getting a list of relations we can only go towards a list of entities or a list of functions
		// So we recurse by calling RelToEnt and RelToAllFunc
		for i := range newlist {
			fmt.Println("RelToEnt being called with index?: " + strconv.Itoa(newlist[i].Pointer))
			listoflists = relToEnt(JSONQuery, listoflists, newlist[i])
			fmt.Println("RelToAllFunc being called with index?: " + strconv.Itoa(newlist[i].Pointer))
			listoflists = relToAllFunc(JSONQuery, listoflists, newlist[i])

		}
	}
	return listoflists
}

/*
relToAllFunc Get the functions connected (both functions that are applied to a subquery a relation is a part of and functions the relation is connected to itself)
 to a relation and recursivly constructs part of the hierarchy
If a function is applied to a subquery the relation is a part of, we add it BEHIND its respective relation
If a function is connected to a relation (relation uses the results from the function), we add it IN FRONT OF its respective relation
	JSONQuery: *entity.IncomingQueryJSON, the query in JSON format
	rel: pdict, the relation to find all connected functions for
*/
func relToAllFunc(JSONQuery *entity.IncomingQueryJSON, listoflists []entity.PdictList, rel entity.Pdict) []entity.PdictList {
	var funcappliedtosubquery entity.PdictList
	var functowhichrelapplies entity.PdictList
	layercounter := findCurrentLayer(listoflists, rel)
	// Loop over all functions
	// If a relation is already in the relfuncdone dict we already added it to the hierarchy, so we don't have to add it again
	// If a function's relationID matches the current relation then the function is applied to a subquery
	// If the relation's functionpointer matches a function's ID then the relation is connected to the function
	// Depending on the case they get put in a different list and are put in different places in the hierarchy
	for i := range JSONQuery.GroupBys {
		if _, ok := relfuncdone[rel.Pointer]; !ok {
			if _, ok := funcdone[i]; !ok {
				if JSONQuery.GroupBys[i].RelationID == rel.Pointer {
					relfunc := makePdict(GROUPBYSTRING, i)
					funcappliedtosubquery = append(funcappliedtosubquery, relfunc)
					fmt.Println("I AM HERE 1")
				}

				if JSONQuery.Relations[rel.Pointer].FromID == i && JSONQuery.Relations[rel.Pointer].FromType == GROUPBYSTRING {
					fromfunc := makePdict(GROUPBYSTRING, i)
					functowhichrelapplies = append(functowhichrelapplies, fromfunc)
					fmt.Println("I AM HERE 2")

				} else if JSONQuery.Relations[rel.Pointer].ToID == i && JSONQuery.Relations[rel.Pointer].ToType == GROUPBYSTRING {
					tofunc := makePdict(GROUPBYSTRING, i)
					functowhichrelapplies = append(functowhichrelapplies, tofunc)
					fmt.Println("I AM HERE 3")

				}

			}
		}

	}
	relfuncdone[rel.Pointer] = true
	layercountertwo := layercounter
	layercounterthree := layercounter
	// See main function comment to see which sublist gets put where in the hierarchy
	if len(functowhichrelapplies) != 0 {
		listoflists = aboveAppend(listoflists, layercountertwo, functowhichrelapplies)

		for i := range functowhichrelapplies {
			fmt.Println("FuncToAllRell being called with index?: " + strconv.Itoa(functowhichrelapplies[i].Pointer))
			listoflists = funcToAllRel(JSONQuery, listoflists, functowhichrelapplies[i])

		}
	}

	if len(funcappliedtosubquery) != 0 {
		//newlayercounter := layercounter
		listoflists = belowAppend(listoflists, layercounterthree, funcappliedtosubquery)

		for i := range funcappliedtosubquery {
			fmt.Println("FuncToAllRel being called with index?: " + strconv.Itoa(funcappliedtosubquery[i].Pointer))
			listoflists = funcToAllRel(JSONQuery, listoflists, funcappliedtosubquery[i])
		}
	}
	return listoflists
}

/*
funcToAllRel Get the relations connected (both relations that are in a subquery a function is applied to and relations that are connected to the function itself)
 to a function and recursivly constructs part of the hierarchy
If a relation is in a subquery that the function is applied to, we add the relation IN FRONT OF its respective function
If a relation is connected to a function, we add the relation BEHIND its respective function
	JSONQuery: *entity.IncomingQueryJSON, the query in JSON format
	function: pdict, the function to find all connected relations for
*/
func funcToAllRel(JSONQuery *entity.IncomingQueryJSON, listoflists []entity.PdictList, function entity.Pdict) []entity.PdictList {
	var funcappliedtosubquery entity.PdictList
	var relattachedtofunc entity.PdictList
	layercounter := findCurrentLayer(listoflists, function)
	for i := range JSONQuery.Relations {
		if _, ok := funcdone[function.Pointer]; !ok {
			if _, ok := relfuncdone[i]; !ok {
				// The func is attached to this relation
				if JSONQuery.GroupBys[function.Pointer].RelationID == i {
					funcrel := makePdict(RELATIONSTRING, i)
					funcappliedtosubquery = append(funcappliedtosubquery, funcrel)

				}

				if JSONQuery.Relations[i].FromID == function.Pointer && JSONQuery.Relations[i].FromType == GROUPBYSTRING {
					fromrel := makePdict(RELATIONSTRING, i)
					relattachedtofunc = append(relattachedtofunc, fromrel)

				} else if JSONQuery.Relations[i].ToID == function.Pointer && JSONQuery.Relations[i].ToType == GROUPBYSTRING {
					torel := makePdict(RELATIONSTRING, i)
					relattachedtofunc = append(relattachedtofunc, torel)

				}

			}
		}
	}
	funcdone[function.Pointer] = true

	layercountertwo := layercounter
	layercounterthree := layercounter
	if len(funcappliedtosubquery) != 0 {
		//newlayercounter := layercounter
		listoflists = aboveAppend(listoflists, layercountertwo, funcappliedtosubquery)

		for i := range funcappliedtosubquery {
			fmt.Println("RelToEnt being called with index?: " + strconv.Itoa(funcappliedtosubquery[i].Pointer))
			listoflists = relToEnt(JSONQuery, listoflists, funcappliedtosubquery[i])
			fmt.Println("RelToAllFunc being called with index?: " + strconv.Itoa(funcappliedtosubquery[i].Pointer))
			listoflists = relToAllFunc(JSONQuery, listoflists, funcappliedtosubquery[i])

		}
	}

	if len(relattachedtofunc) != 0 {
		listoflists = belowAppend(listoflists, layercounterthree, relattachedtofunc)

		for i := range relattachedtofunc {
			fmt.Println("RelToEnt being called with index?: " + strconv.Itoa(relattachedtofunc[i].Pointer))
			listoflists = relToEnt(JSONQuery, listoflists, relattachedtofunc[i])
			fmt.Println("RelToAllFunc being called with index?: " + strconv.Itoa(relattachedtofunc[i].Pointer))
			listoflists = relToAllFunc(JSONQuery, listoflists, relattachedtofunc[i])
		}
	}
	return listoflists
}

// A function that appends 1 level above (if index is 0 this won't work)
func aboveAppend(listoflists []entity.PdictList, index int, values entity.PdictList) []entity.PdictList {
	if index == 0 {
		return prepend(listoflists, values)
	} else {
		index--
		for i := range values {
			listoflists[index] = append(listoflists[index], values[i])
		}
		return listoflists
	}

}

// A function that appends 1 level below
func belowAppend(listoflists []entity.PdictList, index int, values entity.PdictList) []entity.PdictList {
	if index == len(listoflists)-1 {
		listoflists = append(listoflists, values)
		return listoflists

	} else {
		index++
		for i := range values {
			listoflists[index] = append(listoflists[index], values[i])
		}
		return listoflists

	}
}

// A simple double-for loop that finds the layer in which an element resides in the hierarchy
// Because we only append elements relative to another element, we can freely use this to keep track of layers
func findCurrentLayer(list []entity.PdictList, element entity.Pdict) int {
	currlayer := -1
	for i, sublist := range list {
		for j := range sublist {
			if sublist[j].Pointer == element.Pointer && sublist[j].Typename == element.Typename {
				currlayer = i
				//break
			}
		}
	}
	return currlayer
}

// Adds a list of pdicts to the hierarchy, but IN FRONT OF the current layer
// Only needed when a entire new list has to be inserted in front of the hierarcy
// Prepending to existing layers can be done by decreasing the layercounter and appending
// See XToY functions for example usage
func prepend(list []entity.PdictList, element entity.PdictList) []entity.PdictList {
	var dummylist entity.PdictList
	dummy := makePdict("dummy", -1)
	dummylist = append(dummylist, dummy)
	list = append(list, dummylist)
	copy(list[1:], list)
	list[0] = element
	return list
}

/*
makePdict makes a pdict based on a typename and pointer
*/
func makePdict(typeName string, pointer int) entity.Pdict {
	return entity.Pdict{
		Typename: typeName,
		Pointer:  pointer,
	}

}