package helper

import (
	"errors"
	"io/ioutil"
	"sort"
	"strconv"
	"strings"
)

type convert func(int) string

func GetInput(filename string) ([]string, error) {
	content, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	lines := strings.Split(string(content), "\r\n")
	return lines, err
}

func GetFile(filename string) (string, error) {
	content, err := ioutil.ReadFile(filename)
	if err != nil {
		return "", err
	}
	return string(content), err
}

func MapToNumber(numberStrings []string) ([]int, error) {
	var numbers []int
	for _, numberString := range numberStrings {
		trimmedNumberString := strings.TrimSpace(numberString)
		number, err := strconv.Atoi(trimmedNumberString)
		if err != nil {
			return numbers, err
		}
		numbers = append(numbers, number)
	}
	return numbers, nil
}

func RunesToNumber(runes []rune) []int {
	var numbers []int
	for _, r := range runes {
		number := int(r - '0')
		numbers = append(numbers, number)
	}
	return numbers
}

func FindAddendsForSum(possibleAddends []int, sum int) (int, int, error) {
	var array = make([]int, len(possibleAddends))
	copy(array, possibleAddends)
	sort.Ints(array)
	p1 := 0
	p2 := len(array) - 1
	for p1 < p2 {
		if array[p1]+array[p2] < sum {
			p1++
		}
		if array[p1]+array[p2] > sum {
			p2--
		}
		if array[p1]+array[p2] == sum {
			return array[p1], array[p2], nil
		}
	}
	return 0, 0, errors.New("No fitting Addends found")
}

func SliceIndex(limit int, predicate func(i int) bool) int {
	for i := 0; i < limit; i++ {
		if predicate(i) {
			return i
		}
	}
	return -1
}

func DeleteAt(a []int, i int) []int {
	return append(a[:i], a[i+1:]...)
}

func DeleteCharAt(a string, i int) string {
	return a[:i] + a[i+1:]
}

func Equal(a, b []int) bool {
	if len(a) != len(b) {
		return false
	}
	for i, v := range a {
		if v != b[i] {
			return false
		}
	}
	return true
}

func EqualStringArray(a, b []string) bool {
	if len(a) != len(b) {
		return false
	}
	for i, v := range a {
		if v != b[i] {
			return false
		}
	}
	return true
}

func Contains(elem int, array []int) bool {
	for _, val := range array {
		if val == elem {
			return true
		}
	}
	return false
}

func ContainsString(elem string, array []string) int {
	sum := 0
	for _, val := range array {
		if val == elem {
			sum++
		}
	}
	return sum
}

func ContainsStringArray(elem []string, array [][]string) int {
	sum := 0
	for _, val := range array {
		if EqualStringArray(elem, val) {
			sum++
		}
	}
	return sum
}

func ContainsCharacter(input string, characters string) bool {
	for _, char := range characters {
		if !strings.ContainsRune(input, char) {
			return false
		}
	}
	return true
}

func AmountCharacterInString(input string, selector rune) int {
	amount := 0
	for _, char := range input {
		if char == selector {
			amount++
		}
	}
	return amount
}

func OverlapFloatArray(a, b []float64) []float64 {
	mb := make(map[float64]struct{}, len(b))
	for _, x := range b {
		mb[x] = struct{}{}
	}
	var overlap []float64
	for _, x := range a {
		if _, found := mb[x]; found {
			overlap = append(overlap, x)
		}
	}
	return overlap
}

func Overlap3DPoint(a, b [][3]int) [][3]int {
	mb := make(map[[3]int]struct{}, len(b))
	for _, x := range b {
		mb[x] = struct{}{}
	}
	var overlap [][3]int
	for _, x := range a {
		if _, found := mb[x]; found {
			overlap = append(overlap, x)
		}
	}
	return overlap
}

func Difference3DPoint(a, b [][3]int) [][3]int {
	mb := make(map[[3]int]struct{}, len(b))
	for _, x := range b {
		mb[x] = struct{}{}
	}
	var diff [][3]int
	for _, x := range a {
		if _, found := mb[x]; !found {
			diff = append(diff, x)
		}
	}
	return diff
}

func AddNummbers(numbers ...int) int {
	result := 0
	for _, number := range numbers {
		result += number
	}
	return result
}

func Transpose(in [][]int) [][]int {
	out := make([][]int, len(in[0]))
	for i := 0; i < len(in); i += 1 {
		for j := 0; j < len(in[0]); j += 1 {
			out[j] = append(out[j], in[i][j])
		}
	}
	return out
}

func RemoveElementFromStringArray(stringArray []string, selector string) []string {
	var returnString []string
	for _, str := range stringArray {
		if str != selector {
			returnString = append(returnString, str)
		}
	}
	return returnString
}

func RemovePointFromPointArray(pointArray [][2]int, selector [2]int) [][2]int {
	var returnArray [][2]int
	for _, point := range pointArray {
		if point != selector {
			returnArray = append(returnArray, point)
		}
	}
	return returnArray
}

func GCD(x int, y int) int {
	gcd := -1
	for i := 1; i <= x && i <= y; i++ {
		if y%i == 0 && x%i == 0 {
			gcd = i
		}
	}
	return gcd
}

func Abs(x int) int {
	if x < 0 {
		return -x
	}
	return x
}

func RemoveCharactersFromString(input string, characters string) string {
	filter := func(r rune) rune {
		if strings.IndexRune(characters, r) < 0 {
			return r
		}
		return -1
	}
	return strings.Map(filter, input)
}

type sortRunes []rune

func (s sortRunes) Less(i, j int) bool {
	return s[i] < s[j]
}

func (s sortRunes) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

func (s sortRunes) Len() int {
	return len(s)
}

func SortString(s string) string {
	r := []rune(s)
	sort.Sort(sortRunes(r))
	return string(r)
}

type Pair struct {
	Key   string
	Value int
}

type PairList []Pair

func RankByValueAmount(wordFrequencies map[string]int) PairList {
	pl := make(PairList, len(wordFrequencies))
	i := 0
	for k, v := range wordFrequencies {
		pl[i] = Pair{k, v}
		i++
	}
	sort.Sort(sort.Reverse(pl))
	return pl
}

func (p PairList) Len() int           { return len(p) }
func (p PairList) Less(i, j int) bool { return p[i].Value < p[j].Value }
func (p PairList) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
