package flowserver

import (
	"encoding/json"
	"errors"
	"flow"
	"flowserver/flowmsg"
	"log"
	"sync"

	"github.com/gorilla/websocket"
)

// FlowSession Create a session and link clients
type FlowSession struct {
	sync.Mutex
	ID string // Random handle for sessionID
	// List of clients on this session
	clients []*websocket.Conn
	Chat    ChatRoom

	RawDoc []byte // Just share data

	flow *flow.Flow
}

//NewSession creates and initializes a NewSession
func NewSession(ID string) *FlowSession {
	s := &FlowSession{sync.Mutex{}, ID, []*websocket.Conn{}, ChatRoom{}, []byte{}, nil}
	return s
}

// ClientAdd add a client to session
func (s *FlowSession) ClientAdd(c *websocket.Conn) error {
	s.Lock()
	defer s.Unlock()
	err := c.WriteJSON(flowmsg.SendMessage{OP: "sessionJoin", ID: s.ID})
	if err != nil {
		return err
	}
	desc, err := flow.Descriptions()
	if err != nil {
		return err
	}
	err = c.WriteJSON(flowmsg.SendMessage{OP: "registry", Data: desc})
	if err != nil {
		return err
	}
	s.clients = append(s.clients, c)

	if len(s.RawDoc) > 0 {
		return c.WriteJSON(flowmsg.SendMessage{OP: "document", Data: json.RawMessage(s.RawDoc)})
	}
	return nil

	// Send registry
}

// ClientRemove remove client from Session
func (s *FlowSession) ClientRemove(c *websocket.Conn) {
	s.Lock()
	defer s.Unlock()
	for i, cl := range s.clients {
		if cl == c {
			s.clients = append(s.clients[:i], s.clients[i+1:]...)
			break
		}
	}
	s.Chat.ClientRemove(c)
}

// ChatJoin the chat room on this session
func (s *FlowSession) ChatJoin(c *websocket.Conn, handle string) {
	s.Chat.ClientAdd(c, handle)
}

// DocumentUpdate client c Updates the session document
func (s *FlowSession) DocumentUpdate(c *websocket.Conn, data []byte) error {
	s.Lock()
	defer s.Unlock()

	s.RawDoc = data

	return s.broadcast(c, flowmsg.SendMessage{OP: "document", Data: json.RawMessage(s.RawDoc)})

}

// Document send document to client c
func (s *FlowSession) Document(c *websocket.Conn) error {
	s.Lock()
	defer s.Unlock()

	return c.WriteJSON(flowmsg.SendMessage{OP: "document", Data: json.RawMessage(s.RawDoc)})
}

// NodeRun a node triggering results
// Build a flow and run
func (s *FlowSession) NodeRun(c *websocket.Conn, data []byte) error {
	var err error
	ID := string(data[1 : len(data)-1]) // remove " instead of unmarshalling json
	log.Println("Node will run", ID)
	if s.flow != nil {
		return errors.New("node already running")
	}

	log.Printf("Building flow from '%s'\n", string(s.RawDoc))
	s.flow, err = FlowBuild(s.RawDoc)
	if err != nil {
		return err
	}

	log.Println("Flow--\n", s.flow)
	defer func() {
		s.flow = nil
	}()

	log.Println("Attaching hooks")
	s.flow.Handle(func(name string, payload map[string]flow.Data) {
		// Add to executing nodes
		log.Println("Exec doc", name)
		s.Broadcast(nil, flowmsg.SendMessage{OP: name, ID: payload["id"].(string), Data: payload})
	})

	log.Println("Fetch result")
	op := s.flow.Res(ID)
	if s.flow.Err() != nil {
		return err
	}

	res := op.Process()
	if s.flow.Err() != nil {
		return err
	}

	log.Println("Node Result:", res)

	return nil
}

// Broadcast broadcast a message in session besides C
func (s *FlowSession) Broadcast(c *websocket.Conn, v interface{}) error {
	s.Lock()
	defer s.Unlock()
	return s.broadcast(c, v)
}

//
func (s *FlowSession) broadcast(c *websocket.Conn, v interface{}) error {
	for _, sc := range s.clients {
		if sc == c { // ours
			continue
		}
		err := sc.WriteJSON(v)
		if err != nil {
			return err
		}
	}
	return nil

}
