package langext

import (
	"errors"
	"fmt"
	"reflect"
)

func BoolCount(arr ...bool) int {
	c := 0
	for _, v := range arr {
		if v {
			c++
		}
	}
	return c
}

func Range[T IntegerConstraint](start T, end T) []T {
	r := make([]T, 0, end-start)
	for i := start; i < end; i++ {
		r = append(r, i)
	}
	return r
}

func ForceArray[T any](v []T) []T {
	if v == nil {
		return make([]T, 0)
	} else {
		return v
	}
}

func ReverseArray[T any](v []T) {
	for i, j := 0, len(v)-1; i < j; i, j = i+1, j-1 {
		v[i], v[j] = v[j], v[i]
	}
}

func InArray[T comparable](needle T, haystack []T) bool {
	for _, v := range haystack {
		if v == needle {
			return true
		}
	}
	return false
}

func ArrUnique[T comparable](array []T) []T {
	m := make(map[T]bool, len(array))
	for _, v := range array {
		m[v] = true
	}
	result := make([]T, 0, len(m))
	for v := range m {
		result = append(result, v)
	}
	return result
}

func ArrUniqueStable[T comparable](array []T) []T {
	hist := make(map[T]bool, len(array))
	result := make([]T, 0, len(array))
	for _, v := range array {
		if _, ok := hist[v]; !ok {
			hist[v] = true
			result = append(result, v)
		}
	}
	return result
}

func ArrEqualsExact[T comparable](arr1 []T, arr2 []T) bool {
	if len(arr1) != len(arr2) {
		return false
	}

	for i := range arr1 {
		if arr1[i] != arr2[i] {
			return false
		}
	}
	return true
}

func ArrAll[T any](arr []T, fn func(T) bool) bool {
	for _, av := range arr {
		if !fn(av) {
			return false
		}
	}
	return true
}

func ArrAllErr[T any](arr []T, fn func(T) (bool, error)) (bool, error) {
	for _, av := range arr {
		v, err := fn(av)
		if err != nil {
			return false, err
		}
		if !v {
			return false, nil
		}
	}
	return true, nil
}

func ArrNone[T any](arr []T, fn func(T) bool) bool {
	for _, av := range arr {
		if fn(av) {
			return false
		}
	}
	return true
}

func ArrNoneErr[T any](arr []T, fn func(T) (bool, error)) (bool, error) {
	for _, av := range arr {
		v, err := fn(av)
		if err != nil {
			return false, err
		}
		if v {
			return false, nil
		}
	}
	return true, nil
}

func ArrAny[T any](arr []T, fn func(T) bool) bool {
	for _, av := range arr {
		if fn(av) {
			return true
		}
	}
	return false
}

func ArrAnyErr[T any](arr []T, fn func(T) (bool, error)) (bool, error) {
	for _, av := range arr {
		v, err := fn(av)
		if err != nil {
			return false, err
		}
		if v {
			return true, nil
		}
	}
	return false, nil
}

func ArrIdxAll(arr any, fn func(int) bool) bool {
	av := reflect.ValueOf(arr)
	for i := 0; i < av.Len(); i++ {
		if !fn(i) {
			return false
		}
	}
	return true
}

func ArrIdxAllErr(arr any, fn func(int) (bool, error)) (bool, error) {
	av := reflect.ValueOf(arr)
	for i := 0; i < av.Len(); i++ {
		v, err := fn(i)
		if err != nil {
			return false, err
		}
		if !v {
			return false, nil
		}
	}
	return true, nil
}

func ArrIdxNone(arr any, fn func(int) bool) bool {
	av := reflect.ValueOf(arr)
	for i := 0; i < av.Len(); i++ {
		if fn(i) {
			return false
		}
	}
	return true
}

func ArrIdxNoneErr(arr any, fn func(int) (bool, error)) (bool, error) {
	av := reflect.ValueOf(arr)
	for i := 0; i < av.Len(); i++ {
		v, err := fn(i)
		if err != nil {
			return false, err
		}
		if v {
			return false, nil
		}
	}
	return true, nil
}

func ArrIdxAny(arr any, fn func(int) bool) bool {
	av := reflect.ValueOf(arr)
	for i := 0; i < av.Len(); i++ {
		if fn(i) {
			return true
		}
	}
	return false
}

func ArrIdxAnyErr(arr any, fn func(int) (bool, error)) (bool, error) {
	av := reflect.ValueOf(arr)
	for i := 0; i < av.Len(); i++ {
		v, err := fn(i)
		if err != nil {
			return false, err
		}
		if v {
			return true, nil
		}
	}
	return false, nil
}

func ArrFirst[T any](arr []T, comp func(v T) bool) (T, bool) {
	for _, v := range arr {
		if comp(v) {
			return v, true
		}
	}
	return *new(T), false
}

func ArrFirstOrNil[T any](arr []T, comp func(v T) bool) *T {
	for _, v := range arr {
		if comp(v) {
			return Ptr(v)
		}
	}
	return nil
}

func ArrLast[T any](arr []T, comp func(v T) bool) (T, bool) {
	found := false
	result := *new(T)
	for _, v := range arr {
		if comp(v) {
			found = true
			result = v
		}
	}
	return result, found
}

func ArrLastOrNil[T any](arr []T, comp func(v T) bool) *T {
	found := false
	result := *new(T)
	for _, v := range arr {
		if comp(v) {
			found = true
			result = v
		}
	}
	if found {
		return Ptr(result)
	} else {
		return nil
	}
}

func ArrFirstIndex[T comparable](arr []T, needle T) int {
	for i, v := range arr {
		if v == needle {
			return i
		}
	}
	return -1
}

func ArrFirstIndexFunc[T any](arr []T, comp func(v T) bool) int {
	for i, v := range arr {
		if comp(v) {
			return i
		}
	}
	return -1
}

func ArrLastIndex[T comparable](arr []T, needle T) int {
	result := -1
	for i, v := range arr {
		if v == needle {
			result = i
		}
	}
	return result
}

func ArrLastIndexFunc[T any](arr []T, comp func(v T) bool) int {
	result := -1
	for i, v := range arr {
		if comp(v) {
			result = i
		}
	}
	return result
}

func AddToSet[T comparable](set []T, add T) []T {
	for _, v := range set {
		if v == add {
			return set
		}
	}
	return append(set, add)
}

func ArrMap[T1 any, T2 any](arr []T1, conv func(v T1) T2) []T2 {
	r := make([]T2, len(arr))
	for i, v := range arr {
		r[i] = conv(v)
	}
	return r
}

func MapMap[TK comparable, TV any, TR any](inmap map[TK]TV, conv func(k TK, v TV) TR) []TR {
	r := make([]TR, 0, len(inmap))
	for k, v := range inmap {
		r = append(r, conv(k, v))
	}
	return r
}

func MapMapErr[TK comparable, TV any, TR any](inmap map[TK]TV, conv func(k TK, v TV) (TR, error)) ([]TR, error) {
	r := make([]TR, 0, len(inmap))
	for k, v := range inmap {
		elem, err := conv(k, v)
		if err != nil {
			return nil, err
		}
		r = append(r, elem)
	}
	return r, nil
}

func ArrMapExt[T1 any, T2 any](arr []T1, conv func(idx int, v T1) T2) []T2 {
	r := make([]T2, len(arr))
	for i, v := range arr {
		r[i] = conv(i, v)
	}
	return r
}

func ArrMapErr[T1 any, T2 any](arr []T1, conv func(v T1) (T2, error)) ([]T2, error) {
	var err error
	r := make([]T2, len(arr))
	for i, v := range arr {
		r[i], err = conv(v)
		if err != nil {
			return nil, err
		}
	}
	return r, nil
}

func ArrFilterMap[T1 any, T2 any](arr []T1, filter func(v T1) bool, conv func(v T1) T2) []T2 {
	r := make([]T2, 0, len(arr))
	for _, v := range arr {
		if filter(v) {
			r = append(r, conv(v))
		}
	}
	return r
}

func ArrFilter[T any](arr []T, filter func(v T) bool) []T {
	r := make([]T, 0, len(arr))
	for _, v := range arr {
		if filter(v) {
			r = append(r, v)
		}
	}
	return r
}

func ArrSum[T NumberConstraint](arr []T) T {
	var r T = 0
	for _, v := range arr {
		r += v
	}
	return r
}

func ArrFlatten[T1 any, T2 any](arr []T1, conv func(v T1) []T2) []T2 {
	r := make([]T2, 0, len(arr))
	for _, v1 := range arr {
		r = append(r, conv(v1)...)
	}
	return r
}

func ArrFlattenDirect[T1 any](arr [][]T1) []T1 {
	r := make([]T1, 0, len(arr))
	for _, v1 := range arr {
		r = append(r, v1...)
	}
	return r
}

func ArrCastToAny[T1 any](arr []T1) []any {
	r := make([]any, len(arr))
	for i, v := range arr {
		r[i] = any(v)
	}
	return r
}

func ArrCastSafe[T1 any, T2 any](arr []T1) []T2 {
	r := make([]T2, 0, len(arr))
	for _, v := range arr {
		if vcast, ok := any(v).(T2); ok {
			r = append(r, vcast)
		}
	}
	return r
}

func ArrCastErr[T1 any, T2 any](arr []T1) ([]T2, error) {
	r := make([]T2, len(arr))
	for i, v := range arr {
		if vcast, ok := any(v).(T2); ok {
			r[i] = vcast
		} else {
			return nil, errors.New(fmt.Sprintf("Cannot cast element %d of type %T to type %v", i, v, *new(T2)))
		}
	}
	return r, nil
}

func ArrCastPanic[T1 any, T2 any](arr []T1) []T2 {
	r := make([]T2, len(arr))
	for i, v := range arr {
		if vcast, ok := any(v).(T2); ok {
			r[i] = vcast
		} else {
			panic(fmt.Sprintf("Cannot cast element %d of type %T to type %v", i, v, *new(T2)))
		}
	}
	return r
}

func ArrConcat[T any](arr ...[]T) []T {
	c := 0
	for _, v := range arr {
		c += len(v)
	}
	r := make([]T, c)
	i := 0
	for _, av := range arr {
		for _, v := range av {
			r[i] = v
			i++
		}
	}
	return r
}

// ArrAppend works similar to append(x, y, z) - but doe snot touch the old array and creates a new one
func ArrAppend[T any](arr []T, add ...T) []T {
	r := ArrCopy(arr)
	for _, v := range add {
		r = append(r, v)
	}
	return r
}

// ArrPrepend works similar to append(x, y, z) - but doe snot touch the old array and creates a new one
// Also - in contrast to ArrAppend - the add values are inserted at the start of the resulting array (in reverse order)
func ArrPrepend[T any](arr []T, add ...T) []T {
	out := make([]T, len(arr)+len(add))
	copy(out[len(add):], arr)
	for i := 0; i < len(add); i++ {
		out[len(add)-i-1] = add[i]
	}
	return out
}

// ArrCopy does a shallow copy of the 'in' array
func ArrCopy[T any](in []T) []T {
	out := make([]T, len(in))
	copy(out, in)
	return out
}

func ArrRemove[T comparable](arr []T, needle T) []T {
	idx := ArrFirstIndex(arr, needle)
	if idx >= 0 {
		return append(arr[:idx], arr[idx+1:]...)
	}
	return arr
}

func ArrRemoveAt[T any](arr []T, idx int) []T {
	return append(arr[:idx], arr[idx+1:]...)
}

func ArrExcept[T comparable](arr []T, needles ...T) []T {
	r := make([]T, 0, len(arr))
	rmlist := ArrToSet(needles)
	for _, v := range arr {
		if _, ok := rmlist[v]; !ok {
			r = append(r, v)
		}
	}
	return r
}

func ArrayToInterface[T any](t []T) []interface{} {
	res := make([]interface{}, 0, len(t))
	for i, _ := range t {
		res = append(res, t[i])
	}
	return res
}

func JoinString(arr []string, delimiter string) string {
	str := ""
	for i, v := range arr {
		str += v
		if i < len(arr)-1 {
			str += delimiter
		}
	}

	return str
}

// ArrChunk splits the array into buckets of max-size `chunkSize`
// order is being kept.
// The last chunk may contain less than length elements.
//
// (chunkSize == -1) means no chunking
//
// see https://www.php.net/manual/en/function.array-chunk.php
func ArrChunk[T any](arr []T, chunkSize int) [][]T {
	if chunkSize == -1 {
		return [][]T{arr}
	}

	res := make([][]T, 0, 1+len(arr)/chunkSize)

	i := 0
	for i < len(arr) {

		right := i + chunkSize
		if right >= len(arr) {
			right = len(arr)
		}

		res = append(res, arr[i:right])

		i = right
	}

	return res
}

func ArrGroupBy[T1 any, T2 comparable](arr []T1, groupfunc func(v T1) T2) map[T2][]T1 {
	r := make(map[T2][]T1)

	for _, v := range arr {
		key := groupfunc(v)
		if _, ok := r[key]; ok {
			r[key] = append(r[key], v)
		} else {
			r[key] = []T1{v}
		}
	}

	return r
}