/*
 * Copyright (c) 2024  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.
 *
 * MEC Demo6 API
 *
 * Demo6 is a MEC application to illustrate the usage of the MEC Sandbox command line APIs.
 *
 * API version: 0.0.1
 * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
 */

package main

import (
	"bufio"
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"syscall"
	"time"

	client "github.com/InterDigitalInc/AdvantEDGE/example/demo6/client"

	"github.com/google/uuid"
	"github.com/gorilla/handlers"
	"github.com/roymx/viper"
)

type Config struct {
	Mode          string `mapstructure:"mode"`
	SandboxUrl    string `mapstructure:"sandbox"`
	HttpsOnly     bool   `mapstructure:"https"`
	MecPlatform   string `mapstructure:"mecplatform"`
	SandboxName   string `mapstructure:"sandboxname"`
	AppInstanceId string `mapstructure:"appid"`
	Localurl      string `mapstructure:"localurl"`
	Port          string `mapstructure:"port"`
}

// MEC 011 registration
// ETSI GS MEC 011 V3.2.1 (2024-04) Clause 7.1.2.6 Type: AppInfo
type AppInfo struct {
	AppName       string `json:"appName"`                 // Name of the application. It shall be consistent with the appName in the AppD, if an AppD is available.
	AppProvider   string `json:"appProvider,omitempty"`   // Provider of the application. It shall be consistent with the appProvider in the AppD, if an AppD is available
	AppDId        string `json:"appDId,omitempty"`        // The application descriptor identifier. It is managed by the application provider to identify the application descriptor in a globally unique way
	AppInstanceId string `json:"appInstanceId,omitempty"` // Identifier of the application instance. Shall be present if the application instance is instantiated by the MEC Management
	IsInsByMec    bool   `json:"isInsByMec,omitempty"`    // Indicate whether the application instance is instantiated by the MEC Management. Default to FALSE if absent.
}

// MEC 011 ServiceInfo
// ETSI GS MEC 011 V3.2.1 (2024-04) Clause 8.1.2.2 Type: ServiceInfo
type ServiceInfo struct {
	SerInstanceId string        `json:"serInstanceId,omitempty"`
	SerName       string        `json:"serName"`
	SerCategory   *CategoryRef  `json:"serCategory,omitempty"`
	Version       string        `json:"version"` // Service version
	State         string        `json:"state"`
	TransportInfo TransportInfo `json:"transportInfo"`
	Serializer    string        `json:"serializer"`
	Links         *Links        `json:"_links,omitempty"`
}
type CategoryRef struct {
	Href    string `json:"href"`
	Id      string `json:"id"`
	Name    string `json:"name"`
	Version string `json:"version"`
}
type TransportInfo struct {
	Id       string                      `json:"id"`
	Name     string                      `json:"name"`
	Type_    *string                     `json:"type"`
	Protocol string                      `json:"protocol"`
	Version  string                      `json:"version"`
	Endpoint *OneOfTransportInfoEndpoint `json:"endpoint"`
}
type OneOfTransportInfoEndpoint struct {
	EndPointInfoUris
	EndPointInfoFqdn
	EndPointInfoAddresses
	EndPointInfoAlternative
}
type EndPointInfoUris struct {
	Uris []string `json:"uris"`
}
type EndPointInfoFqdn struct {
	Fqdn []string `json:"fqdn"`
}
type EndPointInfoAddress struct {
	Host string `json:"host"`
	Port int32  `json:"port"`
}
type EndPointInfoAddresses struct {
	Addresses []EndPointInfoAddress `json:"addresses"`
}
type EndPointInfoAlternative struct {
	Alternative *interface{} `json:"alternative"`
}

// MEC 011 Termination subscription
// ETSI GS MEC 011 V3.2.1 (2024-04) Clause 7.1.3.2 Type: AppTerminationNotificationSubscription
type AppTerminationNotificationSubscription struct {
	SubscriptionType  string `json:"subscriptionType"`
	CallbackReference string `json:"callbackReference"`
	Links             *Self  `json:"_links"`
	AppInstanceId     string `json:"appInstanceId"`
}

// MEC 030 V2X Subscription
// ETSI GS MEC 030 V3.2.1 Clause 6.3.5 Type: V2xMsgSubscription
type V2xMsgSubscription struct {
	Links                   *Links                            `json:"_links,omitempty"`
	CallbackReference       string                            `json:"callbackReference,omitempty"`
	FilterCriteria          *V2xMsgSubscriptionFilterCriteria `json:"filterCriteria"`
	RequestTestNotification bool                              `json:"requestTestNotification,omitempty"`
	SubscriptionType        string                            `json:"subscriptionType"`
}

// MEC 030 V2X Subscription
// ETSI GS MEC 030 V3.2.1 Clause 6.5.15 Type: V2xMsgFilterCriteria
type V2xMsgSubscriptionFilterCriteria struct {
	MsgType         []int32 `json:"msgType,omitempty"`
	StdOrganization string  `json:"stdOrganization"`
}

// MEC Common types
type LinkType struct {
	// URI referring to a resource
	Href string `json:"href,omitempty"`
}
type Links struct {
	Self *LinkType `json:"self"`
}
type Self struct {
	Self *LinkType `json:"self"`
}

type UeContext struct {
	id string
	v  int32
}

var (
	dir                       string
	fileName                  string
	provider                  string = "github" //"Jupyter2024"
	run                       bool   = true
	done                      chan bool
	cfg                       *client.Configuration = nil
	cl                        *client.APIClient     = nil
	reader                    *bufio.Reader         = nil
	sandboxName               string                = ""
	verificationUri           string                = ""
	userCode                  string                = ""
	mecUrl                    string                = ""
	mecPlateform              string                = ""
	callbackUrl               string                = "http://mec-platform.etsi.org/callbacks"
	terminationSubscriptionID string                = ""
	scenarios                 []client.SandboxNetworkScenario
	scenario                  []client.Scenario
	scenarioId                int
	services                  []client.SandboxMecServices
	appsInfo                  client.ApplicationInfo
	isRegistered              bool = false
	appServiceInfo            ServiceInfo
	ues                       []UeContext
)

// Display menu and read selection
const (
	LOGIN                 = "l"
	NAMESPACE             = "n"
	LOGOUT                = "L"
	LIST_SC               = "s"
	SC                    = "S"
	ACTIVATE              = "a"
	DEACTIVATE            = "A"
	LIST_SERVICES         = "m"
	LIST_APP              = "i"
	CREATE_APP            = "c"
	DELETE_APP            = "C"
	MEC011_CONFIRM_READY  = "y"
	MEC011_REGISTRATION   = "r"
	MEC011_DEREGISTRATION = "R"
	MEC011_CREATE_SVC     = "v"
	MEC011_DELETE_SVC     = "V"
	MEC011_GET_SVC        = "g"
	LIST_UES              = "u"
	INC_UE                = "x"
	DEC_UE                = "X"
	MEC030_UU_SETTINGS    = "Y"
	MEC030_V2X_SUB        = "z"
	MEC030_V2X_DEL_SUB    = "Z"
	STATUS                = "Q"
	QUIT                  = "q"
)

func clearScreen() {
	fmt.Println("\033[2J")
}

func menu(message string) []string {
	clearScreen()
	fmt.Printf(
		"Mandatory commands:\n"+
			"\t%s: Login, %s: Get Namespace, %s: Logout, %s: Get scenarios list\n"+
			"\t%s <index>: Activate a scenario, %s: Terminate a scenario\n"+
			"Optional commands:\n"+
			"\t%s <index>: Get scenario description\n"+
			"\t%s: Get MEC application services list\n"+
			"\t%s: Get application instances list, %s: Create a new application instance, %s: Delete a new application instance\n"+
			"\t%s: Get UEs, %s <index>: Increase UE, %s <index>: Decrease UE\n"+
			"MEC 011 App Support:\n"+
			"\t%s: Send ConfirmReady, %s: Send Registration, %s: Send Deregistration\n"+
			"MEC 011 Service Management:\n"+
			"\t%s: Create new service, %s: Delete service, %s: Get list of MEC services\n"+
			"MEC 030:\n"+
			"\t%s: Get V2X UU unicast setting', %s: V2X Msg subscription, %s <subID>: Delete V2X subscription\n"+
			"\t%s: Current status:\n"+
			"%s: Quit\n",
		LOGIN, NAMESPACE, LOGOUT, LIST_SC, ACTIVATE, DEACTIVATE, SC, LIST_SERVICES, LIST_APP, CREATE_APP, DELETE_APP, LIST_UES, INC_UE, DEC_UE, MEC011_CONFIRM_READY, MEC011_REGISTRATION, MEC011_DEREGISTRATION, MEC011_CREATE_SVC, MEC011_DELETE_SVC, MEC011_GET_SVC, MEC030_UU_SETTINGS, MEC030_V2X_SUB, MEC030_V2X_DEL_SUB, STATUS, QUIT)
	if message != "" {
		fmt.Println("Last message: ", message)
	}
	fmt.Print("Enter your choice: ")

	// Read selection
	choice, _ := reader.ReadString('\n')
	choice = strings.Trim(strings.Trim(choice, "\n"), " ")
	return strings.Split(choice, " ")
}

func login() (string, string, error) {
	fmt.Println(">>> login")

	// Sanity checks
	if sandboxName != "" {
		return "", "", errors.New("Please, logout first")
	}

	// Initialize g;lobal variables
	scenarioId = -1
	appsInfo.Id = ""
	terminationSubscriptionID = ""
	appServiceInfo.SerInstanceId = ""

	sandbox, _, err := cl.AuthorizationApi.Login(context.TODO(), provider)
	if err != nil {
		return "", "", err
	}
	fmt.Println("login: sandbox: ", sandbox)

	return sandbox.User_code, sandbox.Verification_uri, nil
}

func getNamespace() (string, error) {
	fmt.Println(">>> Get Namespace")

	response, _, err := cl.AuthorizationApi.GetNamespace(context.TODO(), userCode)
	if err != nil {
		return "", err
	}
	fmt.Println("login: Namespace is: ", response)
	sandboxName = response.Sandbox_name

	return sandboxName, nil
}

func logout() error {
	fmt.Println(">>> logout: ", sandboxName)

	// Sanity check
	if sandboxName == "" {
		err := errors.New("logout: Wrong parameters")
		return err
	}

	// Delete subscription if any
	if terminationSubscriptionID != "" {
		delete_termination_subscription()
	}

	// Delete MEC service if any
	if appServiceInfo.SerInstanceId != "" {
		mec011_delete_service()
	}

	// Delete registration if any
	if isRegistered {
		mec011_send_deregistration()
	}

	// Delete created AppInstId if any
	if appsInfo.Id != "" {
		deleteMECAppInstId()
		appsInfo.Id = ""
		time.Sleep(2 * time.Second)
	}

	if appServiceInfo.SerInstanceId != "" {
		mec011_delete_service()
		appServiceInfo.SerInstanceId = ""
		time.Sleep(2 * time.Second)
	}

	// Terminate scenario if any
	if scenarioId != -1 {
		terminateScenario(scenarios[scenarioId].Id)
		scenarioId = -1
		time.Sleep(2 * time.Second)
	}

	_, err := cl.AuthorizationApi.Logout(context.TODO(), sandboxName)
	if err != nil {
		return err
	}

	sandboxName = ""

	return nil
}

func getListOfScenarios() ([]client.SandboxNetworkScenario, error) {
	fmt.Println(">>> getListOfScenarios")

	scenarios, _, err := cl.SandboxNetworkScenariosApi.SandboxNetworkScenariosGET(context.TODO(), sandboxName)
	if err != nil {
		fmt.Println("getListOfScenarios: ", err.Error())
		return nil, err
	}
	fmt.Println("getListOfScenarios: ", scenarios)

	return scenarios, nil
}

func getScenario(scenarioId string) ([]client.Scenario, error) {
	fmt.Println(">>> getScenario: ", scenarioId)

	scenario, _, err := cl.SandboxNetworkScenariosApi.SandboxIndividualNetworkScenariosGET(context.TODO(), sandboxName, scenarioId)
	if err != nil {
		fmt.Println("getScenario: ", err.Error())
		return nil, err

	}
	fmt.Println("scenario: ", scenario)

	return scenario, nil
}

func activateScenario(scenarioId string) error {
	fmt.Println(">>> activateScenario: ", scenarioId)

	// Sanity checks
	if sandboxName == "" {
		return errors.New("No sandbox available")
	}

	_, err := cl.SandboxNetworkScenariosApi.SandboxNetworkScenarioPOST(context.TODO(), sandboxName, scenarioId)
	if err != nil {
		return err
	}

	return nil
}

func terminateScenario(scenarioId string) error {
	fmt.Println(">>> terminateScenario: ", scenarioId)

	// Sanity checks
	if sandboxName == "" {
		return errors.New("No sandbox available")
	}

	_, err := cl.SandboxNetworkScenariosApi.SandboxNetworkScenarioDELETE(context.TODO(), sandboxName, scenarioId)
	if err != nil {
		return err
	}

	return nil
}

func getListOfMECServices() ([]client.SandboxMecServices, error) {
	fmt.Println(">>> getListOfMECServices")

	// Sanity checks
	if sandboxName == "" {
		return nil, errors.New("No sandbox available")
	}

	services, _, err := cl.SandboxMECServicesApi.SandboxMecServicesGET(context.TODO(), sandboxName)
	if err != nil {
		return nil, err
	}

	return services, nil
}

func getListOfMECAppInstIds() ([]client.ApplicationInfo, error) {
	fmt.Println(">>> getListOfMECAppInstIds")

	// Sanity checks
	if sandboxName == "" {
		return nil, errors.New("No sandbox available")
	}

	appsInfos, _, err := cl.SandboxAppInstancesApi.SandboxAppInstancesGET(context.TODO(), sandboxName)
	if err != nil {
		return nil, err
	}

	return appsInfos, nil
}

func createMECAppInstId(appInfo client.ApplicationInfo) error {
	fmt.Println(">>> createMECAppInstId: ", appInfo)

	// Sanity checks
	if sandboxName == "" {
		return errors.New("No sandbox available")
	}

	_, _, err := cl.SandboxAppInstancesApi.SandboxAppInstancesPOST(context.TODO(), appInfo, sandboxName)
	if err != nil {
		return err
	}

	return nil
}

func deleteMECAppInstId() error {
	fmt.Println(">>> deleteMECAppInstId")

	// Sanity checks
	if sandboxName == "" {
		return errors.New("No sandbox available")
	}
	if appsInfo.Id == "" {
		return errors.New("No App instance available")
	}

	_, err := cl.SandboxAppInstancesApi.SandboxAppInstancesDELETE(context.TODO(), sandboxName, appsInfo.Id)
	if err != nil {
		return err
	}

	return nil
}

func verify_idx_len(choice string, len int) (int, error) {
	idx, err := strconv.Atoi(choice)
	if err != nil {
		return -1, err
	}
	if idx >= len {
		return -1, errors.New("Index out of range: " + choice)
	}

	return idx, nil
}

func getListOfUes() (ues []UeContext, err error) {
	fmt.Println(">>> getListOfUes")

	// Sanity checks
	if sandboxName == "" {
		return ues, errors.New("No sandbox available")
	}

	u, _, err := cl.SandboxUEControllerApi.SandboxUeControllerGET(context.TODO(), sandboxName)
	if err != nil {
		return ues, err
	}

	for _, k := range u {
		ues = append(ues, UeContext{id: k.Id, v: k.Count})
	}
	fmt.Println("getListOfUes: ues=", ues)

	return ues, nil
}

func increaseUE(idx int) error {
	fmt.Println(">>> increaseUE: idx=", idx)
	fmt.Println(">>> increaseUE: ues=", ues[idx])

	// Sanity checks
	if sandboxName == "" {
		return errors.New("No sandbox available")
	}

	if ues[idx].v == 4 {
		return errors.New("Already reach the maximum value for " + ues[idx].id)
	}
	_, err := cl.SandboxUEControllerApi.SandboxUeControllerPATCH(context.TODO(), sandboxName, ues[idx].id, int32(ues[idx].v))
	if err != nil {
		return err
	}
	ues[idx].v = ues[idx].v + 1

	return nil
}

func decreaseUE(idx int) error {
	fmt.Println(">>> decreaseUE: idx=", idx)
	fmt.Println(">>> decreaseUE: ues=", ues[idx])

	// Sanity checks
	if sandboxName == "" {
		return errors.New("No sandbox available")
	}

	if ues[idx].v == 0 {
		return errors.New("Already reach the minimum value for " + ues[idx].id)
	}
	ues[idx].v = ues[idx].v - 1
	_, err := cl.SandboxUEControllerApi.SandboxUeControllerPATCH(context.TODO(), sandboxName, ues[idx].id, int32(ues[idx].v))
	if err != nil {
		return err
	}

	return nil
}

func mec011_send_confirm_ready() (subId string, response *http.Response, err error) {
	fmt.Println(">>> mec011_send_confirm_ready")

	// Sanity checks
	if sandboxName == "" {
		return "", nil, errors.New("No sandbox available")
	} else if appsInfo.Id == "" {
		return "", nil, errors.New("No App instance available")
	}

	// Set URL
	url := mecUrl + "/" + sandboxName + "/" + mecPlateform + "/mec_app_support/v2/applications/" + appsInfo.Id + "/confirm_ready"
	fmt.Println("mec011_send_confirm_ready: url: " + url)
	// Build message body
	json_body := "{\"indication\":\"READY\"}"
	io_body := strings.NewReader(json_body)
	// Send request and await response
	_, response, err = send_mec_service_request(http.MethodPost, url, io_body, nil, nil, nil)
	if err != nil {
		return "", nil, err
	}
	fmt.Println("mec011_send_confirm_ready: confirm ready: " + response.Status)

	return mec011_send_subscribe_termination()
}

func mec011_send_subscribe_termination() (subId string, response *http.Response, err error) {
	fmt.Println(">>> mec011_send_subscribe_termination")

	// Sanity checks
	if sandboxName == "" {
		return "", nil, errors.New("No sandbox available")
	} else if appsInfo.Id == "" {
		return "", nil, errors.New("No App instance available")
	}

	// Set URL
	url := mecUrl + "/" + sandboxName + "/" + mecPlateform + "/mec_app_support/v2/applications/" + appsInfo.Id + "/subscriptions"
	fmt.Println("mec011_send_subscribe_termination: url: " + url)
	// Build message body
	appTerminationBody := AppTerminationNotificationSubscription{
		SubscriptionType:  "AppTerminationNotificationSubscription",
		CallbackReference: callbackUrl + "/asc/termination",
		AppInstanceId:     appsInfo.Id,
	}
	json_body, err := json.Marshal(appTerminationBody)
	if err != nil {
		return "", nil, err
	}
	fmt.Println("mec011_send_subscribe_termination: json_body: " + string(json_body))
	io_body := bytes.NewReader(json_body)
	fmt.Println("mec011_send_subscribe_termination: json_body: ", io_body)
	// Send request and await response
	_, response, err = send_mec_service_request(http.MethodPost, url, io_body, nil, nil, nil)
	if err != nil {
		return "", nil, err
	}
	fmt.Println("mec011_send_subscribe_termination: response: " + response.Status)
	fmt.Println("mec011_send_subscribe_termination: Location: " + response.Header["Location"][0])

	subId, err = extract_subscription_id(url, response.Header["Location"][0])
	if err != nil {
		return "", nil, err
	}

	return subId, response, nil
}

func extract_subscription_id(base_url string, subscription_url string) (string, error) {
	fmt.Println(">>> extract_subscription_id: base_url: " + base_url)
	fmt.Println(">>> extract_subscription_id: subscription_url: " + subscription_url)

	re := regexp.MustCompile(base_url + "/(?P<sub_id>.+)")
	if re == nil {
		return "", errors.New("Regexp creation failure")
	}
	matches := re.FindStringSubmatch(subscription_url)
	if matches == nil {
		return "", errors.New("Matching failure")
	}

	return matches[re.SubexpIndex("sub_id")], nil
}

func delete_termination_subscription() (err error) {
	fmt.Println(">>> delete_termination_subscription")

	// Sanity checks
	if sandboxName == "" {
		return errors.New("No sandbox available")
	} else if appsInfo.Id == "" {
		return errors.New("No App instance available")
	} else if terminationSubscriptionID == "" {
		return errors.New("No termination subscription")
	}

	// Set URL
	url := mecUrl + "/" + sandboxName + "/" + mecPlateform + "/mec_app_support/v2/applications/" + appsInfo.Id + "/subscriptions/" + terminationSubscriptionID
	fmt.Println("delete_termination_subscription: url: " + url)
	// Send request and await response
	_, _, err = send_mec_service_request(http.MethodDelete, url, nil, nil, nil, nil)
	if err != nil {
		fmt.Println("delete_termination_subscription: " + err.Error())
		return err
	}

	terminationSubscriptionID = ""

	return nil
}

func mec011_send_registration() (body []byte, response *http.Response, err error) {
	fmt.Println(">>> mec011_send_registration: ", appsInfo.Name)
	fmt.Println(">>> mec011_send_registration: ", appsInfo.Id)

	// Sanity checks
	if sandboxName == "" {
		return nil, nil, errors.New("No sandbox available")
	} else if appsInfo.Id == "" {
		return nil, nil, errors.New("No App instance available")
	}

	// Set URL
	url := mecUrl + "/" + sandboxName + "/" + mecPlateform + "/mec_app_support/v2/registrations"
	fmt.Println("mec011_send_registration: url: " + url)
	// Build message body
	appInfo := AppInfo{
		AppName:       appsInfo.Name,
		AppProvider:   "ETSI",
		AppDId:        uuid.New().String(),
		AppInstanceId: appsInfo.Id,
		IsInsByMec:    true,
	}
	fmt.Println("mec011_send_registration: appInfo: ", appInfo)
	json_body, err := json.Marshal(appInfo)
	if err != nil {
		return nil, nil, err
	}
	fmt.Println("mec011_send_registration: json_body: " + string(json_body))
	io_body := bytes.NewReader(json_body)
	fmt.Println("mec011_send_registration: io_body: ", io_body)
	// Send request and await response
	body, response, err = send_mec_service_request(http.MethodPost, url, io_body, nil, nil, nil)
	if err != nil {
		return nil, nil, err
	}
	fmt.Println("mec011_send_registration: status: " + response.Status)
	fmt.Println("mec011_send_registration: Location: ", response.Header["Location"][0])
	isRegistered = true

	return body, response, nil
}

func mec011_send_deregistration() (response *http.Response, err error) {
	fmt.Println(">>> mec011_send_deregistration")

	// Sanity checks
	if sandboxName == "" {
		return nil, errors.New("No sandbox available")
	} else if appsInfo.Id == "" {
		return nil, errors.New("No App instance available")
	}

	// Set URL
	url := mecUrl + "/" + sandboxName + "/" + mecPlateform + "/mec_app_support/v2/registrations/" + appsInfo.Id
	fmt.Println("mec011_send_deregistration: url: " + url)
	// Send request and await response
	_, response, err = send_mec_service_request(http.MethodDelete, url, nil, nil, nil, nil)
	if err != nil {
		return nil, err
	}
	fmt.Println("mec011_send_deregistration: status: " + response.Status)

	isRegistered = false

	return response, nil
}

func mec011_create_service() (resId string, response *http.Response, err error) {
	fmt.Println(">>> mec011_create_service")

	// Sanity checks
	if sandboxName == "" {
		return "", nil, errors.New("No sandbox available")
	} else if appsInfo.Id == "" {
		return "", nil, errors.New("No App instance available")
	} else if appServiceInfo.SerInstanceId != "" {
		return "", nil, errors.New("A MEC service is already created")
	}

	// Set URL
	url := mecUrl + "/" + sandboxName + "/" + mecPlateform + "/mec_service_mgmt/v1/applications/" + appsInfo.Id + "/services"
	fmt.Println("mec011_create_service: url: " + url)
	// Build message body
	transportType := "REST_HTTP"
	appServiceInfo = ServiceInfo{
		SerName: "demo6 MEC Service",
		SerCategory: &CategoryRef{
			Href:    callbackUrl + "/demo6svc/v1/Demo",
			Id:      uuid.New().String(),
			Name:    "Demo",
			Version: "1.0.0",
		},
		Version: "1.0.0",
		State:   "ACTIVE",
		TransportInfo: TransportInfo{
			Id:       "transport",
			Name:     "REST",
			Type_:    &transportType,
			Protocol: "HTTP",
			Version:  "2.0",
			Endpoint: &OneOfTransportInfoEndpoint{},
		},
		Serializer: "JSON",
	}
	appServiceInfo.TransportInfo.Endpoint.Uris = append(appServiceInfo.TransportInfo.Endpoint.Uris, callbackUrl+"/demo6svc/v1")
	json_body, err := json.Marshal(appServiceInfo)
	if err != nil {
		return "", nil, err
	}
	fmt.Println("mec011_create_service: json_body: " + string(json_body))
	io_body := bytes.NewReader(json_body)
	fmt.Println("mec011_create_service: json_body: ", io_body)
	// Send request and await response
	body, response, err := send_mec_service_request(http.MethodPost, url, io_body, nil, nil, nil)
	if err != nil {
		return "", nil, err
	}
	fmt.Println("mec011_create_service: body: " + string(body))
	if response.StatusCode != 201 {
		return "", nil, errors.New("Invalid Status: " + response.Status)
	}
	// Unmarshal the response body
	fmt.Println("mec011_create_service: body: " + string(body))
	err = json.Unmarshal([]byte(body), &appServiceInfo)
	if err != nil {
		return "", nil, err
	}

	fmt.Println("mec011_create_service: Location: " + response.Header["Location"][0])
	resId, err = extract_subscription_id(url, response.Header["Location"][0])
	if err != nil {
		return "", nil, err
	}

	return resId, response, nil
}

func mec011_delete_service() (err error) {
	fmt.Println(">>> mec011_delete_service")

	// Sanity checks
	if sandboxName == "" {
		return errors.New("No sandbox available")
	} else if appsInfo.Id == "" {
		return errors.New("No App instance available")
	} else if appServiceInfo.SerInstanceId == "" {
		return errors.New("No MEC service created")
	}

	// Set URL
	url := mecUrl + "/" + sandboxName + "/" + mecPlateform + "/mec_service_mgmt/v1/applications/" + appsInfo.Id + "/services/" + appServiceInfo.SerInstanceId
	fmt.Println("mec011_delete_service: url: " + url)
	// Send request and await response
	_, _, err = send_mec_service_request(http.MethodDelete, url, nil, nil, nil, nil)
	if err != nil {
		return err
	}

	appServiceInfo.SerInstanceId = ""

	return nil
}

func mec011_get_mec_services() (body []byte, response *http.Response, err error) {
	fmt.Println(">>> mec011_get_mec_services")

	// Sanity checks
	if sandboxName == "" {
		return nil, nil, errors.New("No sandbox available")
	} else if appsInfo.Id == "" {
		return nil, nil, errors.New("No App instance available")
	}

	// Set URL
	url := mecUrl + "/" + sandboxName + "/" + mecPlateform + "/mec_service_mgmt/v1/services"
	fmt.Println("mec011_get_mec_services: url: " + url)
	// Send request and await response
	body, response, err = send_mec_service_request(http.MethodGet, url, nil, nil, nil, nil)
	if err != nil {
		return nil, nil, err
	}

	return body, nil, nil
}

func send_mec_service_request(method string, path string, body io.Reader, vars url.Values, queryParams url.Values, location *string) (resbody []byte, res *http.Response, err error) {
	fmt.Println(">>> send_mec_service_request: ", appsInfo.Name)

	// Sanity checks
	if sandboxName == "" {
		err = errors.New("No sandbox available")
		return nil, nil, err
	}

	// Setup path and query parameters
	url, err := url.Parse(path)
	if err != nil {
		return nil, nil, err
	}

	// Adding Query Param
	query := url.Query()
	for k, v := range queryParams {
		for _, iv := range v {
			query.Add(k, iv)
		}
	}

	// Encode the parameters.
	url.RawQuery = query.Encode()

	// Generate a new request
	var localVarRequest *http.Request
	if body != nil {
		localVarRequest, err = http.NewRequest(method, url.String(), body)
	} else {
		localVarRequest, err = http.NewRequest(method, url.String(), nil)
	}
	if err != nil {
		return nil, nil, err
	}

	// Override request host, if applicable
	if cfg.Host != "" {
		localVarRequest.Host = cfg.Host
	}

	headers := http.Header{}
	headers.Set("Accept", "application/json")
	localVarRequest.Header = headers

	// Add the user agent to the request.
	localVarRequest.Header.Add("User-Agent", cfg.UserAgent)

	fmt.Println("send_mec_service_request: localVarRequest: ", localVarRequest)

	// tr := &http.Transport{
	// 	TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	// }
	// cfg.HTTPClient = &http.Client{Transport: tr}

	res, err = cfg.HTTPClient.Do(localVarRequest)
	if err != nil {
		return nil, nil, err
	}

	resbody, err = ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, nil, err
	}
	res.Body.Close()

	return resbody, res, err
}

func mec030_get_v2x_uu_unicast_setting() (body []byte, response *http.Response, err error) {
	fmt.Println(">>> mec030_get_v2x_uu_unicast_setting")

	// Sanity checks
	if sandboxName == "" {
		return nil, nil, errors.New("No sandbox available")
	}

	url := mecUrl + "/" + sandboxName + "/" + mecPlateform + "/vis/v2/queries/uu_unicast_provisioning_info?location_info=ecgi,268708941961,268711972264"
	fmt.Println("send_mec_service_request: url: " + url)
	body, response, err = send_mec_service_request(http.MethodGet, url, nil, nil, nil, nil)
	if err != nil {
		return nil, nil, err
	}

	return body, response, nil
}

func mec030_subscribe_v2x_messages() (body []byte, response *http.Response, err error) {
	fmt.Println(">>> mec030_subscribe_v2x_messages")

	// Sanity checks
	if sandboxName == "" {
		return nil, nil, errors.New("No sandbox available")
	}

	// Set URL
	url := mecUrl + "/" + sandboxName + "/" + mecPlateform + "/vis/v2/subscriptions"
	fmt.Println("mec030_subscribe_v2x_messages: url: " + url)
	// Build message body
	var v2xMsgSubscription = V2xMsgSubscription{
		CallbackReference: callbackUrl + "/vis/v2/v2x_msg_notification",
		FilterCriteria: &V2xMsgSubscriptionFilterCriteria{
			MsgType:         []int32{1, 2},
			StdOrganization: "ETSI",
		},
		SubscriptionType: "V2xMsgSubscription",
	}
	json_body, err := json.Marshal(v2xMsgSubscription)
	if err != nil {
		return nil, nil, err
	}
	io_body := bytes.NewReader(json_body)
	// Send request and await response
	body, response, err = send_mec_service_request(http.MethodPost, url, io_body, nil, nil, nil)
	if err != nil {
		return nil, nil, err
	}

	return body, response, nil
}

func mec030_delete_v2x_messages_subscription(choice string) (response *http.Response, err error) {
	fmt.Println(">>> mec030_delete_v2x_messages_subscription: ", choice)

	// Sanity checks
	if sandboxName == "" {
		return nil, errors.New("No sandbox available")
	}

	// Set URL
	url := mecUrl + "/" + sandboxName + "/" + mecPlateform + "/vis/v2/subscriptions/" + choice
	fmt.Println("mec030_delete_v2x_messages_subscription: url: " + url)
	// Send request and await response
	_, response, err = send_mec_service_request(http.MethodDelete, url, nil, nil, nil, nil)
	if err != nil {
		return nil, err
	}

	return response, nil
}

func v2x_msg_notification(w http.ResponseWriter, r *http.Request) {
	fmt.Println(">>> v2x_msg_notification: ", r)
	w.WriteHeader(http.StatusOK)
}

func app_status() (resp string) {
	resp = ""
	if sandboxName != "" {
		resp += "Sandbox: " + sandboxName
	}
	if appsInfo.Id != "" {
		resp += ", appsInfo.Id: " + appsInfo.Id
	}
	if appServiceInfo.SerInstanceId != "" {
		resp += ", SerInstanceId: " + appServiceInfo.SerInstanceId
	}
	if terminationSubscriptionID != "" {
		resp += ", Subscription: " + terminationSubscriptionID
	}
	if isRegistered {
		resp += ", Demo6 app registered"
	} else {
		resp += ", Demo6 app not registered"
	}

	return resp
}

func main() {
	if len(os.Args) < 2 {
		// no config argument
		fmt.Errorf("Missing parameter, require file path to configurations!")
		return
	}

	// Read configuration file path in command line arugments
	configPath := os.Args[1]
	dir = strings.TrimSpace(filepath.Dir(configPath))
	fileName = strings.TrimSpace(filepath.Base(configPath))
	fmt.Println("dir: ", dir)
	fmt.Println("fileName: ", fileName)

	// Retrieve environmental variable
	var config Config
	// trim file extension
	envName := strings.TrimSuffix(fileName, ".yaml")
	fmt.Println("Using config values from ", dir, "/", envName)
	config, err := LoadConfig(dir, envName)
	if err != nil {
		fmt.Errorf(err.Error())
		return
	}
	// 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 {
		fmt.Errorf("Wrong configuration!")
		return
	}
	if strings.HasSuffix(mecUrl, "/") {
		mecUrl = strings.TrimSuffix(mecUrl, "/")
	}
	mecPlateform = config.MecPlatform
	fmt.Println("mecUrl: " + mecUrl)
	fmt.Println("mecPlateform: " + mecPlateform)

	cfg = client.NewConfiguration()
	if cfg == nil {
		fmt.Errorf("Failed to create client configuration!")
		return
	}
	cfg.BasePath = mecUrl + "/sandbox-api/v1"
	fmt.Println("cfg.BasePath: " + cfg.BasePath)
	cl = client.NewAPIClient(cfg)
	if cl == nil {
		fmt.Errorf("Failed to create client reference!")
		return
	}

	go func() {
		// Start demo6 server
		reader = bufio.NewReader(os.Stdin)

		var message string = ""
		for run {
			// Display menu and read selection
			choice := menu(message)

			// Apply it
			fmt.Println("choice: ", choice)
			if strings.Compare(choice[0], "q") == 0 {
				run = false
				break
			} else {
				message = process_choice(choice)
			}

		} // End of 'for' statement

	}()

	// Listen for SIGKILL
	go func() {
		sigchan := make(chan os.Signal, 10)
		signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM)
		<-sigchan
		fmt.Println("Waiting to shut down program !")
		run = false
	}()

	// Start REST API Server
	go func() {
		router := NewRouter()
		methods := handlers.AllowedMethods([]string{"OPTIONS", "DELETE", "GET", "HEAD", "POST", "PUT"})
		header := handlers.AllowedHeaders([]string{"content-type"})
		fmt.Println(http.ListenAndServe(":80", handlers.CORS(methods, header)(router)))
		run = false
		fmt.Println("<<< Stop REST API Server")
	}()

	// Listen for demo6 error exit program
	go func() {
		<-done
		fmt.Println("Waiting to shut down program !")
		run = false
	}()

	for {
		// Invoke graceful termination upon program kill
		if !run {
			fmt.Println("Invoking demo6 graceful termination")
			if sandboxName != "" {
				logout()
			}
			break
		}
		time.Sleep(time.Second)
	}

	return
}

func LoadConfig(path string, name string) (config Config, err error) {
	viper.SetConfigType("yaml")
	viper.AddConfigPath(path)
	viper.SetConfigName(name)
	viper.AutomaticEnv()

	err = viper.ReadInConfig()
	if err != nil {
		return config, err
	}

	err = viper.Unmarshal(&config)
	if err != nil {
		return config, err
	}
	return

}

func process_choice(choice []string) string {

	var message string
	if strings.Compare(choice[0], LOGIN) == 0 {
		userCode, verificationUri, _ = login()
		message = fmt.Sprintf("User Code: %s and Verification URI is %s: ", userCode, verificationUri)
	} else if strings.Compare(choice[0], LOGOUT) == 0 {
		err := logout()
		if err != nil {
			return err.Error()
		}
		message = "Sandbox terminated"
	} else if strings.Compare(choice[0], NAMESPACE) == 0 {
		sandbox, err := getNamespace()
		if err != nil {
			return err.Error()
		}
		message = fmt.Sprintf("Sandbox ID is: %s", sandbox)
	} else if strings.Compare(choice[0], LIST_SC) == 0 {
		scenarios, _ = getListOfScenarios()
		message = fmt.Sprintf("scenarios: %s", fmt.Sprint(scenarios))
	} else if strings.Compare(choice[0], SC) == 0 {
		if len(choice) == 1 {
			return fmt.Sprintf("Index is not set")
		}
		idx, err := verify_idx_len(choice[1], len(scenarios))
		if err != nil {
			return fmt.Sprintf("Invalid index: %s", err.Error())
		}
		scenario, _ = getScenario(scenarios[idx].Id)
		message = fmt.Sprintf("Scenario %s:", fmt.Sprint(scenario))
	} else if strings.Compare(choice[0], ACTIVATE) == 0 {
		if len(choice) == 1 {
			return fmt.Sprintf("Index is not set")
		}
		var err error
		scenarioId, err = verify_idx_len(choice[1], len(scenarios))
		if err != nil {
			return fmt.Sprintf("Invalid index: %s", err.Error())
		}
		err = activateScenario(scenarios[scenarioId].Id)
		if err != nil {
			return err.Error()
		}
		message = fmt.Sprintf("Scenario %s activated (wait some seconds before the next command)", scenarios[scenarioId].Id)
	} else if strings.Compare(choice[0], DEACTIVATE) == 0 {
		err := terminateScenario(scenarios[scenarioId].Id)
		if err != nil {
			return err.Error()
		}
		message = fmt.Sprintf("Scenario %s terminated (wait some seconds before the next command)", scenarios[scenarioId].Id)
		scenarioId = -1
	} else if strings.Compare(choice[0], LIST_SERVICES) == 0 {
		var err error
		services, err = getListOfMECServices()
		if err != nil {
			return err.Error()
		}
		message = fmt.Sprintf("Services:  %s", fmt.Sprint(services))
	} else if strings.Compare(choice[0], LIST_APP) == 0 {
		appsInfos, err := getListOfMECAppInstIds()
		if err != nil {
			return err.Error()
		}
		message = fmt.Sprintf("AppInstIds:  %s", fmt.Sprint(appsInfos))
	} else if strings.Compare(choice[0], CREATE_APP) == 0 {
		if appsInfo.Id != "" {
			return fmt.Sprintf("App instance id already created: %s", appsInfo.Id)
		}
		appsInfo = client.ApplicationInfo{
			Id:       uuid.New().String(),
			Name:     "demo6 test app",
			NodeName: mecPlateform,
			Type_:    "USER",
			Persist:  false,
		}
		err := createMECAppInstId(appsInfo)
		if err != nil {
			return err.Error()
		}
		message = fmt.Sprintf("appsInfo:  %s created", fmt.Sprint(appsInfo.Id))
	} else if strings.Compare(choice[0], DELETE_APP) == 0 {
		err := deleteMECAppInstId()
		if err != nil {
			return err.Error()
		}
		message = fmt.Sprintf("appsInfo:  %s deleted", fmt.Sprint(appsInfo.Id))
		appsInfo.Id = ""
	} else if strings.Compare(choice[0], LIST_UES) == 0 {
		ues = make([]UeContext, 0)
		var err error
		ues, err = getListOfUes()
		if err != nil {
			return fmt.Sprintf("Failed to get the list of UEs: %s", err.Error())
		}
		message = fmt.Sprintf("List of UEs %v", ues)
	} else if strings.Compare(choice[0], INC_UE) == 0 {
		if len(choice) == 1 {
			return fmt.Sprintf("Index is not set")
		}
		ueId, err := verify_idx_len(choice[1], len(ues))
		if err != nil {
			return fmt.Sprintf("Invalid index: %s", err.Error())
		}
		err = increaseUE(ueId)
		if err != nil {
			return err.Error()
		}
		message = fmt.Sprintf("Increase %s, new value: %d", ues[ueId].id, ues[ueId].v)
	} else if strings.Compare(choice[0], DEC_UE) == 0 {
		if len(choice) == 1 {
			return fmt.Sprintf("Index is not set")
		}
		ueId, err := verify_idx_len(choice[1], len(ues))
		if err != nil {
			return fmt.Sprintf("Invalid index: %s", err.Error())
		}
		err = decreaseUE(ueId)
		if err != nil {
			return err.Error()
		}
		message = fmt.Sprintf("Decrease %s, new value: %d", ues[ueId].id, ues[ueId].v)
	} else if strings.Compare(choice[0], MEC011_CONFIRM_READY) == 0 {
		var err error
		terminationSubscriptionID, _, err = mec011_send_confirm_ready()
		if err != nil {
			return err.Error()
		}
		message = fmt.Sprintf("Termination subscription ID:  %s", terminationSubscriptionID)
	} else if strings.Compare(choice[0], MEC011_REGISTRATION) == 0 {
		var err error
		body, _, err := mec011_send_registration()
		if err != nil {
			return err.Error()
		}
		message = fmt.Sprintf("response body:  %s", string(body))
	} else if strings.Compare(choice[0], MEC011_DEREGISTRATION) == 0 {
		var err error
		response, err := mec011_send_deregistration()
		if err != nil {
			return err.Error()
		}
		message = fmt.Sprintf("response body:  %s", response.Status)
	} else if strings.Compare(choice[0], MEC011_CREATE_SVC) == 0 {
		var err error
		resId, _, err := mec011_create_service()
		if err != nil {
			return err.Error()
		}
		message = fmt.Sprintf("response body: resource id: %s", resId)
	} else if strings.Compare(choice[0], MEC011_DELETE_SVC) == 0 {
		err := mec011_delete_service()
		if err != nil {
			return err.Error()
		}
		message = fmt.Sprintf("MEC Service deleted.")
	} else if strings.Compare(choice[0], MEC011_GET_SVC) == 0 {
		body, _, err := mec011_get_mec_services()
		if err != nil {
			return err.Error()
		}
		message = fmt.Sprintf("response body:  %s", string(body))
	} else if strings.Compare(choice[0], MEC030_UU_SETTINGS) == 0 {
		var err error
		body, _, err := mec030_get_v2x_uu_unicast_setting()
		if err != nil {
			return err.Error()
		}
		message = fmt.Sprintf("response body:  %s", string(body))
	} else if strings.Compare(choice[0], MEC030_V2X_SUB) == 0 {
		var err error
		body, _, err := mec030_subscribe_v2x_messages()
		if err != nil {
			return err.Error()
		}
		message = fmt.Sprintf("response body:  %s", string(body))
	} else if strings.Compare(choice[0], MEC030_V2X_DEL_SUB) == 0 {
		if len(choice) == 1 {
			return fmt.Sprintf("Subscription ID is not set")
		}
		_, err := mec030_delete_v2x_messages_subscription(choice[1])
		if err != nil {
			return err.Error()
		}
		message = fmt.Sprintf("response body:  Subscription %s deleted", choice[1])
	} else if strings.Compare(choice[0], STATUS) == 0 {
		resp := app_status()
		message = fmt.Sprintf("Current status: %s", resp)
	} else {
		message = fmt.Sprintf("Invalid command: %s", choice)
	}

	return message
}
