package main

import (
	"AOC2021/src/helper"
	"fmt"
	"os"
	"strings"
)

type checkHitFunc func(int, [2]int) (bool, int)

type targetArea struct {
	xValues [2]int
	yValues [2]int
}

func main() {
	args := os.Args[1:]
	input, err := helper.GetInput(args[0])
	if err != nil {
		fmt.Println(err)
	}
	coordinateString := strings.Split(input[0], ": ")[1]
	coordinateString = helper.RemoveCharactersFromString(coordinateString, "yx=")
	coordinateStringSplit := strings.Split(coordinateString, ", ")
	xValues, _ := helper.MapToNumber(strings.Split(coordinateStringSplit[0], ".."))
	yValues, _ := helper.MapToNumber(strings.Split(coordinateStringSplit[1], ".."))
	area := targetArea{xValues: [2]int{xValues[0], xValues[1]}, yValues: [2]int{yValues[0], yValues[1]}}
	fmt.Println(area)
	hitFunctionUpward := getcheckHitFunction(changeVelocityUpward, true)
	hitFunctionForward := getcheckHitFunction(changeVelocityForward, false)

	//Part 1
	highestY, maxHeight := getHighestPossibleVelocityWhichStillHits(area.yValues, hitFunctionUpward)
	fmt.Printf("Highest Y: %d  Max Height: %d \n", highestY, maxHeight)

	testPointsString, err := helper.GetInput("day17TestPart2Points.txt")
	var testPoints [][2]int
	for _, pointString := range testPointsString {
		testPointIntArray, _ := helper.MapToNumber(strings.Split(pointString,","))
		testPoints = append(testPoints, [2]int{testPointIntArray[0],testPointIntArray[1]})
	}
	//Part 2
	possibleYs := getAllPossibleVelocitiesWhichStillHits(area.yValues, hitFunctionUpward, highestY, yValues[0])
	possibleXs := getAllPossibleVelocitiesWhichStillHits(area.xValues, hitFunctionForward, xValues[1], 1)
	fmt.Println(possibleXs)
	fmt.Println(possibleYs)
	sum := 0
	for _, possibleY := range possibleYs {
		for _, possibleX := range possibleXs {
			if checkHitXAndY(possibleX,possibleY, &area){
				sum +=1
			}
		}
	}
	fmt.Println(sum)

}

func getHighestPossibleVelocityWhichStillHits(target [2]int, checkHitFunc checkHitFunc) (int, int) {
	hit := true
	max := 0
	n := 1000
	for {
		hit, max = checkHitFunc(n, target)
		if hit {
			return n, max
		}
		n--
	}
}

func getAllPossibleVelocitiesWhichStillHits(target [2]int, checkHitFunc checkHitFunc, highestVelocity int, lowestVelocity int) (hits []int) {
	n := highestVelocity
	for n >= lowestVelocity {
		hit, _ := checkHitFunc(n, target)
		if hit {
			hits = append(hits, n)
		}
		n--
	}
	return hits
}

func getcheckHitFunction(velocityChangeFunction func(*int), lowBorder bool) checkHitFunc {
	var whileFunc func(int, [2]int, int) bool
	if lowBorder {
		whileFunc = func(n int,target [2]int, velcoity int) bool{
			return n > target[0]
		}
	}else {
		whileFunc = func(n int,target [2]int, velocity int) bool{
			return n < target[1] && velocity != 0
		}
	}
	return func(velocity int, target [2]int) (bool, int) {
		n := 0
		max := 0
		for whileFunc(n,target,velocity) {
			n += velocity
			if n > max {
				max = n
			}
			velocityChangeFunction(&velocity)
			if n >= target[0] && n <= target[1] {
				return true, max
			}
		}
		return false, max
	}
}

func changeVelocityUpward(velocity *int) {
	*velocity -= 1
}

func changeVelocityForward(velocity *int) {
	if *velocity > 0 {
		*velocity -= 1
	}
	if *velocity < 0 {
		*velocity += 1
	}
}

func checkHitXAndY(forward int, upward int, area *targetArea) bool {
	x := 0
	y := 0
	for x <= area.xValues[1] && y >= area.yValues[0] {
		x += forward
		y += upward
		changeVelocityUpward(&upward)
		changeVelocityForward(&forward)
		if x <= area.xValues[1] && y >= area.yValues[0] && x >= area.xValues[0] && y <= area.yValues[1] {
			return true
		}
	}
	return false
}