package a

import "fmt"

type ImplicitOrd interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64 |
		~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
		~float32 | ~float64 |
		~string
}

func LessGiven[T ImplicitOrd]() Ord[T] {
	return LessFunc[T](func(a, b T) bool {
		return a < b
	})
}

type Eq[T any] interface {
	Eqv(a T, b T) bool
}

type Ord[T any] interface {
	Eq[T]
	Less(a T, b T) bool
}

type LessFunc[T any] func(a, b T) bool

func (r LessFunc[T]) Eqv(a, b T) bool {
	return r(a, b) == false && r(b, a) == false
}

func (r LessFunc[T]) Less(a, b T) bool {
	return r(a, b)
}

type Option[T any] struct {
	v *T
}

func (r Option[T]) IsDefined() bool {
	return r.v != nil
}

func (r Option[T]) IsEmpty() bool {
	return !r.IsDefined()
}

func (r Option[T]) Get() T {
	return *r.v
}

func (r Option[T]) String() string {
	if r.IsDefined() {
		return fmt.Sprintf("Some(%v)", r.v)
	} else {
		return "None"
	}
}

func (r Option[T]) OrElse(t T) T {
	if r.IsDefined() {
		return *r.v
	}
	return t
}

func (r Option[T]) Recover(f func() T) Option[T] {
	if r.IsDefined() {
		return r
	}
	t := f()
	return Option[T]{&t}
}

type Func1[A1, R any] func(a1 A1) R

type Func2[A1, A2, R any] func(a1 A1, a2 A2) R

func (r Func2[A1, A2, R]) Curried() Func1[A1, Func1[A2, R]] {
	return func(a1 A1) Func1[A2, R] {
		return Func1[A2, R](func(a2 A2) R {
			return r(a1, a2)
		})
	}
}

type HList interface {
	sealed()
}

// Header is constrains interface type,  enforce Head type of Cons is HT
type Header[HT any] interface {
	HList
	Head() HT
}

// Cons means H :: T
// zero value of Cons[H,T] is not allowed.
// so Cons defined as interface type
type Cons[H any, T HList] interface {
	HList
	Head() H
	Tail() T
}

type Nil struct {
}

func (r Nil) Head() Nil {
	return r
}

func (r Nil) Tail() Nil {
	return r
}

func (r Nil) String() string {
	return "Nil"
}

func (r Nil) sealed() {

}

type hlistImpl[H any, T HList] struct {
	head H
	tail T
}

func (r hlistImpl[H, T]) Head() H {
	return r.head
}

func (r hlistImpl[H, T]) Tail() T {
	return r.tail
}

func (r hlistImpl[H, T]) String() string {
	return fmt.Sprintf("%v :: %v", r.head, r.tail)
}

func (r hlistImpl[H, T]) sealed() {

}

func hlist[H any, T HList](h H, t T) Cons[H, T] {
	return hlistImpl[H, T]{h, t}
}

func Concat[H any, T HList](h H, t T) Cons[H, T] {
	return hlist(h, t)
}

func Empty() Nil {
	return Nil{}
}
func Some[T any](v T) Option[T] {
	return Option[T]{}.Recover(func() T {
		return v
	})
}

func None[T any]() Option[T] {
	return Option[T]{}
}

func Ap[T, U any](t Option[Func1[T, U]], a Option[T]) Option[U] {
	return FlatMap(t, func(f Func1[T, U]) Option[U] {
		return Map(a, f)
	})
}

func Map[T, U any](opt Option[T], f func(v T) U) Option[U] {
	return FlatMap(opt, func(v T) Option[U] {
		return Some(f(v))
	})
}

func FlatMap[T, U any](opt Option[T], fn func(v T) Option[U]) Option[U] {
	if opt.IsDefined() {
		return fn(opt.Get())
	}
	return None[U]()
}

type ApplicativeFunctor1[H Header[HT], HT, A, R any] struct {
	h  Option[H]
	fn Option[Func1[A, R]]
}

func (r ApplicativeFunctor1[H, HT, A, R]) ApOption(a Option[A]) Option[R] {
	return Ap(r.fn, a)
}

func (r ApplicativeFunctor1[H, HT, A, R]) Ap(a A) Option[R] {
	return r.ApOption(Some(a))
}

func Applicative1[A, R any](fn Func1[A, R]) ApplicativeFunctor1[Nil, Nil, A, R] {
	return ApplicativeFunctor1[Nil, Nil, A, R]{Some(Empty()), Some(fn)}
}

type ApplicativeFunctor2[H Header[HT], HT, A1, A2, R any] struct {
	h  Option[H]
	fn Option[Func1[A1, Func1[A2, R]]]
}

func (r ApplicativeFunctor2[H, HT, A1, A2, R]) ApOption(a Option[A1]) ApplicativeFunctor1[Cons[A1, H], A1, A2, R] {

	nh := FlatMap(r.h, func(hv H) Option[Cons[A1, H]] {
		return Map(a, func(av A1) Cons[A1, H] {
			return Concat(av, hv)
		})
	})

	return ApplicativeFunctor1[Cons[A1, H], A1, A2, R]{nh, Ap(r.fn, a)}
}
func (r ApplicativeFunctor2[H, HT, A1, A2, R]) Ap(a A1) ApplicativeFunctor1[Cons[A1, H], A1, A2, R] {

	return r.ApOption(Some(a))
}

func Applicative2[A1, A2, R any](fn Func2[A1, A2, R]) ApplicativeFunctor2[Nil, Nil, A1, A2, R] {
	return ApplicativeFunctor2[Nil, Nil, A1, A2, R]{Some(Empty()), Some(fn.Curried())}
}
func OrdOption[T any](m Ord[T]) Ord[Option[T]] {
	return LessFunc[Option[T]](func(t1 Option[T], t2 Option[T]) bool {
		if !t1.IsDefined() && !t2.IsDefined() {
			return false
		}
		return Applicative2(m.Less).ApOption(t1).ApOption(t2).OrElse(!t1.IsDefined())
	})
}

func Given[T ImplicitOrd]() Ord[T] {
	return LessGiven[T]()
}