package flow

//
// Find a way to improve this mess, maybe it can be merged in one func
//
//

import (
	"errors"
	"fmt"
	"log"
	"reflect"
	"sync"
)

// OpCtx operation Context
type OpCtx = *sync.Map

// NewOpCtx creates a running context
func newOpCtx() OpCtx {
	return &sync.Map{}
}

// dumbSet
func dumbSet(params ...Data) {}

// Operation interface
type Operation interface { // Id perhaps?
	ID() string
	Set(inputs ...Data) // Special var method
	Process(params ...Data) Data
}

// Run Context actually not OpCTX

//local operation information
type operation struct {
	flow    *Flow
	id      interface{} // Interface key
	kind    string
	set     func(params ...Data)
	process func(ctx OpCtx, params ...Data) Data
}

// Id returns string Id of the operaton
func (o *operation) ID() string {
	return fmt.Sprint(o.id)
}

// Process operation process wrapper
func (o *operation) Process(params ...Data) Data {
	return o.processWithCtx(newOpCtx(), params...)
}

// Every single one is run with this internally
func (o *operation) processWithCtx(ctx OpCtx, params ...Data) Data {
	return o.process(ctx, params...)
	/*entry, _ := o.flow.getOp(fmt.Sprint(o.id))
	if entry == nil {
		log.Println("Entry is nil for id:", o.id, ", why??")
		return nil
	}
	entry.Lock()
	defer entry.Unlock()

	if o.flow.err != nil {
		return nil
	}
	if ctx == nil { // No cache/Context
	}
	if v, ok := ctx.Load(o.id); ok {
		return v
	}
	res := o.process(ctx, params...)
	ctx.Store(o.id, res)
	return res*/
}

// Set setter for certain operations (Var)
func (o *operation) Set(params ...Data) {
	o.set(params...)
}

//////////////////////////////////////
// Operators definition
///////////////

func opIn(f *Flow, id int) *operation {
	return &operation{
		flow: f,
		id:   id,
		kind: "in",
		set:  dumbSet,
		process: func(ctx OpCtx, params ...Data) Data {
			if id >= len(params) || id < 0 {
				f.err = errors.New("invalid input")
				return nil
			}
			return params[id]
		},
	}
}
func opConst(f *Flow, id string) *operation {
	return &operation{
		flow: f,
		id:   id,
		kind: "const",
		set:  dumbSet,
		process: func(ctx OpCtx, params ...Data) Data {
			ret := f.consts[id]
			return ret
		},
	}
}

// Debug type
func opFunc(f *Flow, id string) *operation {
	return &operation{
		flow: f,
		id:   id,
		kind: "func",
		set:  dumbSet,
		process: func(ctx OpCtx, params ...Data) Data {

			defer func() {
				if r := recover(); r != nil {
					f.Err(fmt.Errorf("Panic: %v", r))
					f.hooks.error(id, f.Err())
				}
			}()

			op, ok := f.getOp(id)
			if !ok {
				f.err = fmt.Errorf("invalid operation '%s'", id)
				log.Println("Operation not ok", f.err)
				f.hooks.error(id, f.err)
				return nil
			}

			if f.err != nil {
				return nil
			}
			op.Lock()
			defer op.Unlock()
			if ctx != nil {
				if v, ok := ctx.Load(id); ok {
					return v
				}
			}

			// Check inputs
			fnval := reflect.ValueOf(op.executor)
			if fnval.Type().NumIn() != len(op.inputs) {
				err := fmt.Errorf("expect %d inputs got %d", fnval.Type().NumIn(), len(op.inputs))
				f.hooks.error(id, f.err)
				f.Err(err)
				return nil
			}
			/////////////////////////////
			// NEW PARALLEL PROCESSING
			///////////
			f.hooks.wait(id)

			callParam := make([]reflect.Value, len(op.inputs))

			// Parallel processing if inputs
			wg := sync.WaitGroup{}
			wg.Add(len(op.inputs))
			for i, in := range op.inputs {
				go func(i int, in *operation) {
					defer wg.Done()
					fr := in.processWithCtx(ctx, params...)
					p := reflect.ValueOf(fr)

					// Error checking
					if !p.IsValid() {
						f.Err(fmt.Errorf("Input %d is not valid", i))
						return
					}
					if !p.Type().AssignableTo(fnval.Type().In(i)) {
						f.Err(fmt.Errorf("Input %d not assignable to %v", i, p.Type()))
					}

					callParam[i] = reflect.ValueOf(fr)
				}(i, in)
			}
			wg.Wait()

			f.hooks.start(id)

			if f.Err() != nil {
				f.hooks.error(id, f.err)
				return nil
			}

			fnret := fnval.Call(callParam)
			if len(fnret) > 1 && (fnret[1].Interface() != nil) {
				err, ok := fnret[1].Interface().(error)
				if !ok {
					err = errors.New("unknown error")
				}
				f.Err(err)
				f.hooks.error(id, err)
				return nil
			}

			// THE RESULT
			ret := fnret[0].Interface()
			if ctx != nil {
				ctx.Store(id, ret)
			}
			f.hooks.finish(id, ret)
			return ret
		},
	}
}

func opVar(f *Flow, id string) *operation {
	return &operation{
		flow:    f,
		id:      id,
		kind:    "var",
		set:     func(params ...Data) { f.data[id] = params[0] },
		process: func(ctx OpCtx, params ...Data) Data { return f.data[id] },
	}
}

func opNil(f *Flow) *operation {
	return &operation{
		flow:    f,
		kind:    "nil",
		process: func(ctx OpCtx, params ...Data) Data { f.err = errors.New("Nil operation"); return nil },
	}
}
