package main

import (
	"AoC2020/helper"
	"fmt"
	"os"
	"regexp"
	"strings"
)

func main() {
	lineBreak := "\n"
	args := os.Args[1:]
	input, err := helper.GetFile(args[0])
	if err != nil {
		fmt.Println(err)
	}
	rows := strings.Split(input, lineBreak+lineBreak)
	rulesInput := strings.Split(rows[0], lineBreak)
	ruleNames := getRuleNames(rulesInput)
	rules := getRules(rulesInput)
	myTicket := getFields(strings.Split(rows[1], lineBreak)[1])
	nearbyTicketsInputs := strings.Split(rows[2], lineBreak)[1:]
	var nearbyTickets [][]int
	for _, val := range nearbyTicketsInputs {
		nearbyTickets = append(nearbyTickets, getFields(val))
	}
	fmt.Printf("RuleNames: %v \n Rules: %v \n myTicket: %v \n nearbyTickets: %v \n", ruleNames, rules, myTicket, nearbyTickets)
	part1(rules, nearbyTickets)
	fmt.Println()
	part2(rules, nearbyTickets,myTicket, ruleNames)
}

func part1(rules [][]int, nearbyTickets [][]int) {
	var invalidFields []int
	for _, ticket := range nearbyTickets {
		invalidFields = append(invalidFields, getInvalidFields(rules, ticket)...)
	}
	fmt.Printf("Invalid Fields: %v \n", invalidFields)
	sum := 0
	for _, field := range invalidFields {
		sum += field
	}
	fmt.Printf("Result: %v \n", sum)
}

func part2(rules [][]int, nearbyTickets [][]int, myTicket []int, ruleNames []string) {
	validTickets := getValidTickets(rules,nearbyTickets)
	var ruleFieldPossibleMapping = make(map[int][]int)
	for fieldIndex := 0; fieldIndex < len(validTickets[0]); fieldIndex++ {
		ruleIndexes := findFittingRulesForFieldsAtPositionN(rules, validTickets, fieldIndex)
		ruleFieldPossibleMapping[fieldIndex] = ruleIndexes
	}
	fmt.Printf("Possible Mapping: %v \n", ruleFieldPossibleMapping)
	var ruleFieldMapping = make(map[int]int)
	for  len(ruleFieldMapping) < len(ruleFieldPossibleMapping) {
		for field, possibleMapping := range ruleFieldPossibleMapping {
			if len(possibleMapping) == 1 {
				reducePossibleMapping(ruleFieldPossibleMapping,possibleMapping[0])
				ruleFieldMapping[field] = possibleMapping[0]
			}
		}
	}
	for fieldIndex,  ruleIndex := range ruleFieldMapping {
		fmt.Printf("Rule: %v Position: %v Value: %v \n", ruleNames[ruleIndex], fieldIndex, myTicket[fieldIndex])
	}
}

func reducePossibleMapping(ruleFieldPossibleMapping map[int][]int, possibleMapping int){
	for key, val := range ruleFieldPossibleMapping{
		ruleFieldPossibleMapping[key] = remove(val,possibleMapping)
	}
}

func getRules(input []string) [][]int {
	var rules [][]int
	for _, val := range input {
		r, _ := regexp.Compile("[0-9]*-[0-9]*")
		ruleStrings := r.FindAllString(val, -1)
		for _, ruleString := range ruleStrings {
			rule, _ := helper.MapToNumber(strings.Split(ruleString, "-"))
			rules = append(rules, rule)
		}
	}
	return rules
}

func getRuleNames(input []string) []string {
	var ruleNames []string
	for _, val := range input {
		r, _ := regexp.Compile("[a-z| ]*:")
		ruleName := r.FindString(val)
		ruleNames = append(ruleNames, ruleName)

	}
	return ruleNames
}

func getFields(ticket string) []int {
	fieldValues := strings.Split(ticket, ",")
	fields, _ := helper.MapToNumber(fieldValues)
	return fields
}

func fullfillsRule(rule []int, field int) bool {
	return field >= rule[0] && field <= rule[1]
}

func findFittingRulesForFieldsAtPositionN(rules [][]int, tickets [][]int, position int) []int {
	var fittingRules []int
	for i := 0; i < len(rules); i += 2 {
		if rulesApplyForAllFieldsAtPositionN(rules[i:i+2], tickets, position) {
			fittingRules = append(fittingRules,i/2)
		}
	}
	return fittingRules
}

func rulesApplyForAllFieldsAtPositionN(rules [][]int, tickets [][]int, position int) bool {
	for _, ticket := range tickets {
		if !fullfillsRule(rules[1], ticket[position]) && !fullfillsRule(rules[0], ticket[position])  {
			return false
		}
	}
	return true
}

func fullFillAtLeastOneRule(rules [][]int, field int) bool {
	for _, val := range rules {
		if fullfillsRule(val, field) {
			return true
		}
	}
	return false
}

func getInvalidFields(rules [][]int, ticket []int) []int {
	var invalidFields []int
	for _, field := range ticket {
		if !fullFillAtLeastOneRule(rules, field) {
			invalidFields = append(invalidFields, field)
		}
	}
	return invalidFields
}

func getValidTickets(rules [][]int, tickets [][]int) [][]int {
	var validTickets [][]int
	for _, ticket := range tickets {
		if len(getInvalidFields(rules,ticket)) == 0 {
			validTickets = append(validTickets,ticket)
		}
	}
	return validTickets
}

func remove(s []int, val int) []int {
	var newS []int
	for _,elem := range s {
		if val != elem {
			newS = append(newS,elem)
		}
	}
	return newS
}