package aoc

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"log"
	"math"
	"os"
	"strconv"
	"strings"
)

// Some handy Constants
const (
	BORDER_NS = "\u2502"
	BORDER_WE = "\u2500"

	BORDER_NW = "\u250C"
	BORDER_NE = "\u2510"
	BORDER_SW = "\u2514"
	BORDER_SE = "\u2518"

	FILL_CHAR    = "\u2588"
	CLEAR_SCREEN = "\033[H\033[2J"

	MAX_INT = int(^uint(0) >> 1)
	MIN_INT = -MAX_INT - 1

	SHRUG = "¯\\_(ツ)_/¯"
)

// Fact returns the factorial of the given int
func Fact(x int) int {
	var ret int
	for i := 0; i <= x; i++ {
		ret += i
	}
	return ret
}

// Abs returns the absolute value of the given int
func Abs(x int) int {
	if x < 0 {
		return x * -1
	}
	return x
}

// Gcd Finds the greatest common denominator
func Gcd(x, y int) int {
	for y != 0 {
		x, y = y, x%y
	}
	return x
}

// Lcm finds the least common multiple, using gcd
func Lcm(a, b int, integers ...int) int {
	result := a * b / Gcd(a, b)
	for i := 0; i < len(integers); i++ {
		result = Lcm(result, integers[i])
	}
	return result
}

// AbsInt returns the absolute value of i
func AbsInt(i int) int {
	if i < 0 {
		return i * -1
	}
	return i
}

// ArgIsSet return true if an argument with the asked for key is present
func ArgIsSet(a string) bool {
	for i := range os.Args {
		if os.Args[i] == a || strings.HasPrefix(os.Args[i], a+"=") {
			return true
		}
	}
	return false
}

// GetArgValue returns the argument with the asked for key or ""
func GetArgValue(a string) string {
	for i := range os.Args {
		if strings.HasPrefix(os.Args[i], a+"=") {
			return strings.TrimPrefix(os.Args[i], a+"=")
		}
	}
	return ""
}

// GetArgNumber returns the asked for argument position or ""
func GetArgNumber(i int) string {
	if len(os.Args) > i {
		return os.Args[i]
	}
	return ""
}

// OptArgNumber returns either the asked for argument position or the passed default
func OptArgNumber(i int, def string) string {
	if len(os.Args) > i {
		return os.Args[i]
	}
	return def
}

// StdinToString reads from stdin and returns a string
func StdinToString() string {
	var input string
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		input += scanner.Text()
	}
	return input
}

// StdinToIntSlice reads from stdin and returns it as an int slice
func StdinToIntSlice() []int {
	var ret []int
	st := StdinToStringSlice()
	for _, v := range st {
		ret = append(ret, Atoi(v))
	}
	return ret
}

// StdinToStringSlice reads from stdin and returns it as a string slice
func StdinToStringSlice() []string {
	var input []string
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		input = append(input, scanner.Text())
	}
	return input
}

// StdinToCoordMap reads stdin and returns it as a CoordByteMap
func StdinToCoordMap() CoordByteMap {
	return StringSliceToCoordByteMap(StdinToStringSlice())
}

// Atoi is basically redundant
func Atoi(i string) int {
	var ret int
	var err error
	if ret, err = strconv.Atoi(i); err != nil {
		log.Fatalf("Invalid Atoi: %s\n%v", i, err)
	}
	return ret
}

// Itoa is basically redundant
func Itoa(i int) string {
	return strconv.Itoa(i)
}

// FileToStringSlice takes a file and returns it as a slice of strings
func FileToStringSlice(fn string) []string {
	return strings.Split(string(FileToBytes(fn)), "\n")
}

// FileToString reads a file and returns it as a string
func FileToString(fn string) string {
	return string(FileToBytes(fn))
}

// FileToBytes reads a file and returns it as a slice of bytes
func FileToBytes(fn string) []byte {
	var c []byte
	var err error
	c, err = ioutil.ReadFile(fn)
	if err != nil {
		fmt.Println("Unable to read file: " + fn)
		os.Exit(1)
	}
	return c
}

// PrintProgress is for outputting a progress bar
func PrintProgress(curr, total int) {
	pct := int(float64(curr)/float64(total)) * 100
	for i := 0; i < 100; i += 10 {
		if pct > i {
			fmt.Print(FILL_CHAR)
		}
	}
}

// StringPermutations takes a string and returns all permutations of it
func StringPermutations(str string) []string {
	perms := stringPermHelper(str, 0)
	var wrk []string
	// Now de-dupe
	for i := range perms {
		var found bool
		for j := range wrk {
			if wrk[j] == perms[i] {
				found = true
			}
		}
		if !found {
			wrk = append(wrk, perms[i])
		}
	}
	return wrk
}

func stringPermHelper(str string, i int) []string {
	ret := []string{str}
	if i != len(str) {
		r := []rune(str)
		for j := i; j < len(r); j++ {
			r[i], r[j] = r[j], r[i]
			ret = append(ret, stringPermHelper(string(r), i+1)...)
			r[i], r[j] = r[j], r[i]
		}
	}
	return ret
}

// IntPermutations takes a slice of ints and returns all permutations of it
func IntPermutations(inp []int) [][]int {
	perms := intPermHelper(inp, 0)
	var wrk [][]int
	// Now de-dupe
	for i := range perms {
		var found bool
		for j := range wrk {
			if IntSlicesAreEqual(perms[i], wrk[j]) {
				found = true
				break
			}
		}
		if !found {
			wrk = append(wrk, perms[i])
		}
	}
	return wrk
}

func intPermHelper(inp []int, i int) [][]int {
	ret := [][]int{inp}
	if i != len(inp) {
		r := make([]int, len(inp))
		copy(r, inp)
		for j := i; j < len(r); j++ {
			r[i], r[j] = r[j], r[i]
			ret = append(ret, intPermHelper(r, i+1)...)
			r[i], r[j] = r[j], r[i]
		}
	}
	return ret
}

// IntSlicesAreEqual takes two int slices and returns if they are equal
func IntSlicesAreEqual(s1 []int, s2 []int) bool {
	if len(s1) != len(s2) {
		return false
	}
	for k := range s1 {
		if s1[k] != s2[k] {
			return false
		}
	}
	return true
}

func StringSliceIndex(h []string, n string) int {
	for k, v := range h {
		if v == n {
			return k
		}
	}
	return -1
}

// StringSliceContains takes a string slice and a string and return true
// if the string is in the slice
func StringSliceContains(h []string, n string) bool {
	for _, v := range h {
		if v == n {
			return true
		}
	}
	return false
}

// IntSliceContains takes an int slice and an int and return true
// if the int is in the slice
func IntSliceContains(h []int, n int) bool {
	for _, v := range h {
		if v == n {
			return true
		}
	}
	return false
}

// AppendString returns a slice of strings that are base+group[...]
func AppendStrings(base string, group []string) []string {
	for k, v := range group {
		group[k] = base + v
	}
	return group
}

// UnionStringSlice returns a slice that combines list1 and list2
func UnionStringSlice(list1, list2 []string) []string {
	for k := range list1 {
		if !StringSliceContains(list2, list1[k]) {
			list2 = append(list2, list1[k])
		}
	}
	return list2
}

// IntersectStringSlice returns a slice that contains all elements that
// both list1 and list2 contain
func IntersectStringSlice(list1, list2 []string) []string {
	var ret []string
	for k := range list1 {
		if StringSliceContains(list2, list1[k]) {
			ret = append(ret, list1[k])
		}
	}
	return ret
}

// Unique removes all duplicates
func UniqueStringSlice(list []string) []string {
	var ret []string
	for k := 0; k < len(list); k++ {
		var dupe bool
		for j := k; j < len(list); j++ {
			if list[k] == list[j] {
				dupe = true
				break
			}
		}
		if !dupe {
			ret = append(ret, list[k])
		}
	}
	return ret
}

// IsPrime takes a number and return true if that number is prime
func IsPrime(value int) bool {
	for i := 2; i <= int(math.Floor(float64(value)/2)); i++ {
		if value%i == 0 {
			return false
		}
	}
	return value > 1
}

func Min(v1, v2 int, vrest ...int) int {
	min := v2
	if v1 < v2 {
		min = v1
	}
	for i := range vrest {
		if vrest[i] < min {
			min = vrest[i]
		}
	}
	return min
}

func Max(v1, v2 int, vrest ...int) int {
	max := v2
	if v1 > v2 {
		max = v1
	}
	for i := range vrest {
		if vrest[i] > max {
			max = vrest[i]
		}
	}
	return max
}

// Sum adds up all of the numbers from l to h
func Sum(l, h int) int {
	var ret int
	for i := Min(l, h); i <= Max(l, h); i++ {
		ret = ret + i
	}
	return ret
}
