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

// data-cursors.go
package expr

import (
	"errors"
	"io"
)

type dataCursor struct {
	ds          map[string]Functor
	ctx         ExprContext
	index       int
	resource    any
	nextFunc    Functor
	cleanFunc   Functor
	resetFunc   Functor
	currentFunc Functor
}

func newDataCursor(ctx ExprContext, ds map[string]Functor) (dc *dataCursor) {
	dc = &dataCursor{
		ds:    ds,
		index: -1,
		ctx:   ctx.Clone(),
	}
	return
}

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 []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.Invoke(ctx, name, []any{})
			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)
			if _, err = dc.resetFunc.Invoke(ctx, resetName, []any{dc.resource}); err == nil {
				dc.index = -1
			}
			exportObjects(dc.ctx, ctx)
		} 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)
			_, err = dc.cleanFunc.Invoke(ctx, cleanName, []any{dc.resource})
			dc.resource = nil
			exportObjects(dc.ctx, ctx)
		} else {
			err = errInvalidDataSource()
		}
	} else {
		err = errors.New("no 'clean' function defined in the data-source")
	}
	success = err == nil
	return
}

func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at the last item
	ctx := cloneContext(dc.ctx)
	if item, err = dc.currentFunc.Invoke(ctx, currentName, []any{}); err == nil && item == nil {
		err = io.EOF
	}
	exportObjects(dc.ctx, ctx)
	return
}

// func (dc *dataCursor) _Next() (item any, err error) { // must return io.EOF after the last item
// 	if dc.resource != nil {
// 		ctx := cloneContext(dc.ctx)
// 		// fmt.Printf("Entering Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0))
// 		if item, err = dc.nextFunc.Invoke(ctx, nextName, []any{dc.resource}); err == nil {
// 			if item == nil {
// 				err = io.EOF
// 			} else {
// 				dc.index++
// 			}
// 		}
// 		// fmt.Printf("Exiting Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0))
// 		exportObjects(dc.ctx, ctx)
// 		// fmt.Printf("Outer-Ctx [%p]: %s\n", dc.ctx, CtxToString(dc.ctx, 0))
// 	} else {
// 		err = errInvalidDataSource()
// 	}
// 	return
// }

// func (dc *dataCursor) _filter(item any) (filterdItem any, err error) {
// 	if filter, ok := dc.ds[filterName]; ok {
// 		ctx := cloneContext(dc.ctx)
// 		filterdItem, err = filter.Invoke(ctx, filterName, []any{item, dc.index})
// 	} else {
// 		filterdItem = item
// 	}
// 	return
// }

func (dc *dataCursor) checkFilter(filter Functor, item any) (accepted bool, err error) {
	var v any
	var ok bool
	ctx := cloneContext(dc.ctx)
	if v, err = filter.Invoke(ctx, filterName, []any{item, dc.index}); 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)
	mappedItem, err = mapper.Invoke(ctx, mapName, []any{item, dc.index});
	return
}

func (dc *dataCursor) Next() (item any, err error) { // must return io.EOF after the last item
	var accepted bool
	if dc.resource != nil {
		filter := dc.ds[filterName]
		mapper := dc.ds[mapName]

		for item == nil && err == nil {
			ctx := cloneContext(dc.ctx)
			if item, err = dc.nextFunc.Invoke(ctx, nextName, []any{dc.resource}); err == nil {
				if item == nil {
					err = io.EOF
				} else {
					dc.index++
					if filter != nil {
						if accepted, err = dc.checkFilter(filter, item); err != nil || !accepted {
							item = nil
						}
					}
					if item != nil && mapper != nil {
						item, err = dc.mapItem(mapper, item)
					}
				}
			}
			exportObjects(dc.ctx, ctx)
		}
	} else {
		err = errInvalidDataSource()
	}
	return
}

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