package dynamicstruct

import "reflect"

type (
	// Builder holds all fields' definitions for desired structs.
	// It gives opportunity to add or remove fields, or to edit
	// existing ones. In the end, it provides definition for
	// dynamic structs, used to create new instances.
	Builder interface {
		// AddField creates new struct's field.
		// It expects field's name, type and string.
		// Type is provided as an instance of some golang type.
		// Tag is provided as classical golang field tag.
		//
		// builder.AddField("SomeFloatField", 0.0, `json:"boolean" validate:"gte=10"`)
		//
		AddField(name string, typ interface{}, tag string) Builder
		// RemoveField removes existing struct's field.
		//
		// builder.RemoveField("SomeFloatField")
		//
		RemoveField(name string) Builder
		// HasField checks if struct has a field with a given name.
		//
		// if builder.HasField("SomeFloatField") { ...
		//
		HasField(name string) bool
		// GetField returns struct's field definition.
		// If there is no such field, it returns nil.
		// Usable to edit existing struct's field.
		//
		// field := builder.GetField("SomeFloatField")
		//
		GetField(name string) FieldConfig
		// Build returns definition for dynamic struct.
		// Definition can be used to create new instances.
		//
		// dStruct := builder.Build()
		//
		Build() DynamicStruct
	}

	// FieldConfig holds single field's definition.
	// It provides possibility to edit field's type and tag.
	FieldConfig interface {
		// SetType changes field's type.
		// Expected value is an instance of golang type.
		//
		// field.SetType([]int{})
		//
		SetType(typ interface{}) FieldConfig
		// SetTag changes fields's tag.
		// Expected value is an string which represents classical
		// golang tag.
		//
		// field.SetTag(`json:"slice"`)
		//
		SetTag(tag string) FieldConfig
	}

	// DynamicStruct contains defined dynamic struct.
	// This definition can't be changed anymore, once is built.
	// It provides a method for creating new instances of same defintion.
	DynamicStruct interface {
		// New provides new instance of defined dynamic struct.
		//
		// value := dStruct.New()
		//
		New() interface{}

		// NewSliceOfStructs provides new slice of defined dynamic struct, with 0 length and capacity.
		//
		// value := dStruct.NewSliceOfStructs()
		//
		NewSliceOfStructs() interface{}

		// New provides new map of defined dynamic struct with desired key type.
		//
		// value := dStruct.NewMapOfStructs("")
		//
		NewMapOfStructs(key interface{}) interface{}
	}

	builderImpl struct {
		fields []*fieldConfigImpl
	}

	fieldConfigImpl struct {
		name      string
		pkg       string
		typ       interface{}
		tag       string
		anonymous bool
	}

	dynamicStructImpl struct {
		definition reflect.Type
	}
)

// NewStruct returns new clean instance of Builder interface
// for defining fresh dynamic struct.
//
// builder := dynamicstruct.NewStruct()
//
func NewStruct() Builder {
	return &builderImpl{
		fields: []*fieldConfigImpl{},
	}
}

// ExtendStruct extends existing instance of struct and
// returns new instance of Builder interface.
//
// builder := dynamicstruct.MergeStructs(MyStruct{})
//
func ExtendStruct(value interface{}) Builder {
	return MergeStructs(value)
}

// MergeStructs merges a list of existing instances of structs and
// returns new instance of Builder interface.
//
// builder := dynamicstruct.MergeStructs(MyStructOne{}, MyStructTwo{}, MyStructThree{})
//
func MergeStructs(values ...interface{}) Builder {
	builder := NewStruct()

	for _, value := range values {
		valueOf := reflect.Indirect(reflect.ValueOf(value))
		typeOf := valueOf.Type()

		for i := 0; i < valueOf.NumField(); i++ {
			fval := valueOf.Field(i)
			ftyp := typeOf.Field(i)
			builder.(*builderImpl).addField(ftyp.Name, ftyp.PkgPath, fval.Interface(), string(ftyp.Tag), ftyp.Anonymous)
		}
	}

	return builder
}

func (b *builderImpl) AddField(name string, typ interface{}, tag string) Builder {
	return b.addField(name, "", typ, tag, false)
}

func (b *builderImpl) addField(name string, pkg string, typ interface{}, tag string, anonymous bool) Builder {
	b.fields = append(b.fields, &fieldConfigImpl{
		name:      name,
		typ:       typ,
		tag:       tag,
		anonymous: anonymous,
	})

	return b
}

func (b *builderImpl) RemoveField(name string) Builder {
	for i := range b.fields {
		if b.fields[i].name == name {
			b.fields = append(b.fields[:i], b.fields[i+1:]...)
			break
		}
	}
	return b
}

func (b *builderImpl) HasField(name string) bool {
	for i := range b.fields {
		if b.fields[i].name == name {
			return true
		}
	}
	return false
}

func (b *builderImpl) GetField(name string) FieldConfig {
	for i := range b.fields {
		if b.fields[i].name == name {
			return b.fields[i]
		}
	}
	return nil
}

func (b *builderImpl) Build() DynamicStruct {
	var structFields []reflect.StructField

	for _, field := range b.fields {
		structFields = append(structFields, reflect.StructField{
			Name:      field.name,
			PkgPath:   field.pkg,
			Type:      reflect.TypeOf(field.typ),
			Tag:       reflect.StructTag(field.tag),
			Anonymous: field.anonymous,
		})
	}

	return &dynamicStructImpl{
		definition: reflect.StructOf(structFields),
	}
}

func (f *fieldConfigImpl) SetType(typ interface{}) FieldConfig {
	f.typ = typ
	return f
}

func (f *fieldConfigImpl) SetTag(tag string) FieldConfig {
	f.tag = tag
	return f
}

func (ds *dynamicStructImpl) New() interface{} {
	return reflect.New(ds.definition).Interface()
}

func (ds *dynamicStructImpl) NewSliceOfStructs() interface{} {
	return reflect.New(reflect.SliceOf(ds.definition)).Interface()
}

func (ds *dynamicStructImpl) NewMapOfStructs(key interface{}) interface{} {
	return reflect.New(reflect.MapOf(reflect.Indirect(reflect.ValueOf(key)).Type(), ds.definition)).Interface()
}
