// 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 iso

import (
	"testing"

	EQT "github.com/IBM/fp-go/eq/testing"
	F "github.com/IBM/fp-go/function"
	L "github.com/IBM/fp-go/optics/lens"
	LT "github.com/IBM/fp-go/optics/lens/testing"
	O "github.com/IBM/fp-go/option"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

type (
	Inner struct {
		Value *int
		Foo   string
	}

	Outer struct {
		inner Inner
	}
)

func (outer Outer) GetInner() Inner {
	return outer.inner
}

func (outer Outer) SetInner(inner Inner) Outer {
	outer.inner = inner
	return outer
}

func (inner Inner) GetValue() *int {
	return inner.Value
}

func (inner Inner) SetValue(value *int) Inner {
	inner.Value = value
	return inner
}

func TestIso(t *testing.T) {

	eqOptInt := O.Eq(EQT.Eq[int]())
	eqOuter := EQT.Eq[Outer]()

	emptyOuter := Outer{}

	// iso
	intIso := FromNillable[int]()

	innerFromOuter := L.MakeLens((Outer).GetInner, (Outer).SetInner)
	valueFromInner := L.MakeLens((Inner).GetValue, (Inner).SetValue)

	optValueFromInner := F.Pipe1(
		valueFromInner,
		Compose[Inner](intIso),
	)

	optValueFromOuter := F.Pipe1(
		innerFromOuter,
		L.Compose[Outer](optValueFromInner),
	)

	// try some access
	require.True(t, eqOptInt.Equals(optValueFromOuter.Get(emptyOuter), O.None[int]()))

	updatedOuter := optValueFromOuter.Set(O.Some(1))(emptyOuter)

	require.True(t, eqOptInt.Equals(optValueFromOuter.Get(updatedOuter), O.Some(1)))
	secondOuter := optValueFromOuter.Set(O.None[int]())(updatedOuter)
	require.True(t, eqOptInt.Equals(optValueFromOuter.Get(secondOuter), O.None[int]()))

	// check if this obeys laws
	laws := LT.AssertLaws(
		t,
		eqOptInt,
		eqOuter,
	)(optValueFromOuter)

	assert.True(t, laws(emptyOuter, O.Some(2)))
}
