/*
 * Copyright (C) 2018 "IoT.bzh"
 * Author Sebastien Douheret <sebastien@iot.bzh>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package agent

import (
	"encoding/json"
	"fmt"
	"net/http"

	"gerrit.automotivelinux.org/gerrit/src/xds/xds-agent/lib/xaapiv1"
	common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git/golib"
	"gerrit.automotivelinux.org/gerrit/src/xds/xds-server.git/lib/xsapiv1"
	"github.com/franciscocpg/reflectme"
	"github.com/gin-gonic/gin"
	uuid "github.com/satori/go.uuid"
)

// targetsPassthroughInit Declare passthrough routes for targets
func (s *APIService) targetsPassthroughInit(svr *XdsServer) error {
	svr.PassthroughGet("/targets")
	svr.PassthroughGet("/targets/:id")
	svr.PassthroughPost("/targets")
	svr.PassthroughDelete("/targets/:id")

	svr.PassthroughGet("/targets/:id/terminals")
	svr.PassthroughGet("/targets/:id/terminals/:tid")
	svr.PassthroughPost("/targets/:id/terminals")
	svr.PassthroughPut("/targets/:id/terminals/:tid")
	svr.PassthroughDelete("/targets/:id/terminals/:tid")

	svr.apiRouter.POST("/targets/:id/terminals/:tid/open", s.TargetTerminalOpen)

	svr.PassthroughPost("/targets/:id/terminals/:tid/close")
	svr.PassthroughPost("/targets/:id/terminals/:tid/resize")
	svr.PassthroughPost("/targets/:id/terminals/:tid/signal")
	svr.PassthroughPost("/targets/:id/terminals/:tid/signal/:sig")

	return nil
}

// targetsEventsForwardInit Register events forwarder for targets
func (s *APIService) targetsEventsForwardInit(svr *XdsServer) error {

	if !svr.Connected {
		return fmt.Errorf("Cannot register events: XDS Server %v not connected", svr.ID)
	}

	// Forward Target events from XDS-server to client
	if _, err := svr.EventOn(xsapiv1.EVTTargetAdd, xaapiv1.EVTTargetAdd, s._targetsEventCB); err != nil {
		s.Log.Errorf("XDS Server EventOn '%s' failed: %v", xsapiv1.EVTTargetAdd, err)
		return err
	}
	if _, err := svr.EventOn(xsapiv1.EVTTargetRemove, xaapiv1.EVTTargetRemove, s._targetsEventCB); err != nil {
		s.Log.Errorf("XDS Server EventOn '%s' failed: %v", xsapiv1.EVTTargetRemove, err)
		return err
	}
	if _, err := svr.EventOn(xsapiv1.EVTTargetStateChange, xaapiv1.EVTTargetStateChange, s._targetsEventCB); err != nil {
		s.Log.Errorf("XDS Server EventOn '%s' failed: %v", xsapiv1.EVTTargetStateChange, err)
		return err
	}

	return nil
}

func (s *APIService) _targetsEventCB(privD interface{}, data interface{}) error {
	evt := xsapiv1.EventMsg{}
	d, err := json.Marshal(data)
	if err != nil {
		s.Log.Errorf("Cannot marshal XDS Server Target event err=%v, data=%v", err, data)
		return err
	}
	if err = json.Unmarshal(d, &evt); err != nil {
		s.Log.Errorf("Cannot unmarshal XDS Server Target event err=%v, d=%v", err, string(d))
		return err
	}

	// assume that xsapiv1.TargetConfig == xaapiv1.TargetConfig
	target, err := evt.DecodeTargetEvent()
	if err != nil {
		s.Log.Errorf("Cannot decode XDS Server Target event: err=%v, data=%v", err, data)
		return err
	}

	evtName := privD.(string)

	if err := s.events.Emit(evtName, target, ""); err != nil {
		s.Log.Warningf("Cannot notify %s (from server): %v", evtName, err)
		return err
	}
	return nil
}

// terminalsEventsForwardInit Register events forwarder for terminals
func (s *APIService) terminalsEventsForwardInit(svr *XdsServer) error {

	if !svr.Connected {
		return fmt.Errorf("Cannot register events: XDS Server %v not connected", svr.ID)
	}

	// Forward Terminal events from XDS-server to client
	if _, err := svr.EventOn(xsapiv1.EVTTargetTerminalAdd, xaapiv1.EVTTargetTerminalAdd, s._terminalsEventCB); err != nil {
		s.Log.Errorf("XDS Server EventOn '%s' failed: %v", xsapiv1.EVTTargetTerminalAdd, err)
		return err
	}
	if _, err := svr.EventOn(xsapiv1.EVTTargetTerminalRemove, xaapiv1.EVTTargetTerminalRemove, s._terminalsEventCB); err != nil {
		s.Log.Errorf("XDS Server EventOn '%s' failed: %v", xsapiv1.EVTTargetTerminalRemove, err)
		return err
	}
	if _, err := svr.EventOn(xsapiv1.EVTTargetTerminalStateChange, xaapiv1.EVTTargetTerminalStateChange, s._terminalsEventCB); err != nil {
		s.Log.Errorf("XDS Server EventOn '%s' failed: %v", xsapiv1.EVTTargetTerminalStateChange, err)
		return err
	}

	return nil
}

func (s *APIService) _terminalsEventCB(privD interface{}, data interface{}) error {
	evt := xsapiv1.EventMsg{}
	d, err := json.Marshal(data)
	if err != nil {
		s.Log.Errorf("Cannot marshal XDS Server Target event err=%v, data=%v", err, data)
		return err
	}
	if err = json.Unmarshal(d, &evt); err != nil {
		s.Log.Errorf("Cannot unmarshal XDS Server Target event err=%v, d=%v", err, string(d))
		return err
	}

	// assume that xsapiv1.TargetConfig == xaapiv1.TargetConfig
	target, err := evt.DecodeTerminalEvent()
	if err != nil {
		s.Log.Errorf("Cannot decode XDS Server Target event: err=%v, data=%v", err, data)
		return err
	}

	evtName := privD.(string)

	if err := s.events.Emit(evtName, target, ""); err != nil {
		s.Log.Warningf("Cannot notify %s (from server): %v", evtName, err)
		return err
	}
	return nil
}

// GetServerFromTargetID Retrieve XDS Server definition from a target ID
func (s *APIService) GetServerFromTargetID(targetID, termID string) (*XdsServer, string, error) {

	// FIXME add cache (but take care to support partial term ID)
	for _, svr := range s.xdsServers {
		term := xsapiv1.TerminalConfig{}
		if err := svr.CommandTgtTerminalGet(targetID, termID, &term); err == nil {
			return svr, term.ID, nil
		}
	}
	return nil, "", fmt.Errorf("Cannot identify XDS Server")
}

// TargetTerminalOpen Open a target terminal/console
func (s *APIService) TargetTerminalOpen(c *gin.Context) {

	// First retrieve Server ID and send command to right server
	targetID := c.Param("id")
	svr, termID, err := s.GetServerFromTargetID(targetID, c.Param("tid"))
	if err != nil {
		common.APIError(c, err.Error())
		return
	}

	// Retrieve session info
	sess := s.sessions.Get(c)
	if sess == nil {
		common.APIError(c, "Unknown sessions")
		return
	}
	sock := sess.IOSocket
	if sock == nil {
		common.APIError(c, "Websocket not established")
		return
	}

	// Forward input events from client to XDSServer through WS
	err = (*sock).On(xsapiv1.TerminalInEvent, func(stdin string) {
		s.LogSillyf("TARGET TERMINAL EVENT IN (%s) <<%v>>", xsapiv1.TerminalInEvent, stdin)
		svr.EventEmit(xaapiv1.TerminalInEvent, stdin)
	})
	if err != nil {
		msgErr := "Error while registering WS for " + xsapiv1.TerminalInEvent + " event"
		s.Log.Errorf(msgErr, ", err: %v", err)
		common.APIError(c, msgErr)
		return
	}

	// Forward output events from XDSServer to client through WS
	var outFwdFuncID uuid.UUID
	outFwdFunc := func(pData interface{}, evData interface{}) error {
		sid := pData.(string)
		// IO socket can be nil when disconnected
		so := s.sessions.IOSocketGet(sid)
		if so == nil {
			s.Log.Infof("%s not emitted: WS closed (sid:%s)", xaapiv1.TerminalOutEvent, sid)
			return nil
		}

		// Add sessionID to event Data
		reflectme.SetField(evData, "sessionID", sid)

		s.LogSillyf("TARGET TERMINAL EVENT OUT (%s) <<%v>>", xaapiv1.TerminalOutEvent, evData)

		// Forward event to Client/Dashboard
		(*so).Emit(xaapiv1.TerminalOutEvent, evData)
		return nil
	}
	outFwdFuncID, err = svr.EventOn(xsapiv1.TerminalOutEvent, sess.ID, outFwdFunc)
	if err != nil {
		common.APIError(c, err.Error())
		return
	}

	// Handle Exit event separately to cleanup registered listener
	var exitFuncID uuid.UUID
	exitFunc := func(privD interface{}, evData interface{}) error {
		evN := xaapiv1.TerminalExitEvent

		pData := privD.(map[string]string)
		sid := pData["sessID"]

		// Add sessionID to event Data
		reflectme.SetField(evData, "sessionID", sid)

		// IO socket can be nil when disconnected
		so := s.sessions.IOSocketGet(sid)
		if so != nil {
			(*so).Emit(evN, evData)
		} else {
			s.Log.Infof("%s not emitted: WS closed (sid:%s)", evN, sid)
		}

		// cleanup listener
		svr.EventOff(xaapiv1.TerminalOutEvent, outFwdFuncID)
		svr.EventOff(evN, exitFuncID)

		return nil
	}

	privData := map[string]string{
		"sessID": sess.ID,
	}
	exitFuncID, err = svr.EventOn(xaapiv1.TerminalExitEvent, privData, exitFunc)
	if err != nil {
		common.APIError(c, err.Error())
		return
	}

	// Forward back command to right server
	res := xsapiv1.TerminalConfig{}
	if err := svr.CommandTgtTerminalOpen(targetID, termID, &res); err != nil {
		common.APIError(c, err.Error())
		return
	}

	c.JSON(http.StatusOK, res)
}