// Copyright (c) 2023 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package reader

import (
	G "github.com/IBM/fp-go/reader/generic"
	T "github.com/IBM/fp-go/tuple"
)

// The purpose of the `Reader` monad is to avoid threading arguments through multiple functions in order to only get them where they are needed.
// The first template argument `R` is the the context to read from, the second argument `A` is the return value of the monad
type Reader[R, A any] func(R) A

// MakeReader creates a reader, i.e. a method that accepts a context and that returns a value
func MakeReader[R, A any](r Reader[R, A]) Reader[R, A] {
	return G.MakeReader(r)
}

// Ask reads the current context
func Ask[R any]() Reader[R, R] {
	return G.Ask[Reader[R, R]]()
}

// Asks projects a value from the global context in a Reader
func Asks[R, A any](f Reader[R, A]) Reader[R, A] {
	return G.Asks(f)
}

func AsksReader[R, A any](f func(R) Reader[R, A]) Reader[R, A] {
	return G.AsksReader(f)
}

func MonadMap[E, A, B any](fa Reader[E, A], f func(A) B) Reader[E, B] {
	return G.MonadMap[Reader[E, A], Reader[E, B]](fa, f)
}

// Map can be used to turn functions `func(A)B` into functions `(fa F[A])F[B]` whose argument and return types
// use the type constructor `F` to represent some computational context.
func Map[E, A, B any](f func(A) B) func(Reader[E, A]) Reader[E, B] {
	return G.Map[Reader[E, A], Reader[E, B]](f)
}

func MonadAp[B, R, A any](fab Reader[R, func(A) B], fa Reader[R, A]) Reader[R, B] {
	return G.MonadAp[Reader[R, A], Reader[R, B]](fab, fa)
}

// Ap applies a function to an argument under a type constructor.
func Ap[B, R, A any](fa Reader[R, A]) func(Reader[R, func(A) B]) Reader[R, B] {
	return G.Ap[Reader[R, A], Reader[R, B], Reader[R, func(A) B]](fa)
}

func Of[R, A any](a A) Reader[R, A] {
	return G.Of[Reader[R, A]](a)
}

func MonadChain[R, A, B any](ma Reader[R, A], f func(A) Reader[R, B]) Reader[R, B] {
	return G.MonadChain(ma, f)
}

// Chain composes computations in sequence, using the return value of one computation to determine the next computation.
func Chain[R, A, B any](f func(A) Reader[R, B]) func(Reader[R, A]) Reader[R, B] {
	return G.Chain[Reader[R, A]](f)
}

func Flatten[R, A any](mma func(R) Reader[R, A]) Reader[R, A] {
	return G.Flatten(mma)
}

func Compose[R, B, C any](ab Reader[R, B]) func(Reader[B, C]) Reader[R, C] {
	return G.Compose[Reader[R, B], Reader[B, C], Reader[R, C]](ab)
}

func First[A, B, C any](pab Reader[A, B]) Reader[T.Tuple2[A, C], T.Tuple2[B, C]] {
	return G.First[Reader[A, B], Reader[T.Tuple2[A, C], T.Tuple2[B, C]]](pab)
}

func Second[A, B, C any](pbc Reader[B, C]) Reader[T.Tuple2[A, B], T.Tuple2[A, C]] {
	return G.Second[Reader[B, C], Reader[T.Tuple2[A, B], T.Tuple2[A, C]]](pbc)
}

func Promap[E, A, D, B any](f func(D) E, g func(A) B) func(Reader[E, A]) Reader[D, B] {
	return G.Promap[Reader[E, A], Reader[D, B]](f, g)
}

// Local changes the value of the local context during the execution of the action `ma` (similar to `Contravariant`'s
// `contramap`).
func Local[R2, R1, A any](f func(R2) R1) func(Reader[R1, A]) Reader[R2, A] {
	return G.Local[Reader[R1, A], Reader[R2, A]](f)
}

// Read applies a context to a reader to obtain its value
func Read[E, A any](e E) func(Reader[E, A]) A {
	return G.Read[Reader[E, A]](e)
}

func MonadFlap[R, A, B any](fab Reader[R, func(A) B], a A) Reader[R, B] {
	return G.MonadFlap[Reader[R, func(A) B], Reader[R, B]](fab, a)
}

func Flap[R, A, B any](a A) func(Reader[R, func(A) B]) Reader[R, B] {
	return G.Flap[Reader[R, func(A) B], Reader[R, B]](a)
}
