package flow

import (
	"bytes"
	"encoding/json"
	"errors"
	"flow/registry"
	"fmt"
	"io"
	"os"
	"reflect"
	"sync"
)

// Data interface
type Data = interface{}

type opEntry struct {
	sync.Mutex
	name     string
	inputs   []*operation // still figuring, might be operation
	executor interface{}
}

// Flow structure
// We could Create a single array of operations
// refs would only mean id, types would be embed in operation
type Flow struct {
	sync.Mutex

	registry   *registry.R
	idGen      func() string
	consts     map[string]Data
	data       map[string]Data // Should be named, to fetch later
	operations sync.Map
	//map[string]*opEntry
	err   error
	runID int

	// Experimental run Event
	hooks Hooks
	//func(name string, payLoad map[string]Data)
}

// New create a new flow
func New() *Flow {
	return &Flow{
		registry: registry.Global,
		idGen:    func() string { return RandString(8) },
		// Data
		consts:     map[string]Data{},
		data:       map[string]Data{},
		operations: sync.Map{},
		//map[string]opEntry{},
	}
}

// Err Set or get current error
func (f *Flow) Err(p ...interface{}) error {
	f.Lock()
	defer f.Unlock()

	if len(p) == 0 {
		return f.err
	}

	if err, ok := p[0].(error); ok {
		f.err = err
	}
	return f.err
}

//SetRegistry use the registry specified
func (f *Flow) SetRegistry(r *registry.R) *Flow {
	f.registry = r
	// chain
	return f
}

//SetIDGen set the id generator that will generate
//ID for new nodes
func (f *Flow) SetIDGen(idGen func() string) {
	f.idGen = idGen
}

// DefOp Manual tag an Operation
func (f *Flow) DefOp(id string, name string, params ...interface{}) Operation {
	inputs := make([]*operation, len(params))
	for i, p := range params {
		switch v := p.(type) {
		case *operation:
			inputs[i] = v
		default:
			//log.Println("WARNING defining const with value", v)
			inputs[i] = f.Const(v).(*operation)
		}
	}
	// Grab executor here
	executor, err := f.registry.Get(name)
	if err != nil {
		f.err = err
		return opNil(f)
	}
	f.operations.Store(id, &opEntry{sync.Mutex{}, name, inputs, executor})
	return opFunc(f, id)
}

// Res returns a deferred operation result
// passing the Id
func (f *Flow) Res(id string) Operation {
	// Defered operation
	return opFunc(f, id)
}

// Op return an function operator
//  name - a previous registered function
//  params - the function inputs
func (f *Flow) Op(name string, params ...interface{}) Operation {
	// Use this on Set?
	inputs := make([]*operation, len(params))
	for i, p := range params {
		switch v := p.(type) {
		case *operation:
			inputs[i] = v
		default:
			// fail here
			//log.Println("WARNING defining const with value", v)
			inputs[i] = f.Const(v).(*operation)
		}
	}

	// Grab executor here
	executor, err := f.registry.Get(name)
	if err != nil {
		f.err = err
		return opNil(f)
	}
	// generate ID
	for i := 0; i < 10; i++ {
		id := f.idGen()
		if _, ok := f.operations.Load(id); ok {
			continue
		}
		f.operations.Store(id, &opEntry{sync.Mutex{}, name, inputs, executor})
		return opFunc(f, id)
	}
	f.err = errors.New("ID exausted")
	return opNil(f)
	// Initialize opfunc maybe
}

// Const returns a const operation
func (f *Flow) Const(value Data) Operation {
	// generate ID
	for i := 0; i < 10; i++ {
		id := f.idGen()
		if _, ok := f.consts[id]; ok {
			continue
		}
		f.consts[id] = value
		return opConst(f, id)
	}
	f.err = errors.New("ID exausted")
	return opNil(f)
}

// Var operation
func (f *Flow) Var(name string, initial ...Data) Operation {
	if _, ok := f.data[name]; !ok {
		var v interface{}
		if len(initial) > 0 {
			v = initial[0]
		}
		f.data[name] = v
	}
	return opVar(f, name)
}

// In flow input operator
//  paramID - index of the parameter
func (f *Flow) In(paramID int) Operation {
	return opIn(f, paramID)
}

// Run a batch of operation?
func (f *Flow) Run(op Operation, params ...Data) (Data, error) {
	if f.err != nil {
		return nil, f.err
	}
	cache := map[*operation]Data{}
	return f.run(cache, op, params...)
}
func (f *Flow) run(cache map[*operation]Data, op Operation, params ...Data) (Data, error) {
	o := op.(*operation)
	if v, ok := cache[o]; ok {
		return v, nil
	}
	// Manually fetch func data because of caching
	var r Data
	// This is wrong since the only source of func should be on operation
	if o.kind == "func" {
		op, ok := f.getOp(o.id.(string))
		if !ok {
			return nil, fmt.Errorf("Operation %s not found", o.id)
		}
		callParam := make([]reflect.Value, len(op.inputs))
		for i, in := range op.inputs {
			fr, _ := f.run(cache, in, params...) // ignore error
			callParam[i] = reflect.ValueOf(fr)
		}
		r = reflect.ValueOf(op.executor).Call(callParam)[0].Interface()
	} else {
		r = o.process(nil, params...)
	}
	cache[o] = r
	return r, nil
}

// Analyse every operations
func (f *Flow) Analyse(w io.Writer, params ...Data) {
	if w == nil {
		w = os.Stdout
	}
	fmt.Fprintf(w, "Ops analysis:\n")
	f.operations.Range(func(pk, po interface{}) bool {
		k, op := pk.(string), po.(*opEntry)
		fw := bytes.NewBuffer(nil)
		//fmt.Fprintf(w, "  [%s] (%v)", k, op.name)
		fmt.Fprintf(fw, "  [%s] %s(", pk, op.name)
		for j, in := range op.inputs {
			//ref := in.(Op)
			if j != 0 {
				fmt.Fprintf(fw, ", ")
			}
			ires := in.Process(params...)
			if f.err != nil {
				fmt.Fprintf(w, "Operator: %s error#%s\n", op.name, f.err.Error())
				return false
			}
			fmt.Fprintf(fw, " %s[%v](%v)", in.kind, in.id, ires)
		}
		fmt.Fprintf(fw, ") - ")
		// Create OpProcessor and execute
		//
		opfn := opFunc(f, k)
		res := opfn.Process(params...)
		fmt.Fprintf(fw, "%v\n", res)

		fmt.Fprintf(w, "%s", fw.String())
		return true
	})
}

/////////////////////////////
// Serializers inspectors
//////////////////////

func (f *Flow) String() string {
	ret := bytes.NewBuffer(nil)
	fmt.Fprintf(ret, "Flow\n")
	// consts
	fmt.Fprintf(ret, "consts:\n")
	for i, v := range f.consts {
		fmt.Fprintf(ret, "  [%s] %v\n", i, v)
	}
	fmt.Fprintf(ret, "data:\n") // Or variable
	for k, v := range f.data {
		fmt.Fprintf(ret, "  [%v] %v\n", k, v)
	}

	fmt.Fprintf(ret, "funcs:\n")
	f.operations.Range(func(pk, pv interface{}) bool {
		k, v := pk.(string), pv.(*opEntry)

		fmt.Fprintf(ret, "  [%s] %s(", k, v.name)
		for j, in := range v.inputs {
			if j != 0 {
				fmt.Fprintf(ret, ", ")
			}
			fmt.Fprintf(ret, "%s[%v]", in.kind, in.id)
		}
		fmt.Fprintf(ret, ")\n")
		return true
	})

	return ret.String()
}

// MarshalJSON implementation
func (f *Flow) MarshalJSON() ([]byte, error) {
	data := map[string]interface{}{}
	type opMarshal struct {
		Name  string
		Input []map[string]interface{}
	}
	operations := map[string]opMarshal{}
	f.operations.Range(func(pk, po interface{}) bool {
		k, o := pk.(string), po.(*opEntry)
		refs := []map[string]interface{}{}
		for _, in := range o.inputs { // Switch type?
			refs = append(refs, map[string]interface{}{
				"type": in.kind,
				"id":   in.id,
			})
		}
		operations[k] = opMarshal{o.name, refs}
		return true
	})

	data["operations"] = operations
	data["data"] = f.data
	data["consts"] = f.consts

	return json.Marshal(data)
}

/////////////////
// Async data
/////
func (f *Flow) getOp(id string) (*opEntry, bool) {

	o, ok := f.operations.Load(id)
	if !ok {
		return nil, false
	}
	return o.(*opEntry), true
}

//////////////////////////////////////////////
// Experimental event
////////////////

// Hook attach the node event hooks
func (f *Flow) Hook(hook Hook) {
	f.hooks.Attach(hook)
}
