package flowserver

import (
	"encoding/json"
	"flow"
	"flow/registry"
	"log"
	"reflect"
)

// Node that will contain registry src
type Node struct {
	ID    string            `json:"id"`
	Src   string            `json:"src"`
	Label string            `json:"label"`
	Prop  map[string]string `json:"prop"`
}

// Link that joins two nodes
type Link struct {
	From string `json:"from"`
	To   string `json:"to"`
	In   int    `json:"in"`
}

// FlowDocument flow document
type FlowDocument struct {
	Nodes []Node `json:"nodes"`
	Links []Link `json:"links"`
}

// FlowBuild build a flowGraph
func FlowBuild(rawData []byte, r *registry.R) (*flow.Flow, error) {
	doc := FlowDocument{[]Node{}, []Link{}}
	err := json.Unmarshal(rawData, &doc)
	if err != nil {
		return nil, err
	}

	f := flow.New()
	f.SetRegistry(r)
	nodeMap := map[string]Node{}
	for _, n := range doc.Nodes {
		nodeMap[n.ID] = n
	}
	inputMap := map[string]flow.Operation{}

	ninput := 0
	for _, n := range doc.Nodes {
		if n.Src == "Input" || n.Src == "Variable" || n.Src == "Const" {
			continue
		}

		// Find link refered as To
		entry, err := r.Entry(n.Src)
		if err != nil {
			return nil, err
		}

		param := make([]flow.Data, len(entry.Inputs))
		// Find links
		for _, l := range doc.Links {
			if l.To != n.ID {
				continue
			}
			// Define operators here
			from := nodeMap[l.From]
			switch from.Src {
			case "Input":
				inOp, ok := inputMap[n.ID]
				if !ok {
					inOp = f.In(ninput)
					inputMap[n.ID] = inOp
					ninput++
				}
				param[l.In] = inOp // By id perhaps
			case "Variable":
				param[l.In] = f.Var(from.ID, from.Prop["init"])
			case "Const":
				// XXX: Automate this in a func
				newVal := reflect.New(entry.Inputs[l.In])
				raw := from.Label
				//raw := from.Prop["value"]
				if _, ok := newVal.Interface().(*string); ok {
					log.Println("Trying to unmarshal a string")
					raw = "\"" + raw + "\""
				}
				log.Println("Will unmarshal raw:", raw)
				err := json.Unmarshal([]byte(raw), newVal.Interface())
				if err != nil {
					// ignore error
					log.Println("unmarshalling Error", err)
					//param[l.In] = nil
					//continue
				}
				param[l.In] = f.Const(newVal.Elem().Interface())
			default:
				param[l.In] = f.Res(from.ID)
			}
		}
		f.DefOp(n.ID, n.Src, param...)
	}
	return f, nil
}
