// 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) Index() int {
	return dc.index
}
