//main entry function that calls the right
//functions for getting sodoku answer
func (inst *Solutionizer) GetSodokuSolution(board *sodoku.Board) string {

	inst.possibilities = 0

	pass := inst.SetIndicesWithLeastPossibleChoices(board)

	if !pass {
		panic("Can't be solved!")
		return ""
	}

	return board.GetStringFormat()
}
//for a particular empty index, returns the
//numbers available that can poltentially be
//inserted in that index
func (inst *Solutionizer) getPossibilities(i, j int, board *sodoku.Board) []int {

	families := board.GetFamilies(i, j)
	availableNumbers := 987654321

	for _, family := range families {
		availableNumbers = inst.availableNumbers(availableNumbers, family)
	}

	numsAvailable := inst.getPossibilitiesFromAvailableNumbers(availableNumbers)

	return numsAvailable
}
//retrieves the family with the least
//free options to choose from. For example
//if a particular row only has one blank
//indices then we know we can fill that entry
//with 100% certainty(the unused number will go there)
//returns true if solution was found
func (inst *Solutionizer) SetIndicesWithLeastPossibleChoices(board *sodoku.Board) bool {

	toBeFilled := board.GetEmptyIndices()

	if len(toBeFilled) <= 0 && board.IsBoardComplete() {
		return true
	}

	oppertunityFound := false
	oppertunities := [9][]oppertunity{}
	boardChange := false

	for _, v := range toBeFilled {

		i, j := v[0], v[1]

		numAvailable := inst.getPossibilities(i, j, board)
		length := len(numAvailable)

		//if no oppertunities or certainties were found then
		//we are dealing with a faulty/broken board
		if length <= 0 {
			return false
			//if we only have one choice to choose from then we know 100% we can set it
		} else if length == 1 {
			//set indices for relatives
			board.SetEntry(i, j, numAvailable[0])
			boardChange = true
			//other wise we track available oppurtunities thats ordered
			//based on amount of numbers available
		} else {
			oppertunityFound = true
			oppertunities[length] = append(oppertunities[length], oppertunity{i, j, numAvailable})
		}
	}

	//if board was changed we call recursion on updated board
	if boardChange {
		return inst.SetIndicesWithLeastPossibleChoices(board)
		//else if at least one oppurtunity was found
		//we insert oppurtunity and recompute recursion
		//notice how oppertunities are traverse based on order
		//of minimum possibilities. This gives it a much higher chance
		//of success
	} else if oppertunityFound {

		//make copy of current entries before any alterations
		originalEntry := inst.copy(board.Entries)
		inst.possibilities += 1

		for _, ops := range oppertunities {

			for _, op := range ops {

				if len(op.Entries) <= 0 {
					continue
				}

				for _, v := range op.Entries {
					board.SetEntry(op.I, op.J, v)
					pass := inst.SetIndicesWithLeastPossibleChoices(board)
					//if recursion returns true
					if pass {
						return true
						//otherwise if this oppertunity wasnt the best choice
						//we set it back to 0 and try next oppertunity
					} else {
						board.SetEntries(originalEntry)
					}
				}

			}

		}
	}

	return false
}