package main

import (
	"slices"
)

var whitespaceChars = []rune{' ', '\t', '\n'}
var digitChars = []rune{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}
var wordChars = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_")
var notDotChars = []rune{'\n'}
var LBRACKET rune = 0xF0000
var RBRACKET rune = 0xF0001
var ANY_CHAR rune = 0xF0002 // Represents any character - used for 'dot' metacharacter

func dotChars() []rune { // Returns all possible characters represented by the dot metacharacter - this is too tedious to define as a variable, which is why it is a function
	start := 0x0020
	end := 0x007E
	to_return := make([]rune, (end-start)+1)
	for i := start; i <= end; i++ {
		to_return[i-start] = rune(i)
	}
	return to_return
}

// Returns true if str[idx] and str[idx-1] are separated by a word boundary.
func isWordBoundary(str []rune, idx int) bool {
	str_runes := []rune(str)
	wbounded := idx == 0 ||
		idx >= len(str) ||
		(!slices.Contains(wordChars, str_runes[idx-1]) && slices.Contains(wordChars, str_runes[idx])) ||
		(slices.Contains(wordChars, str_runes[idx-1]) && !slices.Contains(wordChars, str_runes[idx]))
	return wbounded
}

func isNormalChar(c rune) bool {
	specialChars := []rune(`?*\^${}()+|[].~`)
	specialChars = append(specialChars, LBRACKET, RBRACKET)
	return !slices.Contains(specialChars, c)
}

func assert(cond bool) {
	if cond != true {
		panic("Assertion Failed")
	}
}

func deleteFromSlice[T comparable](slc []T, val T) []T {
	toReturn := make([]T, 0, len(slc))
	for _, v := range slc {
		if v != val {
			toReturn = append(toReturn, v)
		}
	}
	return toReturn
}

// Ensure that the given elements are only appended to the given slice if they
// don't already exist. Returns the new slice, and the number of unique items appended.
func unique_append[T comparable](slc []T, items ...T) ([]T, int) {
	num_appended := 0
	for _, item := range items {
		if !slices.Contains(slc, item) {
			slc = append(slc, item)
			num_appended++
		}
	}
	return slc, num_appended
}

// Returns true only if all the given elements are equal
func allEqual[T comparable](items ...T) bool {
	first := items[0]
	for _, item := range items {
		if item != first {
			return false
		}
	}
	return true
}

// Returns all elements in slice A that are NOT in slice B
func setDifference[T comparable](s1 []T, s2 []T) []T {
	toReturn := make([]T, 0, len(s1))
	for _, val := range s1 {
		if !slices.Contains(s2, val) {
			toReturn = append(toReturn, val)
		}
	}
	return toReturn
}

// Map function - convert a slice of T to a slice of V, based on a function
// that maps a T to a V
func Map[T, V any](slc []T, fn func(T) V) []V {
	toReturn := make([]V, len(slc))
	for i, val := range slc {
		toReturn[i] = fn(val)
	}
	return toReturn
}

// Reduce function - reduces a slice of a type into a value of the type,
// based on the given function.
func Reduce[T any](slc []T, fn func(T, T) T) T {
	if len(slc) == 0 {
		panic("Reduce on empty slice.")
	}
	for len(slc) > 1 {
		v1 := slc[0]
		v2 := slc[1]
		slc = slc[1:]
		slc[0] = fn(v1, v2)
	}
	return slc[0]
}

// Generate numbers in a range - start (inclusive) to end (exclusive)
func genRange(start, end int) []int {
	toRet := make([]int, end-start)
	for i := start; i < end; i++ {
		toRet[i-start] = i
	}
	return toRet
}