package main

import (
	"context"
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"

	"code.fbi.h-da.de/danet/gosdn/api/go/gosdn/networkelement"
	"code.fbi.h-da.de/danet/gosdn/api/go/gosdn/topology"

	"code.fbi.h-da.de/danet/gosdn/application-framework/event"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

// Application is an example for a sdn application.
type Application struct {
	eventServiceLinks  event.ServiceInterface
	eventServiceRoutes event.ServiceInterface
	stopChannel        chan os.Signal
	grpcClientConn     *grpc.ClientConn
}

// Run runs the application.
func (a *Application) Run(controllerAddress string) {
	signal.Notify(a.stopChannel, os.Interrupt, syscall.SIGTERM)

	a.eventServiceLinks.SubscribeToEventType([]event.TypeToCallbackTuple{
		{Type: event.Add, Callback: a.LinksCallback},
		{Type: event.Update, Callback: a.LinksCallback},
	})
	a.eventServiceLinks.SetupEventReciever(a.stopChannel)

	a.eventServiceRoutes.SubscribeToEventType([]event.TypeToCallbackTuple{
		{Type: event.Add, Callback: a.RoutesCallback},
		{Type: event.Update, Callback: a.RoutesCallback},
	})
	a.eventServiceRoutes.SetupEventReciever(a.stopChannel)

	conn, err := grpc.Dial(controllerAddress, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		panic(err)
	}

	a.grpcClientConn = conn

	var forever chan struct{}

	go func() {
		for {
			select {
			case <-a.stopChannel:
				close(forever)
				_ = a.grpcClientConn.Close()

				return
			}
		}
	}()

	<-forever
}

// LinksCallback is the callback function for link changes.
func (a *Application) LinksCallback(event *event.Event) {
	fmt.Printf("Links Event Callback: %+v \n", event)

	ctx := context.Background()
	networkElementServer := networkelement.NewNetworkElementServiceClient(a.grpcClientConn)
	topologyServer := topology.NewTopologyServiceClient(a.grpcClientConn)

	getTopologyRequest := &topology.GetTopologyRequest{
		Timestamp: time.Now().UnixNano(),
	}

	getTopologyResponse, err := topologyServer.GetTopology(ctx, getTopologyRequest)
	if err != nil {
		fmt.Printf("Error %+v\n ", err)
		return
	}

	links := getTopologyResponse.Toplogy.Links

	for _, link := range links {
		fmt.Printf("[APP] Link: %+v", link)
		adjustNodePortsToMatchConfiguration(networkElementServer, link.SourceNode, link.SourcePort)
		adjustNodePortsToMatchConfiguration(networkElementServer, link.TargetNode, link.TargetPort)
	}
}

// RoutesCallback is the callback function for route changes.
func (a *Application) RoutesCallback(event *event.Event) {
	fmt.Printf("Routes Event Callback: %+v \n", event)

	ctx := context.Background()
	networkElementServer := networkelement.NewNetworkElementServiceClient(a.grpcClientConn)
	routesServer := topology.NewRoutingTableServiceClient(a.grpcClientConn)

	getRoutingTablesRequest := &topology.GetRoutesRequest{
		Timestamp: time.Now().UnixNano(),
	}

	response, err := routesServer.GetRoutes(ctx, getRoutingTablesRequest)
	if err != nil {
		fmt.Printf("Error %+v\n ", err)
		return
	}

	for _, node := range response.RoutingTables {
		nodeID := node.NodeID

		for _, nodeRoute := range node.Routes {
			adjustNodeRoutesToMatchConfiguration(networkElementServer, nodeID, nodeRoute)
		}
	}
}
