/*
 * Copyright (c) 2022  The AdvantEDGE Authors
 *
 * 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.
 *
 * MEEP Demo 4 API
 * Demo 4 is an edge application that can be used with AdvantEDGE or ETSI MEC Sandbox to demonstrate MEC016 usage
 *
 * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
 *
 * API version: 0.0.1
 * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
 */
package server

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/AdvantEDGE/examples/demo4-ue/src/demo-server/backend/util"
	asc "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-app-support-client"
	dai "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-dai-client"
	loc "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-loc-serv-client"
	log "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-logger"
	sbx "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-sandbox-ctrl-client"
	smc "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-service-mgmt-client"

	"github.com/antihax/optional"
	uuid "github.com/google/uuid"
	"github.com/gorilla/mux"
)

const (
	moduleName = "demo4-ue"
	mepName    = ""
)

var mutex sync.Mutex

var done chan bool

// App-enablement client
var srvMgmtClient *smc.APIClient
var srvMgmtClientPath string
var appSupportClient *asc.APIClient
var appSupportClientPath string

// Sandbox controller client
var sandBoxClient *sbx.APIClient
var sbxCtrlUrl string = "http://meep-sandbox-ctrl"

// Location client
var locServ *loc.APIClient
var locExpiry time.Duration = 5 * time.Second
var locTicker *time.Ticker

// DAI client
var daiClient *dai.APIClient
var daiServiceName string

var svcSubscriptionSent bool
var appTerminationSent bool
var serviceRegistered bool
var terminated bool
var terminateNotification bool
var appEnabled bool

// Config fields
var mecUrl string
var localPort string
var localUrl string
var environment string
var callBackUrl string
var nodeName string
var userInfo loc.UserInfo

// App service & discovered service
var mecServicesMap = make(map[string]string)
var instanceName string
var serviceCategory string = "demo4"
var mep string
var serviceAppVersion string = "v2.2.1"
var scopeOfLocality string = defaultScopeOfLocality
var consumedLocalOnly bool = defaultConsumedLocalOnly

const defaultScopeOfLocality = "MEC_SYSTEM"
const defaultConsumedLocalOnly = true

// Demo models
var demoAppInfo ApplicationInstance
var appActivityLogs []string = []string{}
var subscriptions ApplicationInstanceSubscriptions
var appEnablementServiceId string
var amsSubscriptionId string
var terminationSubscriptionId string

// Creating context state
type AppContextState struct {
	app dai.AppContext
}

var appContexts = make(map[string]AppContextState)

// Init - Config & Client Package initialization
func Init(envPath string, envName string) (port string, err error) {

	// Retrieve environmental variable
	var config util.Config
	// trim file extension
	envName = strings.TrimSuffix(envName, ".yaml")
	log.Info("Using config values from ", envPath, "/", envName)
	config, err = util.LoadConfig(envPath, envName)
	if err != nil {
		log.Fatal("Failed to load configuration file: ", err.Error())
	}
	log.Info("Config.Mode: ", config.Mode)
	log.Info("Config.SandboxUrl: ", config.SandboxUrl)
	log.Info("Config.NodeName: ", config.NodeName)
	log.Info("Config.HttpsOnly: ", config.HttpsOnly)
	log.Info("Config.MecPlatform: ", config.MecPlatform)
	log.Info("Config.AppInstanceId: ", config.AppInstanceId)
	log.Info("Config.Localurl: ", config.Localurl)
	log.Info("Config.Port: ", config.Port)
	log.Info("Config.AppName: ", config.AppName)

	// Retrieve environment
	// Sandbox config is set by user
	// AdvantEDGE config is set by default
	if config.Mode == "sandbox" {
		environment = "sandbox"
		// mecUrl is url of the sandbox system
		mecUrl = config.SandboxUrl
		// Check mecUrl if uses https
		if config.HttpsOnly {
			if !strings.HasPrefix(mecUrl, "https://") {
				mecUrl = "https://" + mecUrl
			}
		} else if !config.HttpsOnly {
			if !strings.HasPrefix(mecUrl, "http://") {
				mecUrl = "http://" + mecUrl
			}
		} else {
			// Throw err
			log.Fatal("Missing field for https in config")
		}

		if strings.HasSuffix(mecUrl, "/") {
			mecUrl = strings.TrimSuffix(mecUrl, "/")
		}

		localPort = config.Port
		localUrl = config.Localurl
	} else if config.Mode == "advantedge" {
		environment = "advantedge"
		localPort = ":80"
		localUrl = config.Localurl
	} else {
		log.Fatal("Missing field for mode, should be set to advantedge or sandbox")
	}

	// Retrieve mec demo3 url & port
	if !strings.HasPrefix(localPort, ":") {
		localPort = ":" + localPort
	}
	if !strings.HasPrefix(localUrl, "http://") {
		localUrl = "http://" + localUrl
	}
	if strings.HasSuffix(localUrl, "/") {
		localUrl = strings.TrimSuffix(localUrl, "/")
	}

	// Retrieve UE application instance/node name
	instanceName = config.AppInstanceId
	nodeName = config.NodeName
	log.Info("Init: environment= ", environment, "-  localPort= ", localPort, "-  localUrl= ", localUrl)
	log.Info("Init: nodeName= ", nodeName, " - instanceName= ", instanceName)
	log.Info("Init: mecUrl= ", mecUrl, "-  mep= ", mep)

	// Prepend url & mapped port into callbackurl
	callBackUrl = localUrl + localPort
	log.Info("callBackUrl: ", callBackUrl)

	// If demo4-ue starts on advantedge then get resource node name from sbx controller
	if environment == "advantedge" {
		sandBoxClientCfg := sbx.NewConfiguration()
		sandBoxClientCfg.BasePath = sbxCtrlUrl + "/sandbox-ctrl/v1"
		sandBoxClient = sbx.NewAPIClient(sandBoxClientCfg)
		if sandBoxClient == nil {
			return "", errors.New("Failed to create Sandbox Controller REST API client")
		}
		appInfo, err := getApplicationInfo(instanceName)
		if err != nil {
			return "", errors.New("Failed to retrieve mec application resource")
		}
		mep = appInfo.NodeName
		log.Info("Init: mep: ", mep)
	}

	// Setup application support client & service management client
	appSupportClientCfg := asc.NewConfiguration()
	srvMgmtClientCfg := smc.NewConfiguration()
	locServerCfg := loc.NewConfiguration()
	if environment == "advantedge" {
		if config.MecPlatform != "" {
			appSupportClientCfg.BasePath = "http://" + mep + "-meep-app-enablement" + "/mec_app_support/v2"
			srvMgmtClientCfg.BasePath = "http://" + mep + "-meep-app-enablement" + "/mec_service_mgmt/v1"
			locServerCfg.BasePath = "http://" + mep + "-meep-loc-serv" + "/location/v2"
		} else {
			appSupportClientCfg.BasePath = "http://meep-app-enablement/mec_app_support/v2"
			srvMgmtClientCfg.BasePath = "http://meep-app-enablement/mec_service_mgmt/v1"
			locServerCfg.BasePath = "http://meep-loc-serv" + "/location/v2"
		}
	} else {
		appSupportClientCfg.BasePath = mecUrl + "/mec_app_support/v2"
		srvMgmtClientCfg.BasePath = mecUrl + "/mec_service_mgmt/v1"
		locServerCfg.BasePath = mecUrl + "/location/v2"
	}

	// Create app enablement client
	appSupportClient = asc.NewAPIClient(appSupportClientCfg)
	appSupportClientPath = appSupportClientCfg.BasePath
	if appSupportClient == nil {
		return "", errors.New("Failed to create App Enablement App Support REST API client")
	}
	// Create service management client
	srvMgmtClient = smc.NewAPIClient(srvMgmtClientCfg)
	srvMgmtClientPath = srvMgmtClientCfg.BasePath
	if srvMgmtClient == nil {
		return "", errors.New("Failed to create App Enablement Service Management REST API client")
	}

	// Initialize demo4 app info
	demoAppInfo.Config = envName
	demoAppInfo.Url = mecUrl
	demoAppInfo.Name = config.AppName
	demoAppInfo.Id = instanceName
	demoAppInfo.Ip = callBackUrl

	// Register to LocationServer
	locServ = loc.NewAPIClient(locServerCfg)
	if locServ == nil {
		return "", errors.New("Failed to create LocationServer REST API client")
	}

	log.Info("Starting Demo 4 instance on Port=", localPort, " using app instance id=", instanceName, " mec platform=", mep)
	return localPort, nil
}

func getApplicationInfo(appId string) (appInfo sbx.ApplicationInfo, err error) {
	appInfo, resp, err := sandBoxClient.ApplicationsApi.ApplicationsAppInstanceIdGET(context.TODO(), appId)
	if err != nil {
		log.Info("Failed to retrieve mec application resource ", err)
		return appInfo, err
	}
	log.Debug("getApplicationInfo: Receive confirmation acknowlegement ", resp.Status)
	log.Debug("getApplicationInfo: appInfo: ", appInfo)

	return appInfo, nil
}

func demo4Register() (err error) {

	mutex.Lock()
	defer mutex.Unlock()

	// Start app registeration
	if !appEnabled {

		// If app is restarted, clean app activity, discovered services
		appActivityLogs = []string{}
		//terminalDevices = make(map[string]string)
		demoAppInfo.DiscoveredServices = []ApplicationInstanceDiscoveredServices{}

		// Send confirm ready
		err = sendReadyConfirmation(instanceName)
		if err != nil {
			// Add to activity log for error indicator
			appActivityLogs = append(appActivityLogs, "=== Register Demo4 MEC Application [501]")
			log.Fatal(err.Error())
			return err
		}
		appActivityLogs = append(appActivityLogs, "=== Register Demo4 MEC Application [200]")
		demoAppInfo.MecReady = true

		// Retrieve mec services
		discoveredServices, err := getMecServices()
		if err != nil {
			log.Fatal(err.Error())
			return err
		}

		// Store discovered service name into app info model
		// Store service as a map using service name as key and url as value
		// to lookup url using service name in O(1)
		mecServicesMap = make(map[string]string)
		var tempService ApplicationInstanceDiscoveredServices
		for _, e := range discoveredServices {
			tempService.SerName = e.SerName
			tempService.SerInstanceId = e.SerInstanceId
			tempService.ConsumedLocalOnly = e.ConsumedLocalOnly
			tempService.Link = e.TransportInfo.Endpoint.Uris[0]
			tempService.Version = e.TransportInfo.Version
			demoAppInfo.DiscoveredServices = append(demoAppInfo.DiscoveredServices, tempService)
			mecServicesMap[tempService.SerName] = tempService.Link
		}

		// Subscribe to app termination
		appTerminationReference := localUrl + localPort + "/application/termination"
		appTerminationId, err := subscribeAppTermination(instanceName, appTerminationReference)
		if err == nil {
			appTerminationSent = true
		}

		// Store app termination subscription id
		var appSubscription ApplicationInstanceAppTerminationSubscription
		appSubscription.SubId = appTerminationId
		subscriptions.AppTerminationSubscription = &appSubscription

		// Subscribe to service availability
		svcCallBackReference := callBackUrl + "/services/callback/service-availability"

		svcSubscriptionId, err := subscribeAvailability(instanceName, svcCallBackReference)
		if err == nil {
			svcSubscriptionSent = true
		}

		// Store service subcription id
		var serSubscription ApplicationInstanceSerAvailabilitySubscription
		serSubscription.SubId = svcSubscriptionId
		subscriptions.SerAvailabilitySubscription = &serSubscription

		// Register demo app service
		registeredService, err := registerService(instanceName, callBackUrl)
		if err != nil {
			appActivityLogs = append(appActivityLogs, "Failed to register service")
			err := errors.New("Failed to register service")
			return err
		} else {
			serviceRegistered = true
		}

		// Store demo app service into app info model
		var serviceLocality = LocalityType(scopeOfLocality)
		var state = ServiceState("ACTIVE")
		demoAppInfo.OfferedService = &ApplicationInstanceOfferedService{
			Id:                registeredService.SerInstanceId,
			SerName:           serviceCategory,
			ScopeOfLocality:   &serviceLocality,
			State:             &state,
			ConsumedLocalOnly: true,
		}

		if environment == "advantedge" {
			daiServiceName = "meep-dai"
		} else {
			daiServiceName = "mec016-1"
		}
		var daiUrl = mecServicesMap[daiServiceName]
		daiClientcfg := dai.NewConfiguration()
		daiClientcfg.BasePath = daiUrl
		log.Debug("daiUrl: ", daiUrl)
		daiClient = dai.NewAPIClient(daiClientcfg)
		if daiClient == nil {
			appActivityLogs = append(appActivityLogs, "Failed to get daiClient instance")
			err := errors.New("Failed to get daiClient instance")
			return err
		}
		log.Debug("daiClient instance: ", daiClient)
		appActivityLogs = append(appActivityLogs, "daiClient instance created")

		demoAppInfo.Subscriptions = &subscriptions

		appEnabled = true
	}

	return nil
}

// Channel sync consume channel listen for app termination
func Run(msg chan bool) {

	err := demo4Register()
	if err != nil {
		log.Fatal("Failed to register demo4-ue: ", err.Error())
	}
	startProcessLocTicker()
	log.Info("ProcessLocTicker successfully started")

	done = msg
}

// Terminate by deleting all resources allocated on MEC platform & mec app
func Terminate() {

	// Only invoke graceful termination if not terminated (triggerd by mec platform)
	if !terminated {

		stopProcessLocTicker()

		if appEnabled {
			//intervalTicker.Stop()
			log.Info("De-register Demo4 MEC Application")
			appActivityLogs = append(appActivityLogs, "=== De-register Demo4 MEC Application [200]")
		}

		if appTerminationSent {
			//Delete app subscriptions
			if demoAppInfo.Subscriptions != nil {
				if demoAppInfo.Subscriptions.AppTerminationSubscription != nil {
					err := delAppTerminationSubscription(instanceName, demoAppInfo.Subscriptions.AppTerminationSubscription.SubId)
					if err == nil {
						log.Info("Deleted App-termination subscription")
						demoAppInfo.Subscriptions.AppTerminationSubscription.SubId = ""
						appTerminationSent = false
					}
				}

				// Delete service subscriptions
				if svcSubscriptionSent {
					err := delsubscribeAvailability(instanceName, demoAppInfo.Subscriptions.SerAvailabilitySubscription.SubId)
					if err == nil {
						log.Info("Deleted Service-avail subscription")
						svcSubscriptionSent = false
						demoAppInfo.Subscriptions.SerAvailabilitySubscription.SubId = ""
					}
				}
			}

			// Delete service
			if serviceRegistered {
				err := unregisterService(instanceName, appEnablementServiceId)
				if err == nil {

					log.Info("Deleted Demo4 service")
					serviceRegistered = false
					demoAppInfo.OfferedService = nil
				}
			}

			//Send Confirm Terminate if received notification
			if terminateNotification {
				confirmTerminate(instanceName)
				terminated = true
			}

			log.Debug("Terminate: instanceName= ", instanceName)
			_, err := sandBoxClient.ApplicationsApi.ApplicationsAppInstanceIdDELETE(context.TODO(), instanceName)
			if err != nil {
				log.Info("Failed to delete UE application resource")
			}

			// Clean app info state
			// Update app info
			demoAppInfo.MecReady = false
			demoAppInfo.MecTerminated = true
			demoAppInfo.DiscoveredServices = nil

		}

	}

}

// REST API handle service subscription callback notification
func applicationContextDeleteNotificationCallback(w http.ResponseWriter, r *http.Request) {
	log.Debug(">>> applicationContextDeleteNotificationCallback: ", r.Body)

	// Decode request body
	var notification ApplicationContextDeleteNotification
	decoder := json.NewDecoder(r.Body)
	err := decoder.Decode(&notification)
	if err != nil {
		log.Error(err)
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintf(w, "Error Decoding Notification")
	}
	log.Info("Application Context Delete notification: notification: ", notification)

	// Remove AppContext entry
	delete(appContexts, notification.ContextId)
	log.Debug("Application Context Delete notification, contextId: " + notification.ContextId + " [200]")
	appActivityLogs = append(appActivityLogs, mep+" Application Context Delete notification, contextId: "+notification.ContextId+" [200]")

	w.WriteHeader(http.StatusOK)
}

// REST API handle service subscription callback notification
func serviceAvailNotificationCallback(w http.ResponseWriter, r *http.Request) {

	// Decode request body
	var notification smc.ServiceAvailabilityNotification
	decoder := json.NewDecoder(r.Body)
	err := decoder.Decode(&notification)
	if err != nil {
		log.Error(err)
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintf(w, "Error Decoding Notification")
	}
	log.Info("Received service availability notification")

	msg := ""
	if *notification.ServiceReferences[0].ChangeType == "ADDED" {
		msg = "Available"
	} else {
		msg = "Unavailable"
	}

	if msg == "Available" {
		// Retrieve MEC service by serviceId
		serviceId := notification.ServiceReferences[0].SerInstanceId
		svcInfo, _, err := srvMgmtClient.MecServiceMgmtApi.ServicesServiceIdGET(context.TODO(), serviceId)
		if err != nil {
			log.Error(err)
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(w, "Error Retrieving MEC Services")
			appActivityLogs = append(appActivityLogs, mep+" Event: Service Availability change, "+notification.ServiceReferences[0].SerName+" service "+msg+" [500]")
		}

		// Update Discovered Service
		tempService := ApplicationInstanceDiscoveredServices{
			SerName:           svcInfo.SerName,
			SerInstanceId:     svcInfo.SerInstanceId,
			ConsumedLocalOnly: svcInfo.ConsumedLocalOnly,
			Link:              svcInfo.TransportInfo.Endpoint.Uris[0],
			Version:           svcInfo.TransportInfo.Version,
		}
		demoAppInfo.DiscoveredServices = append(demoAppInfo.DiscoveredServices, tempService)
		mecServicesMap[tempService.SerName] = tempService.Link
	} else {
		// Remove Service from Discovered Service
		for i, e := range demoAppInfo.DiscoveredServices {
			if e.SerName == notification.ServiceReferences[0].SerName && e.SerInstanceId == notification.ServiceReferences[0].SerInstanceId {

				if i < len(demoAppInfo.DiscoveredServices)-1 {
					demoAppInfo.DiscoveredServices = append(demoAppInfo.DiscoveredServices[:i], demoAppInfo.DiscoveredServices[i+1:]...)
				}
				demoAppInfo.DiscoveredServices = demoAppInfo.DiscoveredServices[:len(demoAppInfo.DiscoveredServices)-1]
			}
		}
		delete(mecServicesMap, notification.ServiceReferences[0].SerName)
	}

	state := ""
	if *notification.ServiceReferences[0].State == smc.ACTIVE_ServiceState {
		state = "ACTIVE"
	} else {
		state = "UNACTIVE"
	}
	log.Info(notification.ServiceReferences[0].SerName + " " + msg + " (" + state + ")")
	appActivityLogs = append(appActivityLogs, mep+" Event: Service Availability change, "+notification.ServiceReferences[0].SerName+" service "+msg+" [200]")
	w.WriteHeader(http.StatusOK)

}

// Client request to notify mec platform of mec app
func sendReadyConfirmation(appInstanceId string) error {

	var appReady asc.AppReadyConfirmation
	appReady.Indication = "READY"
	log.Info(appSupportClientPath)
	resp, err := appSupportClient.MecAppSupportApi.ApplicationsConfirmReadyPOST(context.TODO(), appReady, appInstanceId)
	if err != nil {
		log.Error(err.Error())
		//log.Error("Failed to receive confirmation acknowlegement ", resp.Status)
		appActivityLogs = append(appActivityLogs, "Send confirm ready ["+err.Error()+"]")
		return err
	}
	status := strconv.Itoa(resp.StatusCode)

	appActivityLogs = append(appActivityLogs, "Send confirm ready ["+status+"]")
	return nil
}

// Client request to retrieve list of mec service resources on sandbox
func getMecServices() ([]smc.ServiceInfo, error) {

	appServicesResponse, _, err := srvMgmtClient.MecServiceMgmtApi.ServicesGET(context.TODO(), nil)
	if err != nil {
		log.Error("Failed to retrieve mec services on platform ", err)
		return nil, err
	}

	return appServicesResponse, nil
}

// Client request to create a mec-service resource
func registerService(appInstanceId string, callBackUrl string) (smc.ServiceInfo, error) {
	var srvInfo smc.ServiceInfo
	srvInfo.SerName = serviceCategory
	srvInfo.Version = serviceAppVersion
	state := smc.ACTIVE_ServiceState
	srvInfo.State = &state
	serializer := smc.JSON_SerializerType
	srvInfo.Serializer = &serializer

	var transportInfo smc.TransportInfo
	transportInfo.Id = "transport"
	transportInfo.Name = "REST"
	transportType := smc.REST_HTTP_TransportType
	transportInfo.Type_ = &transportType
	transportInfo.Protocol = "HTTP"
	transportInfo.Version = "2.0"
	var endpoint smc.OneOfTransportInfoEndpoint

	endpointPath := callBackUrl + "/application/transfer"
	endpoint.Uris = append(endpoint.Uris, endpointPath)
	transportInfo.Endpoint = &endpoint
	srvInfo.TransportInfo = &transportInfo

	var category smc.CategoryRef
	category.Href = "catalogueHref"
	category.Id = "amsId"
	category.Name = "AMSI"
	category.Version = "v1"
	srvInfo.SerCategory = &category

	localityType := smc.LocalityType(scopeOfLocality)
	srvInfo.ScopeOfLocality = &localityType
	srvInfo.ConsumedLocalOnly = consumedLocalOnly
	appServicesPostResponse, _, err := srvMgmtClient.MecServiceMgmtApi.AppServicesPOST(context.TODO(), srvInfo, appInstanceId)
	if err != nil {
		log.Error("Failed to register service resource on mec app enablement registry: ", err)
		return appServicesPostResponse, err
	}
	appEnablementServiceId = appServicesPostResponse.SerInstanceId
	return appServicesPostResponse, nil
}

// Client request to sent confirm terminate
func confirmTerminate(appInstanceId string) {
	operationAction := asc.TERMINATING_OperationActionType
	var terminationBody asc.AppTerminationConfirmation
	terminationBody.OperationAction = &operationAction
	resp, err := appSupportClient.MecAppSupportApi.ApplicationsConfirmTerminationPOST(context.TODO(), terminationBody, appInstanceId)
	status := strconv.Itoa(resp.StatusCode)
	if err != nil {
		log.Error("Failed to send confirm termination ", err)
	} else {
		log.Info("Confirm Terminated")
	}
	log.Debug("confirmTerminate: ", status)
	appActivityLogs = append(appActivityLogs, "Confirm Terminated ["+status+"]")
}

// Client request to subscribe app-termination notifications
func subscribeAppTermination(appInstanceId string, callBackReference string) (string, error) {
	log.Debug("Sending request to mec platform add app terminate subscription api")
	var appTerminationBody asc.AppTerminationNotificationSubscription
	appTerminationBody.SubscriptionType = "AppTerminationNotificationSubscription"
	appTerminationBody.CallbackReference = callBackReference
	appTerminationBody.AppInstanceId = appInstanceId
	appTerminationResponse, _, err := appSupportClient.MecAppSupportApi.ApplicationsSubscriptionsPOST(context.TODO(), appTerminationBody, appInstanceId)

	if err != nil {
		log.Error("Failed to send termination subscription: ", err)
		appActivityLogs = append(appActivityLogs, "Subscribe to app-termination notification [501]")
		return "", err
	}

	hRefLink := appTerminationResponse.Links.Self.Href

	// Find subscription id from response
	idPosition := strings.LastIndex(hRefLink, "/")
	if idPosition == -1 {
		log.Error("Error parsing subscription id for subscription")
	}

	terminationSubscriptionId = hRefLink[idPosition+1:]

	log.Debug("subscribeAppTermination: Subscribe to app-termination notification [201]: ", terminationSubscriptionId)
	appActivityLogs = append(appActivityLogs, "Subscribe to app-termination notification [201]")
	return terminationSubscriptionId, nil
}

// Client request to subscribe service-availability notifications
func subscribeAvailability(appInstanceId string, callbackReference string) (string, error) {
	log.Debug("Sending request to mec platform add service-avail subscription api")
	var filter smc.SerAvailabilityNotificationSubscriptionFilteringCriteria
	filter.SerNames = nil
	filter.IsLocal = true
	subscription := smc.SerAvailabilityNotificationSubscription{"SerAvailabilityNotificationSubscription", callbackReference, nil, &filter}
	serAvailabilityNotificationSubscription, resp, err := srvMgmtClient.MecServiceMgmtApi.ApplicationsSubscriptionsPOST(context.TODO(), subscription, appInstanceId)
	status := strconv.Itoa(resp.StatusCode)
	if err != nil {
		log.Error("Failed to send service subscription: ", err)
		appActivityLogs = append(appActivityLogs, "Subscribe to service-availability notification ["+status+"]")
		return "", err
	}
	hRefLink := serAvailabilityNotificationSubscription.Links.Self.Href

	// Find subscription id from response
	idPosition := strings.LastIndex(hRefLink, "/")
	if idPosition == -1 {
		log.Error("Error parsing subscription id for subscription")
	}
	subscriptionId := hRefLink[idPosition+1:]

	log.Info("Subscribed to service availibility notification on mec platform: ", status)
	appActivityLogs = append(appActivityLogs, "Subscribe to service-availability notification ["+status+"]")

	return subscriptionId, nil
}

// Client request to delete a mec-service resource
func unregisterService(appInstanceId string, serviceId string) error {
	resp, err := srvMgmtClient.MecServiceMgmtApi.AppServicesServiceIdDELETE(context.TODO(), appInstanceId, serviceId)
	status := strconv.Itoa(resp.StatusCode)
	if err != nil {
		log.Error("Failed to send request to delete service resource on mec platform ", err)

		appActivityLogs = append(appActivityLogs, "Delete Demo4 service ["+status+"]")

		return err
	}

	appActivityLogs = append(appActivityLogs, "Delete Demo4 service ["+status+"]")
	return nil
}

// Client request to delete app-termination subscriptions
func delAppTerminationSubscription(appInstanceId string, subscriptionId string) error {

	resp, err := appSupportClient.MecAppSupportApi.ApplicationsSubscriptionDELETE(context.TODO(), appInstanceId, subscriptionId)
	status := strconv.Itoa(resp.StatusCode)
	if err != nil {
		log.Error("Failed to clear app termination subscription ", resp.Status)
		appActivityLogs = append(appActivityLogs, "Delete App-termination subscription ["+status+"]")
		return err
	}

	appActivityLogs = append(appActivityLogs, "Delete App-termination subscription ["+status+"]")
	return nil
}

// Client request to delete subscription of service-availability notifications
func delsubscribeAvailability(appInstanceId string, subscriptionId string) error {

	resp, err := srvMgmtClient.MecServiceMgmtApi.ApplicationsSubscriptionDELETE(context.TODO(), appInstanceId, subscriptionId)
	status := strconv.Itoa(resp.StatusCode)
	if err != nil {
		log.Error("Failed to clear service availability subscriptions: ", resp.Status)
		appActivityLogs = append(appActivityLogs, "Delete Service-avail subscription ["+status+"]")
		return err
	}
	appActivityLogs = append(appActivityLogs, "Delete Service-avail subscription ["+status+"]")
	return nil
}

// REST API retrieve app instance info
func demo4GetPlatformInfo(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	// Send resp
	jsonResponse, err := json.Marshal(demoAppInfo)
	if err != nil {
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, string(jsonResponse))
}

// REST API retrieve activity logs
func demo4GetActivityLogs(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	var resp []string
	for i := len(appActivityLogs) - 1; i >= 0; i-- {
		lineNumber := strconv.Itoa(i)
		resp = append(resp, lineNumber+". "+appActivityLogs[i])
	}

	jsonResponse, err := json.Marshal(resp)
	if err != nil {
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, string(jsonResponse))
}

// REST API retrieve activity logs
func demo4DaiAppListGET(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	applicationList, response, err := daiClient.DevAppApi.MeAppListGET(context.TODO(), nil)
	if err != nil {
		log.Error("Failed to get ApplicationList: ", err)
		appActivityLogs = append(appActivityLogs, "Failed to get ApplicationList ["+err.Error()+"]")
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	appActivityLogs = append(appActivityLogs, "demo4DaiAppListGET: applicationList succeed, len= "+strconv.Itoa(len(applicationList.AppList)))
	log.Debug("demo4DaiAppListGET: len(appInfoList.AppList): ", len(applicationList.AppList))

	// TODO Add Logic
	//var applicationList ApplicationList

	// Send resp
	log.Debug("demo4DaiAppListGET: response.Body: ", response.Body)
	jsonResponse, err := json.Marshal(applicationList)
	if err != nil {
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, string(jsonResponse))
}

// REST API retrieve activity logs
const (
	simu_appName        = "onboarded-demo4"
	simu_appDId         = "onboarded-demo4"
	simu_appProvider    = "ETSI"
	simu_appDVersion    = "v0.1.0"
	simu_appSoftVersion = "v0.1.0"
	simu_appDescription = "Basic HTTP Ping Pong"
	//simu_area             = Coordinates{[][][]float32{{{7.43166, 43.736156}, {7.431723, 43.736115}, {7.431162, 43.735607}, {7.430685, 43.73518}}}}
	simu_countryCode      = "33"
	simu_appPackageSource = "appPackageSource1"
)

// REST API sends ping request and get response
func demo4DaiDoPingGET(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	// Check if a MEC application context is existing
	log.Debug("demo4DaiDoPingGET: appContexts= ", appContexts)
	if len(appContexts) == 0 {
		err := errors.New("No context created, please use POST /dai/instantiate")
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	vars := mux.Vars(r)
	contextId := vars["appcontextid"]
	log.Info("demo4DaiDoPingGET: contextId: ", contextId)

	// Retrieve the appContextId
	var appContextId string
	for k := range appContexts {
		if k == contextId {
			log.Debug("demo4DaiDoPingGET: Set appContextId to ", k)
			appContextId = k
			break
		}
	} // End of 'for' statement
	log.Debug("demo4DaiDoPingGET: appContextId: ", appContextId)
	if appContextId == "" { // Not found
		err := errors.New("demo4DaiDoPingGET: Invalid AppContext identifier")
		appActivityLogs = append(appActivityLogs, "demo4DaiDoPingGET: Invalid AppContext identifier")
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusNotFound)
		return
	}

	// Send the ping request
	appActivityLogs = append(appActivityLogs, "demo4DaiDoPingGET: Send ping request: ", strings.TrimSuffix(appContexts[appContextId].app.AppInfo.UserAppInstanceInfo[0].ReferenceURI, "/")+"/ping")
	resp, err := http.Get(strings.TrimSuffix(appContexts[appContextId].app.AppInfo.UserAppInstanceInfo[0].ReferenceURI, "/") + "/ping")
	if err != nil {
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	log.Debug("demo4DaiDoPingGET: Sent: ", resp.Body)
	bodyBytes, _ := ioutil.ReadAll(resp.Body)
	log.Debug("demo4DaiDoPingGET: resp: ", string(bodyBytes))

	// Send resp
	statusCode, err := strconv.Atoi(strings.Split(resp.Status, " ")[0])
	if err != nil {
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(statusCode)
	fmt.Fprintf(w, string(bodyBytes))
}

// REST API creates a new instance of an onboarded MEC application
func demo4DaiDoPingPOST(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	// Check if a MEC application context is existing
	var appContextId string
	log.Debug("demo4DaiDoPingPOST: appContexts= ", appContexts)
	if len(appContexts) == 0 {
		// Create a new context
		userAppInstanceInfo := make([]dai.AppContextAppInfoUserAppInstanceInfo, 1)
		userAppInstanceInfo[0].AppLocation = new(dai.LocationConstraints)
		userAppInstanceInfo[0].AppLocation.CountryCode = simu_countryCode
		appInfo := dai.AppContextAppInfo{simu_appDId, simu_appDVersion, simu_appDescription, simu_appName, simu_appProvider, simu_appSoftVersion, simu_appPackageSource, userAppInstanceInfo}
		appContext := dai.AppContext{true, &appInfo, true, uuid.New().String()[0:32], callBackUrl, ""}
		log.Debug("demo4DaiDoPingPOST: appContext: ", appContext)
		appContext, response, err := daiClient.DevAppApi.DevAppContextsPOST(context.TODO(), appContext)
		if err != nil {
			log.Error("Failed to create new AppContext: ", err)
			appActivityLogs = append(appActivityLogs, "Failed to create new AppContext ["+err.Error()+"]")
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		log.Debug("demo4DaiDoPingPOST: appContext.ContextId: ", appContext.ContextId)
		log.Debug("demo4DaiDoPingPOST: aappContextpp.AppInfo: ", appContext.AppInfo)
		log.Debug("demo4DaiDoPingPOST: appContext.AppInfo.UserAppInstanceInfo: ", appContext.AppInfo.UserAppInstanceInfo)
		appActivityLogs = append(appActivityLogs, "demo4DaiDoPingPOST: AppContext creation succeed, "+string(response.StatusCode))
		log.Debug("demo4DaiDoPingPOST: AppContext creation succeed, response= ", response.StatusCode)
		// Update appContext
		appContexts[appContext.ContextId] = AppContextState{appContext}
		appContextId = appContext.ContextId
	} else {
		for k := range appContexts {
			log.Debug("demo4DaiDoPingPOST: Set appContextId to ", k)
			appContextId = k
		} // End of 'for' statement
		err := errors.New("MEC Application already instanciated: " + appContextId)
		appActivityLogs = append(appActivityLogs, "demo4DaiDoPingPOST: "+err.Error())
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	appActivityLogs = append(appActivityLogs, "demo4DaiDoPingPOST: appContextId: "+appContextId)
	log.Debug("demo4DaiDoPingPOST: appContextId: ", appContextId)

	// Send resp
	jsonResponse, err := json.Marshal(appContexts[appContextId].app)
	if err != nil {
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusCreated)
	fmt.Fprintf(w, string(jsonResponse))
}

// REST API creates an existing instance of an onboarded MEC application
func demo4DaiDoPingDELETE(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	log.Debug("demo4DaiDoPingDELETE: appContexts= ", appContexts)
	if len(appContexts) == 0 {
		err := errors.New("No context created, please use POST /dai/instantiate")
		appActivityLogs = append(appActivityLogs, "demo4DaiDoPingDELETE: "+err.Error())
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	vars := mux.Vars(r)
	contextId := vars["appcontextid"]
	log.Info("demo4DaiDoPingDELETE: contextId: ", contextId)

	// Retrieve the appContextId
	var appContextId string
	for k := range appContexts {
		if k == contextId {
			log.Debug("demo4DaiDoPingDELETE: Set appContextId to ", k)
			appContextId = k
			break
		}
	} // End of 'for' statement
	log.Debug("demo4DaiDoPingDELETE: appContextId: ", appContextId)
	if appContextId == "" { // Not found
		err := errors.New("demo4DaiDoPingDELETE: Invalid AppContext identifier")
		appActivityLogs = append(appActivityLogs, "demo4DaiDoPingDELETE: Invalid AppContext identifier")
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusNotFound)
		return
	}

	_ /*resp*/, err := daiClient.DevAppApi.DevAppContextDELETE(context.TODO(), appContextId)
	if err != nil {
		err := errors.New("Failed to delete appContextId: " + appContextId)
		appActivityLogs = append(appActivityLogs, "demo4DaiDoPingDELETE: Failed to delete appContextId ["+err.Error()+"]")
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// Remove it from appContexts
	delete(appContexts, appContextId)
	log.Debug("demo4DaiDoPingDELETE: Updated appContexts= ", appContexts)

	// Send resp
	w.WriteHeader(http.StatusNoContent)
}

// REST API retrieve location constraints of an existing instance of an onboarded MEC application
func demo4DaiAppLocationAvailabilityPOST(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	// Check if a MEC application context is existing
	log.Debug("demo4DaiAppLocationAvailabilityPOST: appContexts= ", appContexts)
	if len(appContexts) == 0 {
		err := errors.New("No context created, please use POST /dai/instantiate")
		appActivityLogs = append(appActivityLogs, "demo4DaiDoPingDELETE: "+err.Error())
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	vars := mux.Vars(r)
	contextId := vars["appcontextid"]
	log.Info("demo4DaiAppLocationAvailabilityPOST: contextId: ", contextId)

	// Retrieve the appContextId
	var appContextId string
	for k := range appContexts {
		if k == contextId {
			log.Debug("demo4DaiAppLocationAvailabilityPOST: Set appContextId to ", k)
			appContextId = k
			break
		}
	} // End of 'for' statement
	log.Debug("demo4DaiAppLocationAvailabilityPOST: appContextId: ", appContextId)
	if appContextId == "" { // Not found
		err := errors.New("demo4DaiAppLocationAvailabilityPOST: Invalid AppContext identifier")
		appActivityLogs = append(appActivityLogs, "demo4DaiAppLocationAvailabilityPOST: Invalid AppContext identifier")
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusNotFound)
		return
	}

	// Create a ApplicationLocationAvailability
	var appInfo dai.ApplicationLocationAvailabilityAppInfo
	appInfo.AppDVersion = appContexts[appContextId].app.AppInfo.AppDVersion
	appInfo.AppDescription = appContexts[appContextId].app.AppInfo.AppDescription
	appInfo.AppName = appContexts[appContextId].app.AppInfo.AppName
	appInfo.AppPackageSource = appContexts[appContextId].app.AppInfo.AppPackageSource
	appInfo.AppProvider = appContexts[appContextId].app.AppInfo.AppProvider
	appInfo.AppSoftVersion = appContexts[appContextId].app.AppInfo.AppSoftVersion
	var applicationLocationAvailability dai.ApplicationLocationAvailability
	applicationLocationAvailability.AppInfo = &appInfo
	applicationLocationAvailability.AssociateDevAppId = appContexts[appContextId].app.AssociateDevAppId

	appLocAv, response, err := daiClient.DevAppApi.AppLocationAvailabilityPOST(context.TODO(), applicationLocationAvailability)
	if err != nil {
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	log.Debug("demo4DaiAppLocationAvailabilityPOST: Status: ", response.Status)

	// Send resp
	jsonResponse, err := json.Marshal(appLocAv)
	if err != nil {
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, string(jsonResponse))
}

// Rest API handle user-app termination call-back notification
func appTerminationNotificationCallback(w http.ResponseWriter, r *http.Request) {
	var notification asc.AppTerminationNotification
	decoder := json.NewDecoder(r.Body)
	err := decoder.Decode(&notification)
	if err != nil {
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	log.Info("Received user-app termination notification")
	appActivityLogs = append(appActivityLogs, "Received user-app termination notification")
	w.WriteHeader(http.StatusOK)
	terminateNotification = true
	demoAppInfo.MecTerminated = true
	Terminate()
}

func startProcessLocTicker() {
	// Make sure ticker is not running
	if locTicker != nil {
		log.Warn("Registration ticker already running")
		return
	}

	// Start registration ticker
	locTicker = time.NewTicker(locExpiry)
	go func() {

		for range locTicker.C {

			// Get UI position
			s := make([]string, 1)
			s[0] = nodeName
			var localVarOptionals loc.LocationApiUsersGETOpts
			localVarOptionals.Address = optional.NewInterface(s)
			log.Debug("startProcessLocTicker: Call UsersGet with: ", localVarOptionals)
			usersList, _, err := locServ.LocationApi.UsersGET(context.TODO(), &localVarOptionals)
			if err != nil {
				log.Error("UsersGet: Failed: ", err)
				continue // Infinite loop till stopProcessCheckTicker is call
			}
			if usersList.UserList != nil {
				log.Debug("startProcessLocTicker: usersList: ", usersList.UserList)
				log.Debug("startProcessLocTicker: usersList: ", len(usersList.UserList.User))
				if len(usersList.UserList.User) != 0 {
					for _, u := range usersList.UserList.User {
						if u.Address == nodeName && u.LocationInfo != nil {
							userInfo = u
							log.Debug("startProcessLocTicker: New position: ", userInfo.LocationInfo)
							break
						}
					} // End of 'for' statement
				}
			}

			continue // Infinite loop till stopProcessCheckTicker is call
		} // End of 'for' statement
	}()
}

func stopProcessLocTicker() {
	if locTicker != nil {
		log.Info("Stopping App Enablement registration ticker")
		locTicker.Stop()
		locTicker = nil
	}
}
