// Copyright 2022 Gregory Petrosyan <gregory.petrosyan@gmail.com>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

package rapid

import (
	"fmt"
	"reflect"
)

// Make creates a generator of values of type V, using reflection to infer the required structure.
func Make[V any]() *Generator[V] {
	var zero V
	gen := newMakeGen(reflect.TypeOf(zero))
	return newGenerator[V](&makeGen[V]{
		gen: gen,
	})
}

type makeGen[V any] struct {
	gen *Generator[any]
}

func (g *makeGen[V]) String() string {
	var zero V
	return fmt.Sprintf("Make[%T]()", zero)
}

func (g *makeGen[V]) value(t *T) V {
	return g.gen.value(t).(V)
}

func newMakeGen(typ reflect.Type) *Generator[any] {
	gen, mayNeedCast := newMakeKindGen(typ)
	if !mayNeedCast || typ.String() == typ.Kind().String() {
		return gen // fast path with less reflect
	}
	return newGenerator[any](&castGen{gen, typ})
}

type castGen struct {
	gen *Generator[any]
	typ reflect.Type
}

func (g *castGen) String() string {
	return fmt.Sprintf("cast(%v, %v)", g.gen, g.typ.Name())
}

func (g *castGen) value(t *T) any {
	v := g.gen.value(t)
	return reflect.ValueOf(v).Convert(g.typ).Interface()
}

func newMakeKindGen(typ reflect.Type) (gen *Generator[any], mayNeedCast bool) {
	switch typ.Kind() {
	case reflect.Bool:
		return Bool().AsAny(), true
	case reflect.Int:
		return Int().AsAny(), true
	case reflect.Int8:
		return Int8().AsAny(), true
	case reflect.Int16:
		return Int16().AsAny(), true
	case reflect.Int32:
		return Int32().AsAny(), true
	case reflect.Int64:
		return Int64().AsAny(), true
	case reflect.Uint:
		return Uint().AsAny(), true
	case reflect.Uint8:
		return Uint8().AsAny(), true
	case reflect.Uint16:
		return Uint16().AsAny(), true
	case reflect.Uint32:
		return Uint32().AsAny(), true
	case reflect.Uint64:
		return Uint64().AsAny(), true
	case reflect.Uintptr:
		return Uintptr().AsAny(), true
	case reflect.Float32:
		return Float32().AsAny(), true
	case reflect.Float64:
		return Float64().AsAny(), true
	case reflect.Array:
		return genAnyArray(typ), false
	case reflect.Map:
		return genAnyMap(typ), false
	case reflect.Pointer:
		return Deferred(func() *Generator[any] { return genAnyPointer(typ) }), false
	case reflect.Slice:
		return genAnySlice(typ), false
	case reflect.String:
		return String().AsAny(), true
	case reflect.Struct:
		return genAnyStruct(typ), false
	default:
		panic(fmt.Sprintf("unsupported type kind for Make: %v", typ.Kind()))
	}
}

func genAnyPointer(typ reflect.Type) *Generator[any] {
	elem := typ.Elem()
	elemGen := newMakeGen(elem)
	const pNonNil = 0.5

	return Custom[any](func(t *T) any {
		if flipBiasedCoin(t.s, pNonNil) {
			val := elemGen.value(t)
			ptr := reflect.New(elem)
			ptr.Elem().Set(reflect.ValueOf(val))
			return ptr.Interface()
		} else {
			return reflect.Zero(typ).Interface()
		}
	})
}

func genAnyArray(typ reflect.Type) *Generator[any] {
	count := typ.Len()
	elemGen := newMakeGen(typ.Elem())

	return Custom[any](func(t *T) any {
		a := reflect.Indirect(reflect.New(typ))
		if count == 0 {
			t.s.drawBits(0)
		} else {
			for i := 0; i < count; i++ {
				e := reflect.ValueOf(elemGen.value(t))
				a.Index(i).Set(e)
			}
		}
		return a.Interface()
	})
}

func genAnySlice(typ reflect.Type) *Generator[any] {
	elemGen := newMakeGen(typ.Elem())

	return Custom[any](func(t *T) any {
		repeat := newRepeat(-1, -1, -1, elemGen.String())
		sl := reflect.MakeSlice(typ, 0, repeat.avg())
		for repeat.more(t.s) {
			e := reflect.ValueOf(elemGen.value(t))
			sl = reflect.Append(sl, e)
		}
		return sl.Interface()
	})
}

func genAnyMap(typ reflect.Type) *Generator[any] {
	keyGen := newMakeGen(typ.Key())
	valGen := newMakeGen(typ.Elem())

	return Custom[any](func(t *T) any {
		label := keyGen.String() + "," + valGen.String()
		repeat := newRepeat(-1, -1, -1, label)
		m := reflect.MakeMapWithSize(typ, repeat.avg())
		for repeat.more(t.s) {
			k := reflect.ValueOf(keyGen.value(t))
			v := reflect.ValueOf(valGen.value(t))
			if m.MapIndex(k).IsValid() {
				repeat.reject()
			} else {
				m.SetMapIndex(k, v)
			}
		}
		return m.Interface()
	})
}

func genAnyStruct(typ reflect.Type) *Generator[any] {
	numFields := typ.NumField()
	fieldGens := make([]*Generator[any], numFields)
	for i := 0; i < numFields; i++ {
		fieldGens[i] = newMakeGen(typ.Field(i).Type)
	}

	return Custom[any](func(t *T) any {
		s := reflect.Indirect(reflect.New(typ))
		if numFields == 0 {
			t.s.drawBits(0)
		} else {
			for i := 0; i < numFields; i++ {
				f := reflect.ValueOf(fieldGens[i].value(t))
				s.Field(i).Set(f)
			}
		}
		return s.Interface()
	})
}
