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

// data-cursors.go
package expr

import (
	"io"
	"slices"
)

type dataCursor struct {
	ds        map[string]Functor
	ctx       ExprContext
	initState bool // true if no item has produced yet (this replace di initial Next() call in the contructor)
	// cursorValid bool // true if resource is nil or if clean has not yet been called
	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,
		// cursorValid: true,
		index:     -1,
		count:     0,
		current:   nil,
		lastErr:   nil,
		resource:  resource,
		ctx:       ctx.Clone(),
		nextFunc:  ds[NextName],
		cleanFunc: ds[CleanName],
		resetFunc: ds[ResetName],
	}
	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 = slices.Contains([]string{CleanName, ResetName, CurrentName, IndexName}, name)
	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 name == CleanName {
		err = dc.Clean()
	} else if name == ResetName {
		err = dc.Reset()
	} else if functor, ok := dc.ds[name]; ok && isFunctor(functor) {
		ctx := cloneContext(dc.ctx)
		value, err = functor.InvokeNamed(ctx, name, args)
		exportObjects(dc.ctx, ctx)
	} else {
		err = errNoOperation(name)
	}
	return
}

// func (dc *dataCursor) Reset() (err error) {
// 	if dc.resetFunc != nil {
// 		if dc.resource != nil {
// 			ctx := cloneContext(dc.ctx)
// 			actualParams := bindActualParams(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
// 		} else {
// 			err = errInvalidDataSource()
// 		}
// 	} else {
// 		err = errNoOperation(ResetName)
// 	}
// 	return
// }

func (dc *dataCursor) Reset() (err error) {
	if dc.resetFunc != nil {
		ctx := cloneContext(dc.ctx)
		actualParams := bindActualParams(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
	return
}

func (dc *dataCursor) Clean() (err error) {
	if dc.cleanFunc != nil {
		ctx := cloneContext(dc.ctx)
		actualParams := bindActualParams(dc.cleanFunc, []any{dc.resource})
		_, err = dc.cleanFunc.InvokeNamed(ctx, CleanName, actualParams)
		exportObjects(dc.ctx, ctx)
	}
	dc.lastErr = io.EOF
	return
}

// func (dc *dataCursor) Clean() (err error) {
// 	if dc.cleanFunc != nil {
// 		if dc.resource != nil {
// 			ctx := cloneContext(dc.ctx)
// 			actualParams := bindActualParams(dc.cleanFunc, []any{dc.resource})
// 			_, err = dc.cleanFunc.InvokeNamed(ctx, CleanName, actualParams)
// 			exportObjects(dc.ctx, ctx)
// 		} else {
// 			err = errInvalidDataSource()
// 		}
// 	} else {
// 		err = errNoOperation(CleanName)
// 	}
// 	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 := bindActualParams(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 := bindActualParams(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
	filter := dc.ds[FilterName]
	mapper := dc.ds[MapName]
	var item any
	for item == nil && dc.lastErr == nil {
		ctx := cloneContext(dc.ctx)
		dc.index++

		actualParams := bindActualParams(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()
	}
	return
}

// 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 := bindActualParams(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
}