// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.

// data-cursors.go
package expr

import (
	"io"
)

type dataCursor struct {
	ds        map[string]Functor
	ctx       ExprContext
	initState bool // true if no item has prodiced yet (this replace di initial Next() call in the contructor)
	index     int
	count     int
	current   any
	lastErr   error
	resource  any
	nextFunc  Functor
	cleanFunc Functor
	resetFunc Functor
}

func NewDataCursor(ctx ExprContext, ds map[string]Functor, resource any) (dc *dataCursor) {
	dc = &dataCursor{
		ds:        ds,
		initState: true,
		index:     -1,
		count:     0,
		current:   nil,
		lastErr:   nil,
		resource:  resource,
		ctx:       ctx.Clone(),
		nextFunc:  ds[NextName],
		cleanFunc: ds[CleanName],
		resetFunc: ds[ResetName],
	}
	//dc.Next()
	return
}

func (dc *dataCursor) Context() ExprContext {
	return dc.ctx
}

func (dc *dataCursor) TypeName() string {
	return "DataCursor"
}

// func mapToString(m map[string]Functor) string {
// 	var sb strings.Builder
// 	sb.WriteByte('{')
// 	for key, _ := range m {
// 		if sb.Len() > 1 {
// 			sb.WriteString(fmt.Sprintf(", %q: func(){}", key))
// 		} else {
// 			sb.WriteString(fmt.Sprintf("%q: func(){}", key))
// 		}
// 	}
// 	sb.WriteByte('}')
// 	return sb.String()
// }

func (dc *dataCursor) String() string {
	return "$()"
	/*
	   var sb strings.Builder
	   sb.WriteString(fmt.Sprintf(`$(
	   index: %d,
	   ds:    %s,
	   ctx:   `, dc.index, mapToString(dc.ds)))
	   CtxToBuilder(&sb, dc.ctx, 1)
	   sb.WriteByte(')')
	   return sb.String()
	*/
}

func (dc *dataCursor) HasOperation(name string) (exists bool) {
	exists = name == IndexName
	if !exists {
		f, ok := dc.ds[name]
		exists = ok && isFunctor(f)
	}
	return
}

func (dc *dataCursor) CallOperation(name string, args map[string]any) (value any, err error) {
	if name == IndexName {
		value = int64(dc.Index())
	} else if functor, ok := dc.ds[name]; ok && isFunctor(functor) {
		if functor == dc.cleanFunc {
			value, err = dc.Clean()
		} else if functor == dc.resetFunc {
			value, err = dc.Reset()
		} else {
			ctx := cloneContext(dc.ctx)
			value, err = functor.InvokeNamed(ctx, name, args)
			exportObjects(dc.ctx, ctx)
		}
	} else {
		err = errNoOperation(name)
	}
	return
}

func (dc *dataCursor) Reset() (success bool, err error) {
	if dc.resetFunc != nil {
		if dc.resource != nil {
			ctx := cloneContext(dc.ctx)
			actualParams := buildActualParams(dc.resetFunc, []any{dc.resource})
			_, err = dc.resetFunc.InvokeNamed(ctx, ResetName, actualParams)
			exportObjects(dc.ctx, ctx)
			dc.index = -1
			dc.count = 0
			dc.initState = true
			dc.current = nil
			dc.lastErr = nil
			//dc.Next()
		} else {
			err = errInvalidDataSource()
		}
	} else {
		err = errNoOperation(ResetName)
	}
	success = err == nil
	return
}

func (dc *dataCursor) Clean() (success bool, err error) {
	if dc.cleanFunc != nil {
		if dc.resource != nil {
			ctx := cloneContext(dc.ctx)
			actualParams := buildActualParams(dc.cleanFunc, []any{dc.resource})
			_, err = dc.cleanFunc.InvokeNamed(ctx, CleanName, actualParams)
			//			dc.resource = nil
			exportObjects(dc.ctx, ctx)
		} else {
			err = errInvalidDataSource()
		}
	} else {
		err = errNoOperation(CleanName)
	}
	success = err == nil
	return
}

func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at the last item
	dc.init()

	if dc.current != nil {
		item = dc.current
	} else {
		err = io.EOF
	}
	return
}

func (dc *dataCursor) checkFilter(filter Functor, item any) (accepted bool, err error) {
	var v any
	var ok bool
	ctx := cloneContext(dc.ctx)

	actualParams := buildActualParams(filter, []any{item, dc.index})
	if v, err = filter.InvokeNamed(ctx, FilterName, actualParams); err == nil && v != nil {
		if accepted, ok = v.(bool); !ok {
			accepted = true // NOTE: A non-boolean value that is not nil means the item has been accepted
		}
	}
	return
}

func (dc *dataCursor) mapItem(mapper Functor, item any) (mappedItem any, err error) {
	ctx := cloneContext(dc.ctx)
	actualParams := buildActualParams(mapper, []any{item, dc.index})
	mappedItem, err = mapper.InvokeNamed(ctx, MapName, actualParams)
	return
}

func (dc *dataCursor) init() {
	if dc.initState {
		dc.initState = false
		dc.Next()
	}
}

func (dc *dataCursor) Next() (current any, err error) { // must return io.EOF after the last item
	if dc.initState {
		dc.init()
	} else if err = dc.lastErr; err != nil {
		return
	}
	current = dc.current
	if dc.resource != nil {
		filter := dc.ds[FilterName]
		mapper := dc.ds[MapName]
		var item any
		for item == nil && dc.lastErr == nil {
			ctx := cloneContext(dc.ctx)
			dc.index++

			actualParams := buildActualParams(dc.nextFunc, []any{dc.resource, dc.index})
			if item, dc.lastErr = dc.nextFunc.InvokeNamed(ctx, NextName, actualParams); dc.lastErr == nil {
				if item == nil {
					dc.lastErr = io.EOF
				} else {
					accepted := true
					if filter != nil {
						if accepted, dc.lastErr = dc.checkFilter(filter, item); dc.lastErr != nil || !accepted {
							item = nil
						}
					}
					if accepted {
						dc.count++
					}
					if item != nil && mapper != nil {
						item, dc.lastErr = dc.mapItem(mapper, item)
					}
				}
			}
			exportObjects(dc.ctx, ctx)
		}
		dc.current = item
		if dc.lastErr != nil {
			dc.index--
			dc.Clean()
		}
	} else {
		dc.lastErr = errInvalidDataSource()
	}
	return
}

func (dc *dataCursor) Index() int {
	return dc.index - 1
}

func (dc *dataCursor) Count() int {
	return dc.count
}

