/*
 * Copyright (c) 2022  InterDigital Communications, Inc
 *
 * 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.
 */

/**
This package implements MEC-016 application instantiation with the following limitation:
1) The application image is already available in the MEC system (AdvantEDGE platform)
2) Onboarding application is not supported (MEC-010-2)
3) Application list is hard-coded (to be enhance in the furture)
*/
package meepdaimgr

import (
	"database/sql"
	"os"
	//"encoding/json"
	"errors"
	//"sort"
	//"strconv"
	"strings"
	"time"

	uuid "github.com/google/uuid"

	log "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-logger"

	_ "github.com/lib/pq"
)

// DB Config
const (
	DbHost     = "meep-postgis.default.svc.cluster.local"
	DbPort     = "5432"
	DbUser     = ""
	DbPassword = ""
	DbDefault  = "postgres"

	DbMaxRetryCount int = 2
)

// Enable profiling
const profiling = false

var profilingTimers map[string]time.Time

// Tables fields name
const (
	FieldContextId            = "contextId"
	FieldAssociateDevAppId    = "associateDevAppId"
	FieldCallbackReference    = "callbackReference"
	FieldAppLocationUpdates   = "appLocationUpdates"
	FieldAppAutoInstantiation = "appAutoInstantiation"
	FieldAppInfo              = "appInfo"
	FieldAppDId               = "appDId"
	FieldAppName              = "appName"
	FieldAppProvider          = "appProvider"
	FieldAppSoftVersion       = "appSoftVersion"
	FieldAppDVersion          = "appDVersion"
	FieldAppDescription       = "appDescription"
	FieldUserAppInstanceInfo  = "userAppInstanceInfo"
	FieldAppPackageSource     = "appPackageSource"
	FieldAppInstanceId        = "appInstanceId"
	FieldReferenceURI         = "referenceURI"
	FieldAppLocation          = "appLocation"
	FieldArea                 = "area"
	FieldCivicAddressElement  = "civicAddressElement"
	FieldCountryCode          = "countryCode"
	FieldCoordinates          = "coordinates"
)

type Uri string

// ETSI GS MEC 016 Clause Table 6.2.2-1: Definition of type ApplicationList
type ApplicationList struct {
	AppList []AppList `json:"appList"`
}

// ETSI GS MEC 016 Clause Table 6.2.2-1: Definition of type ApplicationList
type AppList struct {
	AppInfoList       []AppInfoList      `json:"appInfoList"`
	vendorSpecificExt *VendorSpecificExt `json:"vendorSpecificExt,omitempty"`
}

type VendorSpecificExt struct {
	vendorId string `json:"vendorId"`
}

// ETSI GS MEC 016 Clause Table 6.2.2-1: Definition of type ApplicationList
type AppInfoList struct {
	AppDId         string
	AppName        string
	AppProvider    string
	AppSoftVersion string
	AppDVersion    string
	AppDescription string
	AppLocation    LocationConstraints
	AppCharcs      []AppCharcs
}

// ETSI GS MEC 016 Clause Table 6.2.2-1: Definition of type ApplicationList
type AppCharcs struct {
	Memory      *uint32
	Storage     *uint32
	Latency     *uint32
	Bandwidth   *uint32
	ServiceCont *uint32
}

// ETSI GS MEC 016 Clause 6.2.3 Type: AppContext
type AppContext struct {
	ContextId            *string //Uniquely identifies the application context in the MEC system
	AssociateDevAppId    string  // Uniquely identifies the device application
	CallbackReference    Uri
	AppLocationUpdates   bool
	AppAutoInstantiation bool
	AppInfo              AppInfo
}

// ETSI GS MEC 016 Clause 6.2.3 Type: AppContext
type AppInfo struct {
	AppDId              *string
	AppName             string
	AppProvider         string
	AppSoftVersion      *string
	AppDVersion         string
	AppDescription      string
	UserAppInstanceInfo UserAppInstanceInfo
	AppPackageSource    *Uri
}

// ETSI GS MEC 016 Clause 6.2.3 Type: AppContext
type UserAppInstanceInfo []UserAppInstanceInfoItem
type UserAppInstanceInfoItem struct {
	AppInstanceId *string
	ReferenceURI  *Uri
	AppLocation   LocationConstraints
}

// ETSI GS MEC 016 Clause 6.5.2 Type: LocationConstraints
type LocationConstraints []LocationConstraintsItem
type LocationConstraintsItem struct {
	Area                *Polygon
	CivicAddressElement *CivicAddressElement
	CountryCode         *string
}
type Polygon struct {
	Coordinates [][][]float32 `json:"coordinates"`
}
type CivicAddressElement []CivicAddressElementItem
type CivicAddressElementItem struct {
	CaType  int32  `json:"caType,omitempty"`
	CaValue string `json:"caValue,omitempty"`
}

// ETSI GS MEC 016 Clause 6.2.4 Type: ApplicationLocationAvailability
type ApplicationLocationAvailability struct {
	associateDevAppId  string
	AppDId             *AppInfo
	AvailableLocations *AvailableLocations
}

// ETSI GS MEC 016 Clause 6.2.4 Type: ApplicationLocationAvailability
type AvailableLocations []AvailableLocationsItem

// ETSI GS MEC 016 Clause 6.2.4 Type: ApplicationLocationAvailability
type AvailableLocationsItem struct {
	appLocation      *LocationConstraints
	appPackageSource *Uri
}

// DAI Asset Manager
type DaiMgr struct {
	name      string
	namespace string
	user      string
	pwd       string
	host      string
	port      string
	dbName    string
	db        *sql.DB
	connected bool
	updateCb  func(string)
}

// DB Table Names
const (
	AppContextTable                      = "app_context"
	AppInfoTable                         = "app_info"
	UserAppInstanceInfoTable             = "user_app_instance_info"
	LocationConstraintsTable             = "location_constraints"
	AvailableLocationsTable              = "available_locations"
	ApplicationLocationAvailabilityTable = "application_location_availability"
	AppInfoListTable                     = "app_info_list"
	AppInfoListLocationConstraintsTable  = "app_info_list_location_constraints"
	AppCharcsTable                       = "app_charcs"
)

// Profiling init
func init() {
	if profiling {
		profilingTimers = make(map[string]time.Time)
	}
}

// NewAssetMgr - Creates and initializes a new DAI Manager
func NewDaiMgr(name, namespace, user, pwd, host, port string) (am *DaiMgr, err error) {
	if name == "" {
		err = errors.New("Missing connector name")
		return nil, err
	}

	// Create new Asset Manager
	am = new(DaiMgr)
	am.name = name
	if namespace != "" {
		am.namespace = namespace
	} else {
		am.namespace = "default"
	}
	am.user = user
	am.pwd = pwd
	am.host = host
	am.port = port

	// Connect to Postgis DB
	for retry := 0; retry <= DbMaxRetryCount; retry++ {
		am.db, err = am.connectDB("", am.user, am.pwd, am.host, am.port)
		if err == nil {
			break
		}
	}
	if err != nil {
		log.Error("Failed to connect to postgis DB with err: ", err.Error())
		return nil, err
	}
	defer am.db.Close()

	// Create sandbox DB if it does not exist
	// Use format: '<namespace>_<name>' & replace dashes with underscores
	am.dbName = strings.ToLower(strings.Replace(namespace+"_"+name, "-", "_", -1))

	// Ignore DB creation error in case it already exists.
	// Failure will occur at DB connection if DB was not successfully created.
	_ = am.CreateDb(am.dbName)

	// Close connection to postgis DB
	_ = am.db.Close()

	// Connect with sandbox-specific DB
	am.db, err = am.connectDB(am.dbName, user, pwd, host, port)
	if err != nil {
		log.Error("Failed to connect to sandbox DB with err: ", err.Error())
		return nil, err
	}

	log.Info("Postgis Connector successfully created")
	am.connected = true
	return am, nil
}

func (am *DaiMgr) connectDB(dbName, user, pwd, host, port string) (db *sql.DB, err error) {
	// Set default values if none provided
	if dbName == "" {
		dbName = DbDefault
	}
	if host == "" {
		host = DbHost
	}
	if port == "" {
		port = DbPort
	}
	log.Debug("Connecting to Postgis DB [", dbName, "] at addr [", host, ":", port, "]")

	// Open postgis DB
	connStr := "user=" + user + " password=" + pwd + " dbname=" + dbName + " host=" + host + " port=" + port + " sslmode=disable"
	db, err = sql.Open("postgres", connStr)
	if err != nil {
		log.Warn("Failed to connect to Postgis DB with error: ", err.Error())
		return nil, err
	}

	// Make sure connection is up
	err = db.Ping()
	if err != nil {
		log.Warn("Failed to ping Postgis DB with error: ", err.Error())
		db.Close()
		return nil, err
	}

	log.Info("Connected to Postgis DB [", dbName, "]")
	return db, nil
}

func (am *DaiMgr) SetListener(listener func(string)) error {
	am.updateCb = listener
	return nil
}

func (am *DaiMgr) notifyListener(assetName string) {
	if am.updateCb != nil {
		go am.updateCb(assetName)
	}
}

// DeleteDaiMgr -
func (am *DaiMgr) DeleteDaiMgr() (err error) {

	if am.db == nil {
		err = errors.New("Asset Manager database not initialized")
		log.Error(err.Error())
		return err
	}

	// Close connection to sandbox-specific DB
	_ = am.db.Close()

	// Connect to Postgis DB
	am.db, err = am.connectDB("", am.user, am.pwd, am.host, am.port)
	if err != nil {
		log.Error("Failed to connect to postgis DB with err: ", err.Error())
		return err
	}
	defer am.db.Close()

	// Destroy sandbox database
	_ = am.DestroyDb(am.dbName)

	return nil
}

// CreateDb -- Create new DB with provided name
func (am *DaiMgr) CreateDb(name string) (err error) {
	_, err = am.db.Exec("CREATE DATABASE " + name)
	if err != nil {
		log.Error(err.Error())
		return err
	}

	log.Info("Created database: " + name)
	return nil
}

// DestroyDb -- Destroy DB with provided name
func (am *DaiMgr) DestroyDb(name string) (err error) {
	_, err = am.db.Exec("DROP DATABASE " + name)
	if err != nil {
		log.Error(err.Error())
		return err
	}

	log.Info("Destroyed database: " + name)
	return nil
}

func (am *DaiMgr) CreateTables() (err error) {
	_, err = am.db.Exec("CREATE EXTENSION IF NOT EXISTS postgis")
	if err != nil {
		log.Error(err.Error())
		return err
	}

	// AppContext
	_, err = am.db.Exec(`CREATE TABLE ` + AppContextTable + ` (
		contextId              varchar(32)     NOT NULL UNIQUE,
		associateDevAppId      varchar(32)     NOT NULL UNIQUE,
		callbackReference      varchar(256)    NOT NULL DEFAULT '',
		appLocationUpdates     boolean         NOT NULL DEFAULT 'false',
		appAutoInstantiation   boolean         NOT NULL DEFAULT 'false',
		appDId                 varchar(32)     NOT NULL UNIQUE,
		PRIMARY KEY (contextId)
	)`)
	if err != nil {
		log.Error(err.Error())
		return err
	}
	log.Info("Created DAI table: ", AppContextTable)

	// AppInfo Table
	_, err = am.db.Exec(`CREATE TABLE ` + AppInfoTable + ` (
		appDId                 varchar(32)     NOT NULL UNIQUE,
		appName                varchar(32)     NOT NULL DEFAULT '',
		appProvider            varchar(32)     NOT NULL DEFAULT '',
		appSoftVersion         varchar(32)     NOT NULL DEFAULT '',
		appDVersion            varchar(32)     NOT NULL DEFAULT '',
		appDescription         varchar(256)    NOT NULL DEFAULT '',
		appPackageSource       varchar(256)    NOT NULL DEFAULT '',
		PRIMARY KEY (appDId),
		FOREIGN KEY (appDId) REFERENCES ` + AppContextTable + `(appDId) ON DELETE CASCADE
	)`)
	if err != nil {
		log.Error(err.Error())
		return err
	}
	log.Info("Created DAI table: ", AppInfoTable)

	// UserAppInstanceInfo Table
	// Warning: appInstanceId shall be present
	_, err = am.db.Exec(`CREATE TABLE ` + UserAppInstanceInfoTable + ` (
		appDId                 varchar(32)     NOT NULL,
		appInstanceId          varchar(32)     NOT NULL UNIQUE,
		referenceURI           varchar(256)    NOT NULL DEFAULT '',
		PRIMARY KEY (appInstanceId),
		FOREIGN KEY (appDId) REFERENCES ` + AppInfoTable + `(appDId) ON DELETE CASCADE
	)`)
	if err != nil {
		log.Error(err.Error())
		return err
	}
	log.Info("Created DAI table: ", UserAppInstanceInfoTable)

	// LocationConstraintsTable Table
	// Warning: appInstanceId shall be present
	_, err = am.db.Exec(`CREATE TABLE ` + LocationConstraintsTable + ` (
		appInstanceId          varchar(32)     UNIQUE,
		appDId                 varchar(32)     NOT NULL DEFAULT '',
		area                   varchar(1024)   NOT NULL DEFAULT '',
		civicAddressElement    varchar(256)    NOT NULL DEFAULT '',
		countryCode            varchar(256)    NOT NULL DEFAULT '',
		PRIMARY KEY (appInstanceId),
		FOREIGN KEY (appDId) REFERENCES ` + AppInfoTable + `(appDId) ON DELETE CASCADE
	)`)
	if err != nil {
		log.Error(err.Error())
		return err
	}
	log.Info("Created DAI table: ", ApplicationLocationAvailabilityTable)

	// AppInfoList Table
	_, err = am.db.Exec(`CREATE TABLE ` + AppInfoListTable + ` (
		appDId                 varchar(32)     UNIQUE,
		appName                varchar(32)     NOT NULL DEFAULT '',
		appProvider            varchar(32)     NOT NULL DEFAULT '',
		appSoftVersion         varchar(32)     NOT NULL DEFAULT '',
		appDVersion            varchar(32)     NOT NULL DEFAULT '',
		appDescription         varchar(256)    NOT NULL DEFAULT '',
		PRIMARY KEY (appDId)
	)`)
	if err != nil {
		log.Error(err.Error())
		return err
	}
	log.Info("Created DAI table: ", AppInfoListTable)

	// AppInfoListLocationConstraintsTable Table
	_, err = am.db.Exec(`CREATE TABLE ` + AppInfoListLocationConstraintsTable + ` (
		appDId                 varchar(32)     NOT NULL DEFAULT '',
		area                   varchar(1024)   NOT NULL DEFAULT '',
		civicAddressElement    varchar(256)    NOT NULL DEFAULT '',
		countryCode            varchar(256)    NOT NULL DEFAULT '',
		FOREIGN KEY (appDId) REFERENCES ` + AppInfoListTable + `(appDId) ON DELETE CASCADE
	)`)
	if err != nil {
		log.Error(err.Error())
		return err
	}
	log.Info("Created DAI table: ", ApplicationLocationAvailabilityTable)

	// AppCharcsTable Table
	_, err = am.db.Exec(`CREATE TABLE ` + AppCharcsTable + ` (
		appDId                 varchar(32)     DEFAULT '',
		memory                 integer         DEFAULT '0',
		storage                integer         DEFAULT '0',
		latency                integer         DEFAULT '0',
		bandwidth              integer         DEFAULT '0',
		serviceCont            integer         DEFAULT '0',
		PRIMARY KEY (appDId),
		FOREIGN KEY (appDId) REFERENCES ` + AppInfoListTable + `(appDId) ON DELETE CASCADE
	)`)
	if err != nil {
		log.Error(err.Error())
		return err
	}
	log.Info("Created DAI table: ", AppCharcsTable)

	return nil
}

// SimulateExistingApplication -- This function simulates an existing onboarded MEC Application on platform. It shall be removed later
func (am *DaiMgr) SimulateExistingApplication() (err error) {
	log.Info("SimulateExistingApplication")

	const (
		//associateDevAppId1    = "associateDevAppId1"
		//callbackReference1    = "http://`docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' 82c1d2d846ce`:8080"
		//appLocationUpdates1   = true
		//appAutoInstantiation1 = false
		appName1        = "onboarded-demo4"
		appProvider1    = "ETSI"
		appDVersion1    = "v0.1.0"
		appDescription1 = "Basic HTTP Ping Pong"

		//associateDevAppId2    = "associateDevAppId2"
		//callbackReference2    = "callbackReference2"
		//appLocationUpdates2   = false
		//appAutoInstantiation2 = false
		appName2        = "appName2"
		appProvider2    = "appProvider2"
		appDVersion2    = "appDVersion2"
		appDescription2 = "appDescription2"
	)

	var ( // Need to take address
		//contextId1               string = "contextId1"
		appDId1         string = "onboarded-demo4"
		appSoftVersion1 string = "v0.1.0"
		//appPackageSource1        string = "appPackageSource1"
		//appInstanceId1_1         string = "appInstanceId1-1"
		//appInstanceId1_2         string = "appInstanceId1-2"
		//appInstanceId1_3         string = "appInstanceId1-3"
		//referenceURI1_1          uri    = "referenceURI1-1"
		//referenceURI1_3          uri    = "referenceURI1-3"
		//area1                       = Polygon{[][][]float32{{{7.420433, 43.729942}, {7.420659, 43.73036}, {7.420621, 43.731045}, {7.420922, 43.73129}}, {{7.420434, 43.729943}, {7.420659, 43.73036}, {7.420621, 43.731045}, {7.420922, 43.73129}}}}
		//civicAddressElement1        = CivicAddressElement{{1, "Value1"}, {10, "Value10"}}
		countryCode1         string = "33"
		memory1              uint32 = 1024
		storage1             uint32 = 1024
		latency1             uint32 = 1024
		bandwidth1           uint32 = 1024
		serviceCont1         uint32 = 0

		//contextId2           string = "contextId2"
		appDId2         string = "appDId2"
		appSoftVersion2 string = "appSoftVersion2"
		//appPackageSource2    string = "appPackageSource2"
		//appInstanceId2_1     string = "appInstanceId2-1"
		//appInstanceId2_2     string = "appInstanceId2-2"
		area2                       = Polygon{[][][]float32{{{7.43166, 43.736156}, {7.431723, 43.736115}, {7.431162, 43.735607}, {7.430685, 43.73518}}}}
		civicAddressElement2        = CivicAddressElement{{2, "Value2"}, {20, "Value20"}}
		countryCode2         string = "countryCode2"
		memory2              uint32 = 1024 * 2
		storage2             uint32 = 1024 * 2
		latency2             uint32 = 1024 * 2
		bandwidth2           uint32 = 1024 * 2
		serviceCont2         uint32 = 0
	)

	// Build a complete AppInfoList data structure
	appLocation1 := make([]LocationConstraintsItem, 1)
	appLocation1[0] = LocationConstraintsItem{nil, nil, &countryCode1}
	appCharcs1 := make([]AppCharcs, 1)
	appCharcs1[0] = AppCharcs{&memory1, &storage1, &latency1, &bandwidth1, &serviceCont1}
	appInfoList1 := AppInfoList{appDId1, appName1, appProvider1, appSoftVersion1, appDVersion1, appDescription1, appLocation1, appCharcs1}
	err = am.CreateAppEntry(appInfoList1)
	if err != nil {
		log.Error(err.Error())
		return err
	}

	// Build another complete dummy AppInfoList data structure
	appLocation2 := make([]LocationConstraintsItem, 2)
	appLocation2[0] = LocationConstraintsItem{&area2, nil, &countryCode2}
	appLocation2[1] = LocationConstraintsItem{nil, &civicAddressElement2, nil}
	appCharcs2 := make([]AppCharcs, 1)
	appCharcs2[0] = AppCharcs{&memory2, &storage2, &latency2, &bandwidth2, &serviceCont2}
	appInfoList2 := AppInfoList{appDId2, appName2, appProvider2, appSoftVersion2, appDVersion2, appDescription2, appLocation2, appCharcs2}
	err = am.CreateAppEntry(appInfoList2)
	if err != nil {
		log.Error(err.Error())
		return err
	}

	log.Info("Created onboarded user application")
	return nil
}

// DeleteTables - Delete all postgis tables
func (am *DaiMgr) DeleteTables() (err error) {
	_ = am.DeleteTable(AppContextTable)
	_ = am.DeleteTable(AppInfoTable)
	_ = am.DeleteTable(UserAppInstanceInfoTable)
	_ = am.DeleteTable(LocationConstraintsTable)
	_ = am.DeleteTable(AppInfoListTable)
	_ = am.DeleteTable(AppCharcsTable)
	_ = am.DeleteTable(AppInfoListLocationConstraintsTable)
	return nil
}

// DeleteTable - Delete postgis table with provided name
func (am *DaiMgr) DeleteTable(tableName string) (err error) {
	_, err = am.db.Exec("DROP TABLE IF EXISTS " + tableName + " CASCADE")
	if err != nil {
		log.Error(err.Error())
		return err
	}
	log.Info("Deleted table: " + tableName)
	return nil
}

// DeleteAppInfoList - Delete DeleteAppContextTable entry
func (am *DaiMgr) DeleteAppInfoList(appDId string) (err error) {
	if profiling {
		profilingTimers["DeleteAppInfoList"] = time.Now()
	}

	// Validate input
	if appDId == "" {
		err = errors.New("Missing appDId")
		return err
	}

	result, err := am.db.Exec(`DELETE FROM `+AppInfoListTable+` WHERE name = ($1)`, appDId)
	if err != nil {
		log.Error(err.Error())
		return err
	}
	rowCnt, err := result.RowsAffected()
	if err != nil {
		log.Error(err.Error())
		return err
	}
	if rowCnt == 0 {
		return errors.New("ContextId not found")
	}

	if profiling {
		now := time.Now()
		log.Debug("DeleteAppInfoList: ", now.Sub(profilingTimers["DeleteAppInfoList"]))
	}
	return nil
}

// DeleteAppContext - Delete all DeleteAppContextTable entries
func (am *DaiMgr) DeleteAllAppInfoList() (err error) {
	if profiling {
		profilingTimers["DeleteAllAppInfoList"] = time.Now()
	}

	_, err = am.db.Exec(`DELETE FROM ` + AppInfoListTable)
	if err != nil {
		log.Error(err.Error())
		return err
	}

	if profiling {
		now := time.Now()
		log.Debug("DeleteAllAppInfoList: ", now.Sub(profilingTimers["DeleteAllAppInfoList"]))
	}
	return nil
}

// DeleteAppContext - Delete DeleteAppContextTable entry
func (am *DaiMgr) DeleteAppContext(appContextId string) (err error) {
	if profiling {
		profilingTimers["DeleteAppContext"] = time.Now()
	}

	// Validate input
	if appContextId == "" {
		err = errors.New("Missing appContextId")
		return err
	}

	result, err := am.db.Exec(`DELETE FROM `+AppContextTable+` WHERE contextId = ($1)`, appContextId)
	if err != nil {
		log.Error(err.Error())
		return err
	}
	rowCnt, err := result.RowsAffected()
	if err != nil {
		log.Error(err.Error())
		return err
	}
	if rowCnt == 0 {
		return errors.New("ContextId not found")
	}

	// TODO Add logic to delete applicationListAppList

	// Notify listener
	am.notifyListener(appContextId)

	if profiling {
		now := time.Now()
		log.Debug("DeleteAppContext: ", now.Sub(profilingTimers["DeleteAppContext"]))
	}

	return nil
}

// DeleteAppContext - Delete all DeleteAppContextTable entries
func (am *DaiMgr) DeleteAllAppContext() (err error) {
	if profiling {
		profilingTimers["DeleteAllAppContext"] = time.Now()
	}

	_, err = am.db.Exec(`DELETE FROM ` + AppContextTable)
	if err != nil {
		log.Error(err.Error())
		return err
	}

	if profiling {
		now := time.Now()
		log.Debug("DeleteAllAppContext: ", now.Sub(profilingTimers["DeleteAllAppContext"]))
	}
	return nil
}

func (am *DaiMgr) CreateAppEntry(appInfoList AppInfoList) (err error) {
	if profiling {
		profilingTimers["CreateAppEntry"] = time.Now()
	}

	// Sanity checks
	if appInfoList.AppDId == "" { // ETSI GS MEC 016 Clause 6.2.2 Type: ApplicationList
		return errors.New("Missing AppDId")
	}
	if appInfoList.AppName == "" { // ETSI GS MEC 016 Clause 6.2.2 Type: ApplicationList
		return errors.New("Missing AppName")
	}
	if appInfoList.AppProvider == "" { // ETSI GS MEC 016 Clause 6.2.2 Type: ApplicationList
		return errors.New("Missing AppProvider")
	}
	if appInfoList.AppSoftVersion == "" { // ETSI GS MEC 016 Clause 6.2.2 Type: ApplicationList
		return errors.New("Missing AppSoftVersion")
	}
	if appInfoList.AppDVersion == "" { // ETSI GS MEC 016 Clause 6.2.2 Type: ApplicationList
		return errors.New("Missing AppDVersion")
	}
	if appInfoList.AppDescription == "" { // ETSI GS MEC 016 Clause 6.2.2 Type: ApplicationList
		return errors.New("Missing AppDescription")
	}
	if len(appInfoList.AppCharcs) > 1 { // ETSI GS MEC 016 Clause 6.2.2 Type: ApplicationList
		return errors.New("Too many entries in AppCharcs")
	}

	// Create AppInfoList entries
	query := `INSERT INTO ` + AppInfoListTable + ` (appDId, appName, appProvider, appSoftVersion, appDVersion, appDescription) VALUES ($1, $2, $3, $4, $5, $6)`
	_, err = am.db.Exec(
		query,
		appInfoList.AppDId,
		appInfoList.AppName,
		appInfoList.AppProvider,
		appInfoList.AppSoftVersion,
		appInfoList.AppDVersion,
		appInfoList.AppDescription,
	)
	if err != nil {
		log.Error(err.Error())
		return err
	}
	// Create AppInfoList.AppLocation entries if any
	if len(appInfoList.AppLocation) != 0 {
		for _, appLocation := range appInfoList.AppLocation {
			// Create AppInfoListLocationConstraintsTable entries
			query = `INSERT INTO ` + AppInfoListLocationConstraintsTable + ` (appDId, area, civicAddressElement, countryCode) VALUES ($1, $2, $3, $4)`
			_, err = am.db.Exec(query, appInfoList.AppDId, convertPolygonToJson(appLocation.Area), convertCivicAddressElementToJson(appLocation.CivicAddressElement), NilToEmptyString(appLocation.CountryCode))
			if err != nil {
				log.Error(err.Error())
				return err
			}
		} // End of 'for' statement
	}
	// Create AppInfoList.AppCharcs entries if any
	if len(appInfoList.AppCharcs) != 0 {
		for _, appCharcs := range appInfoList.AppCharcs {
			// Create AppCharcsTable entries
			query := `INSERT INTO ` + AppCharcsTable + ` (appDId, memory, storage, latency, bandwidth, serviceCont) VALUES ($1, $2, $3, $4, $5, $6)`
			_, err = am.db.Exec(
				query,
				appInfoList.AppDId,
				appCharcs.Memory,
				appCharcs.Storage,
				appCharcs.Latency,
				appCharcs.Bandwidth,
				appCharcs.ServiceCont,
			)
			if err != nil {
				log.Error(err.Error())
				return err
			}
		} // End of 'for' statement
	}

	if profiling {
		now := time.Now()
		log.Debug("CreateAppEntry: ", now.Sub(profilingTimers["CreateAppEntry"]))
	}

	return nil
}

func (am *DaiMgr) GetAppInfoListEntry(appDId string) (appInfoList *AppInfoList, err error) {
	if profiling {
		profilingTimers["GetAppInfoListEntry"] = time.Now()
	}

	// Create AppContext map
	appInfoList = new(AppInfoList)

	var rows *sql.Rows
	rows, err = am.db.Query(
		`SELECT * FROM `+AppInfoListTable+` AS appInfoList `+
			`LEFT JOIN `+AppCharcsTable+` AS appCharcs `+
			`ON (appInfoList.appDId = appCharcs.appDId) `+
			`WHERE appInfoList.appDId = ($1)`, appDId)
	if err != nil {
		log.Error(err.Error())
		return appInfoList, err
	}
	defer rows.Close()

	// Scan results
	appInfoList = new(AppInfoList)
	for rows.Next() {

		appInfoList, err := am.processAppInfoListRecord(rows, appInfoList)
		if err != nil {
			log.Error(err.Error())
			return appInfoList, err
		}
		/////////////////////////////////////////////////////////////////////////
		/*var appCharcsAppDId string
		appCharcs := new(AppCharcs)
		err = rows.Scan(
			&appInfoList.AppDId,
			&appInfoList.AppName,
			&appInfoList.AppProvider,
			&appInfoList.AppSoftVersion,
			&appInfoList.AppDVersion,
			&appInfoList.AppDescription,
			&appCharcsAppDId, // AppCharcs reference
			&appCharcs.memory,
			&appCharcs.storage,
			&appCharcs.latency,
			&appCharcs.bandwidth,
			&appCharcs.serviceCont,
		)
		if err != nil {
			log.Error(err.Error())
			return appInfoList, err
		}
		log.Debug("==> First step: appInfoList: ", appInfoList)
		log.Debug("==> First step: appCharcs: ", appCharcs)
		if appCharcsAppDId != "" {
			appInfoList.AppCharcs = append(appInfoList.AppCharcs, *appCharcs)
		}
		// Update AppLocation
		log.Debug("==> Get appLocation for AppID: ", appInfoList.AppDId)
		var rows_1 *sql.Rows
		rows_1, err = am.db.Query(
			`SELECT * FROM ` + AppInfoListLocationConstraintsTable + `  WHERE appDId = ($1)`,
			appInfoList.AppDId)
		if err != nil {
			log.Error(err.Error())
			return appInfoList, err
		}
		// Scan results
		for rows_1.Next() {
			var applicationLocationAppDId string
			var area string
			var civicAddressElement string
			var countryCode string
			err = rows_1.Scan(
				&applicationLocationAppDId,
				&area,
				&civicAddressElement,
				&countryCode,
			)
			if err != nil {
				log.Error(err.Error())
				return appInfoList, err
			}
			locationItem := new(LocationConstraintsItem)
			log.Debug("==> area: ", area)
			locationItem.Area = convertJsonToPolygon(area)
			log.Debug("==> len(locationItem.Area.Coordinates): ", len(locationItem.Area.Coordinates))
			if len(locationItem.Area.Coordinates) == 0 {
				locationItem.Area = nil
			}
			log.Debug("==> area: ", area)
			locationItem.CivicAddressElement = convertJsonToCivicAddressElement(civicAddressElement)
			if len(*locationItem.CivicAddressElement) == 0 {
				locationItem.CivicAddressElement = nil
			}
			log.Debug("==> civicAddressElement: ", civicAddressElement)
			if countryCode == "" {
				locationItem.CountryCode = nil
			} else {
				locationItem.CountryCode = new(string)
				*locationItem.CountryCode = countryCode
			}
			log.Debug("==> countryCode: ", countryCode)
			log.Debug("==> locationItem: ", locationItem)

			appInfoList.AppLocation = append(appInfoList.AppLocation, *locationItem)
			log.Debug("==>  len(appInfoList.AppLocation): ", len(appInfoList.AppLocation))

		} // End of 'for' statement
		*/
		///////////////////////////////////////////////////////////////////////

		//log.Debug("### *appInfoList", *appInfoList)
		//log.Debug("==> Second step: appInfoList: ", appInfoList)
	} // End of 'for' statement
	if appInfoList == nil {
		err = errors.New("AppInfoList not found")
		return nil, err
	}

	if profiling {
		now := time.Now()
		log.Debug("GetAppInfoListEntry: ", now.Sub(profilingTimers["GetAppInfoListEntry"]))
	}

	return appInfoList, nil
}

func (am *DaiMgr) GetAllAppInfoListEntry() (appInfoList map[string]*AppInfoList, err error) {
	if profiling {
		profilingTimers["GetAllAppInfoListEntry"] = time.Now()
	}

	// Create AppContext map
	appInfoList = make(map[string]*AppInfoList)

	var rows *sql.Rows
	rows, err = am.db.Query(
		`SELECT * FROM ` + AppInfoListTable + ` AS appInfoList ` +
			`LEFT JOIN ` + AppCharcsTable + ` AS appCharcs ` +
			`ON (appInfoList.appDId = appCharcs.appDId)`)
	if err != nil {
		log.Error(err.Error())
		return appInfoList, err
	}
	defer rows.Close()

	// Scan results
	for rows.Next() {

		appInfoListEntry := new(AppInfoList)
		appInfoListEntry, err := am.processAppInfoListRecord(rows, appInfoListEntry)
		if err != nil {
			log.Error(err.Error())
			return appInfoList, err
		}
		/////////////////////////////////////////////////////////////////////////
		/*var appCharcsAppDId string
		appCharcs := new(AppCharcs)
		err = rows.Scan(
			&appInfoListEntry.AppDId,
			&appInfoListEntry.AppName,
			&appInfoListEntry.AppProvider,
			&appInfoListEntry.AppSoftVersion,
			&appInfoListEntry.AppDVersion,
			&appInfoListEntry.AppDescription,
			&appCharcsAppDId, // AppCharcs reference
			&appCharcs.memory,
			&appCharcs.storage,
			&appCharcs.latency,
			&appCharcs.bandwidth,
			&appCharcs.serviceCont,
		)
		if err != nil {
			log.Error(err.Error())
			return appInfoList, err
		}
		log.Debug("==> First step: appInfoListEntry: ", appInfoListEntry)
		log.Debug("==> First step: appCharcs: ", appCharcs)
		if appCharcsAppDId != "" {
			appInfoListEntry.AppCharcs = append(appInfoListEntry.AppCharcs, *appCharcs)
		}
		// Update AppLocation
		log.Debug("==> Get appLocation for AppID: ", appInfoListEntry.AppDId)
		var rows_1 *sql.Rows
		rows_1, err = am.db.Query(
			`SELECT * FROM ` + AppInfoListLocationConstraintsTable + `  WHERE appDId = ($1)`,
			appInfoListEntry.AppDId)
		if err != nil {
			log.Error(err.Error())
			return appInfoList, err
		}
		// Scan results
		for rows_1.Next() {
			var applicationLocationAppDId string
			var area string
			var civicAddressElement string
			var countryCode string
			err = rows_1.Scan(
				&applicationLocationAppDId,
				&area,
				&civicAddressElement,
				&countryCode,
			)
			if err != nil {
				log.Error(err.Error())
				return appInfoList, err
			}
			locationItem := new(LocationConstraintsItem)
			log.Debug("==> area: ", area)
			locationItem.Area = convertJsonToPolygon(area)
			log.Debug("==> len(locationItem.Area.Coordinates): ", len(locationItem.Area.Coordinates))
			if len(locationItem.Area.Coordinates) == 0 {
				locationItem.Area = nil
			}
			log.Debug("==> area: ", area)
			locationItem.CivicAddressElement = convertJsonToCivicAddressElement(civicAddressElement)
			if len(*locationItem.CivicAddressElement) == 0 {
				locationItem.CivicAddressElement = nil
			}
			log.Debug("==> civicAddressElement: ", civicAddressElement)
			if countryCode == "" {
				locationItem.CountryCode = nil
			} else {
				locationItem.CountryCode = new(string)
				*locationItem.CountryCode = countryCode
			}
			log.Debug("==> countryCode: ", countryCode)
			log.Debug("==> locationItem: ", locationItem)

			appInfoListEntry.AppLocation = append(appInfoListEntry.AppLocation, *locationItem)
			log.Debug("==>  len(appInfoListEntry.AppLocation): ", len(appInfoListEntry.AppLocation))

		} // End of 'for' statement
		*/
		///////////////////////////////////////////////////////////////////////
		// Get AppContext entry from UE map (create new entry if not found)
		ac := appInfoList[appInfoListEntry.AppDId]
		if ac == nil {
			ac = appInfoListEntry
			appInfoList[appInfoListEntry.AppDId] = ac
		}

		//log.Debug("### *appInfoList", appInfoList)
		//log.Debug("==> Second step: appInfoList: ", appInfoList)
	} // End of 'for' statement
	if appInfoList == nil {
		err = errors.New("AppInfoList not found")
		return nil, err
	}

	if profiling {
		now := time.Now()
		log.Debug("GetAllAppInfoListEntry: ", now.Sub(profilingTimers["GetAllAppInfoListEntry"]))
	}

	return appInfoList, nil
}

func (am *DaiMgr) CreateAppContext(appContext *AppContext) (app *AppContext, err error) {
	if profiling {
		profilingTimers["CreateAppContext"] = time.Now()
	}

	// Sanity checks
	if appContext == nil {
		return nil, errors.New("CreateAppContext: Invalid input parameters")
	}
	app = appContext
	if app.ContextId != nil { // ETSI GS MEC 016 Clause 6.2.3 Type: AppContext.
		return nil, errors.New("ContextId shall not be set")
	}
	if app.AssociateDevAppId == "" { // ETSI GS MEC 016 Clause 6.2.3 Type: AppContext.
		return nil, errors.New("Missing AssociateDevAppId")
	}
	if len(app.AppInfo.UserAppInstanceInfo) == 0 { // ETSI GS MEC 016 Clause 6.2.3 Type: AppContext.
		return nil, errors.New("Missing at least one UserAppInstanceInfo item")
	}
	for _, item := range app.AppInfo.UserAppInstanceInfo {
		if item.AppInstanceId != nil {
			return nil, errors.New("UserAppInstanceInfo.AppInstanceId shall not be set")
		}
		if item.ReferenceURI != nil {
			return nil, errors.New("UserAppInstanceInfo.ReferenceURI shall not be set")
		}
		if len(item.AppLocation) > 1 {
			return nil, errors.New("Only one AppLocation item expected")
		}
	} // End of 'for' statement

	// TODO Add logic to instanciate applicationListAppList

	// Create the AppContext
	app.ContextId = new(string)
	*app.ContextId = uuid.New().String()[0:32]
	for i := range app.AppInfo.UserAppInstanceInfo {
		app.AppInfo.UserAppInstanceInfo[i].AppInstanceId = new(string)
		*app.AppInfo.UserAppInstanceInfo[i].AppInstanceId = uuid.New().String()[0:32]
		app.AppInfo.UserAppInstanceInfo[i].ReferenceURI = new(Uri)
		*app.AppInfo.UserAppInstanceInfo[i].ReferenceURI = Uri(strings.TrimSpace(os.Getenv("MEEP_HOST_URL")) + *app.AppInfo.UserAppInstanceInfo[i].AppInstanceId + "/")
	} // End of 'for' statement
	log.Debug("CreateAppContext: app: ", app)

	// Create AppContext entries
	query := `INSERT INTO ` + AppContextTable + ` (contextId, associateDevAppId, callbackReference, appLocationUpdates, appAutoInstantiation, appDId) VALUES ($1, $2, $3, $4, $5, $6)`
	_, err = am.db.Exec(query, *app.ContextId, app.AssociateDevAppId, app.CallbackReference, app.AppLocationUpdates, app.AppAutoInstantiation, app.AppInfo.AppDId)
	if err != nil {
		log.Error(err.Error())
		return nil, err
	}
	// Create AppInfo entries
	query = `INSERT INTO ` + AppInfoTable + ` (appDId, appName, appProvider, appSoftVersion, appDVersion, appDescription, appPackageSource) VALUES ($1, $2, $3, $4, $5, $6, $7)`
	_, err = am.db.Exec(
		query,
		NilToEmptyString(app.AppInfo.AppDId),
		app.AppInfo.AppName,
		app.AppInfo.AppProvider,
		NilToEmptyString(app.AppInfo.AppSoftVersion),
		app.AppInfo.AppDVersion,
		app.AppInfo.AppDescription,
		NilToEmptyUri(app.AppInfo.AppPackageSource),
	)
	if err != nil {
		log.Error(err.Error())
		return nil, err
	}

	// Create UserAppInstanceInfo entries
	for _, userAppInstanceInfo := range app.AppInfo.UserAppInstanceInfo {
		query = `INSERT INTO ` + UserAppInstanceInfoTable + ` (appDId, appInstanceId, referenceURI) VALUES ($1, $2, $3)`
		_, err = am.db.Exec(query, *app.AppInfo.AppDId, userAppInstanceInfo.AppInstanceId, userAppInstanceInfo.ReferenceURI)
		if err != nil {
			log.Error(err.Error())
			return nil, err
		}
		// Create AppLocation entries
		if userAppInstanceInfo.AppLocation != nil {
			//log.Debug("==> userAppInstanceInfo.AppLocation")
			for _, appLocation := range userAppInstanceInfo.AppLocation {
				query = `INSERT INTO ` + LocationConstraintsTable + ` (appInstanceId, appDId, area, civicAddressElement, countryCode) VALUES ($1, $2, $3, $4, $5)`
				_, err = am.db.Exec(query, userAppInstanceInfo.AppInstanceId, *app.AppInfo.AppDId, convertPolygonToJson(appLocation.Area), convertCivicAddressElementToJson(appLocation.CivicAddressElement), NilToEmptyString(appLocation.CountryCode))
				if err != nil {
					log.Error(err.Error())
					return nil, err
				}
			} // End of 'for' statement
		}
	} // End of 'for' statement

	// Notify listener
	am.notifyListener(*app.ContextId)

	if profiling {
		now := time.Now()
		log.Debug("CreateAppContext: ", now.Sub(profilingTimers["CreateAppContext"]))
	}

	return app, nil
}

func (am *DaiMgr) PutAppContext(appContext AppContext) (err error) {
	if profiling {
		profilingTimers["PutAppContext"] = time.Now()
	}

	// Sanity checks
	if appContext.ContextId == nil || *appContext.ContextId == "" { // ETSI GS MEC 016 Clause 6.2.3 Type: AppContext.
		return errors.New("ContextId shall be set")
	}
	log.Debug("PutAppContext: appContext: ", appContext)

	// Retrieve the existing AppContext
	curAppContext, err := am.GetAppContext(*appContext.ContextId)
	if err != nil {
		return errors.New("ContextId not found")
	}
	log.Debug("PutAppContext: curAppContext: ", curAppContext)

	// Update the curAppContext
	update := false
	if curAppContext.CallbackReference != appContext.CallbackReference {
		curAppContext.CallbackReference = appContext.CallbackReference
		update = true
	}
	// TODO
	/*for _, item := range appContext.AppInfo.UserAppInstanceInfo {
		// Find the entry in curAppContext
		for j := range curAppContext.AppInfo.UserAppInstanceInfo {
			if curAppContext.AppInfo.UserAppInstanceInfo[j].AppInstanceId == item.AppInstanceId && curAppContext.AppInfo.UserAppInstanceInfo[j].ReferenceURI == item.ReferenceURI {
				curAppContext.AppInfo.UserAppInstanceInfo[j].AppLocation = item.AppLocation
				update = true // TODO Check if some AppLocation have changed insted of forcing the update
			}
		} // End of 'for' statement
	}*/ // End of 'for' statement

	if update {
		query := `UPDATE ` + AppContextTable + ` SET callbackReference = ($1) WHERE contextId = ($2)`
		result, err := am.db.Exec(query, curAppContext.CallbackReference, *curAppContext.ContextId)
		if err != nil {
			log.Error(err.Error())
			return err
		}
		rowCnt, err := result.RowsAffected()
		if err != nil {
			log.Fatal(err)
			return err
		}
		if rowCnt == 0 {
			return errors.New("Failed to update recird")
		}
		// TODO Add logic to instanciate applicationListAppList

		// Notify listener
		am.notifyListener(*curAppContext.ContextId)
	}

	if profiling {
		now := time.Now()
		log.Debug("PutAppContext: ", now.Sub(profilingTimers["PutAppContext"]))
	}

	return nil
}

func (am *DaiMgr) GetAllAppList() (jsonString string, err error) {
	if profiling {
		profilingTimers["GetAllAppList"] = time.Now()
	}

	jsonString = ""

	var rows *sql.Rows
	rows, err = am.db.Query(
		`SELECT * FROM ` + AppInfoTable + ` AS appContext ` +
			`LEFT JOIN ` + LocationConstraintsTable + ` AS locationConstraints ` +
			`ON (appContext.appDId = locationConstraints.appDId) ` +
			`LEFT JOIN ` + AppCharcsTable + ` AS appCharts ` +
			`ON (appContext.appDId = appCharts.appDId)`)
	if err != nil {
		log.Error(err.Error())
		return jsonString, err
	}
	defer rows.Close()

	// Fill ApplicationList and convert into JSon to retur the results

	if profiling {
		now := time.Now()
		log.Debug("GetAppList: ", now.Sub(profilingTimers["GetAppList"]))
	}

	return jsonString, nil
}

func (am *DaiMgr) GetAppContext(contextId string) (appContext *AppContext, err error) {
	if profiling {
		profilingTimers["GetAppContext"] = time.Now()
	}

	// Create AppContext map
	appContext = new(AppContext)

	var rows *sql.Rows
	rows, err = am.db.Query(
		`SELECT * FROM `+AppContextTable+` AS appContext `+
			`LEFT JOIN `+AppInfoTable+` AS appInfo `+
			`ON (appContext.appDId = appInfo.appDId) `+
			`LEFT JOIN `+UserAppInstanceInfoTable+` AS userAppInstanceInfo `+
			`ON (userAppInstanceInfo.appDId = appInfo.appDId) `+
			`WHERE appContext.contextId = ($1)`, contextId)
	if err != nil {
		log.Error(err.Error())
		return appContext, err
	}
	defer rows.Close()

	// Scan results
	appInfoEntry := new(AppInfo)
	//log.Debug("==> Before loop: len(appContext.AppInfo.UserAppInstanceInfo): ", len(appContext.AppInfo.UserAppInstanceInfo))
	for rows.Next() {

		userAppInstanceInfoItem := new(UserAppInstanceInfoItem)
		appContext, appInfoEntry, userAppInstanceInfoItem, err := am.processAppContextRecord(rows, appContext, appInfoEntry, userAppInstanceInfoItem)
		if err != nil {
			log.Error(err.Error())
			return appContext, err
		}
		/////////////////////////////////////////////////////////////////////////
		/*var userAppInstanceInfoAppDId string
		userAppInstanceInfoItem := new(UserAppInstanceInfoItem)
		err = rows.Scan(
			&appContext.ContextId,
			&appContext.AssociateDevAppId,
			&appContext.CallbackReference,
			&appContext.AppLocationUpdates,
			&appContext.AppAutoInstantiation,
			&appInfoEntry.AppDId, // AppContext.AppInfo reference
			&appInfoEntry.AppDId, // AppInfo reference
			&appInfoEntry.AppName,
			&appInfoEntry.AppProvider,
			&appInfoEntry.AppSoftVersion,
			&appInfoEntry.AppDVersion,
			&appInfoEntry.AppDescription,
			&appInfoEntry.AppPackageSource,
			&userAppInstanceInfoAppDId, // UserAppInstanceInfo reference
			&userAppInstanceInfoItem.AppInstanceId,
			&userAppInstanceInfoItem.ReferenceURI,
		)
		if err != nil {
			log.Error(err.Error())
			return appContext, err
		}
		// log.Debug("==> First step: appContext: ", appContext)
		// log.Debug("==> First step: userAppInstanceInfoItem: ", userAppInstanceInfoItem)
		// log.Debug("==> First step: len(appContext.AppInfo.UserAppInstanceInfo): ", len(appContext.AppInfo.UserAppInstanceInfo))
		// Update AppLocation
		//log.Debug("==> Get locationConstraints for AppID: ", userAppInstanceInfoAppDId, " and AppInstanceId ", *userAppInstanceInfoItem.AppInstanceId)
		var rows_1 *sql.Rows
		rows_1, err = am.db.Query(
			`SELECT * FROM `+LocationConstraintsTable+`  WHERE appDId = ($1) AND appInstanceId = ($2)`,
			userAppInstanceInfoAppDId, *userAppInstanceInfoItem.AppInstanceId)
		if err != nil {
			log.Error(err.Error())
			return appContext, err
		}
		// Scan results
		for rows_1.Next() {
			var applicationLocationConstraintsAppDId string
			var applicationLocationConstraintsAppInstanceId string
			var area string
			var civicAddressElement string
			var countryCode string
			err = rows_1.Scan(
				&applicationLocationConstraintsAppDId,
				&applicationLocationConstraintsAppInstanceId,
				&area,
				&civicAddressElement,
				&countryCode,
			)
			if err != nil {
				log.Error(err.Error())
				return appContext, err
			}
			locationConstraintsItem := new(LocationConstraintsItem)
			log.Debug("==> area: ", area)
			locationConstraintsItem.Area = convertJsonToPolygon(area)
			log.Debug("==> len(locationConstraintsItem.Area.Coordinates): ", len(locationConstraintsItem.Area.Coordinates))
			if len(locationConstraintsItem.Area.Coordinates) == 0 {
				locationConstraintsItem.Area = nil
			}
			log.Debug("==> area: ", area)
			locationConstraintsItem.CivicAddressElement = convertJsonToCivicAddressElement(civicAddressElement)
			if len(*locationConstraintsItem.CivicAddressElement) == 0 {
				locationConstraintsItem.CivicAddressElement = nil
			}
			log.Debug("==> civicAddressElement: ", civicAddressElement)
			locationConstraintsItem.CountryCode = new(string)
			*locationConstraintsItem.CountryCode = countryCode
			log.Debug("==> countryCode: ", countryCode)
			log.Debug("==> locationConstraintsItem: ", locationConstraintsItem)

			userAppInstanceInfoItem.AppLocation = append(userAppInstanceInfoItem.AppLocation, *locationConstraintsItem)
			log.Debug("==>  len(userAppInstanceInfoItem.AppLocation): ", len(userAppInstanceInfoItem.AppLocation))

		} // End of 'for' statement
		///////////////////////////////////////////////////////////////////////
		*/
		//log.Debug("### *appContext", *appContext)
		//log.Debug("### *appInfoEntry", *appInfoEntry)
		//log.Debug("### *userAppInstanceInfoItem", *userAppInstanceInfoItem)
		// Update UserAppInstanceInfo
		//log.Debug("==> len(appContext.AppInfo.UserAppInstanceInfo) (before): ", len(appInfoEntry.UserAppInstanceInfo))
		appInfoEntry.UserAppInstanceInfo = append(appInfoEntry.UserAppInstanceInfo, *userAppInstanceInfoItem)
		//log.Debug("==> len(appContext.AppInfo.UserAppInstanceInfo) (after): ", len(appInfoEntry.UserAppInstanceInfo))
		//log.Debug("==> Second step: appContextEntry: ", appContext)
	} // End of 'for' statement
	if appContext.ContextId == nil {
		err = errors.New("AppContext not found")
		return nil, err
	}
	//log.Debug("<<< GetAppContext: *appInfoEntry: ", *appInfoEntry)
	appContext.AppInfo = *appInfoEntry

	if profiling {
		now := time.Now()
		log.Debug("GetAppContext: ", now.Sub(profilingTimers["GetAppContext"]))
	}

	return appContext, nil
}

func (am *DaiMgr) GetAllAppContext() (appContext map[string]*AppContext, err error) {
	if profiling {
		profilingTimers["GetAllAppContext"] = time.Now()
	}

	// Create AppContext map
	appContext = make(map[string]*AppContext)

	var rows *sql.Rows
	rows, err = am.db.Query(
		`SELECT * FROM ` + AppContextTable + ` AS appContext ` +
			`LEFT JOIN ` + AppInfoTable + ` AS appInfo ` +
			`ON (appContext.appDId = appInfo.appDId) ` +
			`LEFT JOIN ` + UserAppInstanceInfoTable + ` AS userAppInstanceInfo ` +
			`ON (userAppInstanceInfo.appDId = appInfo.appDId)`)
	if err != nil {
		log.Error(err.Error())
		return appContext, err
	}
	defer rows.Close()

	// Scan results
	for rows.Next() {
		appContextEntry := new(AppContext)
		appInfoEntry := new(AppInfo)

		userAppInstanceInfoItem := new(UserAppInstanceInfoItem)
		appContextEntry, appInfoEntry, userAppInstanceInfoItem, err := am.processAppContextRecord(rows, appContextEntry, appInfoEntry, userAppInstanceInfoItem)
		if err != nil {
			log.Error(err.Error())
			return appContext, err
		}
		appContextEntry.AppInfo = *appInfoEntry
		// Get AppContext entry from UE map (create new entry if not found)
		ac := appContext[*appContextEntry.ContextId]
		if ac == nil {
			ac = appContextEntry
			appContext[*appContextEntry.ContextId] = ac
		}
		/*
			//////////////////////////////////////////////////////////////////////
			var userAppInstanceInfoAppDId string
			userAppInstanceInfoItem := new(UserAppInstanceInfoItem)
			err = rows.Scan(
				&appContextEntry.ContextId,
				&appContextEntry.AssociateDevAppId,
				&appContextEntry.CallbackReference,
				&appContextEntry.AppLocationUpdates,
				&appContextEntry.AppAutoInstantiation,
				&appInfoEntry.AppDId, // AppContext.AppInfo reference
				&appInfoEntry.AppDId, // AppInfo reference
				&appInfoEntry.AppName,
				&appInfoEntry.AppProvider,
				&appInfoEntry.AppSoftVersion,
				&appInfoEntry.AppDVersion,
				&appInfoEntry.AppDescription,
				&appInfoEntry.AppPackageSource,
				&userAppInstanceInfoAppDId, // UserAppInstanceInfo reference
				&userAppInstanceInfoItem.AppInstanceId,
				&userAppInstanceInfoItem.ReferenceURI,
			)
			if err != nil {
				log.Error(err.Error())
				return appContext, err
			}
			appContextEntry.AppInfo = *appInfoEntry

			// Get AppContext entry from UE map (create new entry if not found)
			ac := appContext[*appContextEntry.ContextId]
			if ac == nil {
				ac = appContextEntry
				appContext[*appContextEntry.ContextId] = ac
			}

			// Update AppLocation
			//log.Debug("==> Get locationConstraints for AppID: ", userAppInstanceInfoAppDId, " and AppInstanceId ", *userAppInstanceInfoItem.AppInstanceId)
			var rows_1 *sql.Rows
			rows_1, err = am.db.Query(
				`SELECT * FROM `+LocationConstraintsTable+`  WHERE appDId = ($1) AND appInstanceId = ($2)`,
				userAppInstanceInfoAppDId, *userAppInstanceInfoItem.AppInstanceId)
			if err != nil {
				log.Error(err.Error())
				return appContext, err
			}
			// Scan results
			for rows_1.Next() {
				var applicationLocationConstraintsAppDId string
				var applicationLocationConstraintsAppInstanceId string
				var area string
				var civicAddressElement string
				var countryCode string
				err = rows_1.Scan(
					&applicationLocationConstraintsAppDId,
					&applicationLocationConstraintsAppInstanceId,
					&area,
					&civicAddressElement,
					&countryCode,
				)
				if err != nil {
					log.Error(err.Error())
					return appContext, err
				}
				locationConstraintsItem := new(LocationConstraintsItem)
				log.Debug("==> area: ", area)
				locationConstraintsItem.Area = convertJsonToPolygon(area)
				log.Debug("==> len(locationConstraintsItem.Area.Coordinates): ", len(locationConstraintsItem.Area.Coordinates))
				if len(locationConstraintsItem.Area.Coordinates) == 0 {
					locationConstraintsItem.Area = nil
				}
				locationConstraintsItem.CivicAddressElement = convertJsonToCivicAddressElement(civicAddressElement)
				if len(*locationConstraintsItem.CivicAddressElement) == 0 {
					locationConstraintsItem.CivicAddressElement = nil
				}
				log.Debug("==> civicAddressElement: ", civicAddressElement)
				locationConstraintsItem.CountryCode = new(string)
				*locationConstraintsItem.CountryCode = countryCode
				log.Debug("==> countryCode: ", countryCode)
				log.Debug("==> locationConstraintsItem: ", locationConstraintsItem)

				userAppInstanceInfoItem.AppLocation = append(userAppInstanceInfoItem.AppLocation, *locationConstraintsItem)

			} // End of 'for' statement
			///////////////////////////////////////////////////////////////////////
		*/
		//log.Debug("### *appContextEntry", *appContextEntry)
		//log.Debug("### *appInfoEntry", *appInfoEntry)
		//log.Debug("### *userAppInstanceInfoItem", *userAppInstanceInfoItem)
		// Update UserAppInstanceInfo
		//log.Debug("==> len(appContext[*appContextEntry.ContextId].AppInfo.UserAppInstanceInfo) (before): ", len(appContext[*appContextEntry.ContextId].AppInfo.UserAppInstanceInfo))
		appContext[*appContextEntry.ContextId].AppInfo.UserAppInstanceInfo = append(appContext[*appContextEntry.ContextId].AppInfo.UserAppInstanceInfo, *userAppInstanceInfoItem)
		//log.Debug("==> len(appContext[*appContextEntry.ContextId].AppInfo.UserAppInstanceInfo) (after): ", len(appContext[*appContextEntry.ContextId].AppInfo.UserAppInstanceInfo))
		//log.Debug("==> appContext[*appContextEntry.ContextId]: ", appContext[*appContextEntry.ContextId])
	} // End of 'for' statement
	err = rows.Err()
	if err != nil {
		log.Error(err)
	}

	if profiling {
		now := time.Now()
		log.Debug("GetAllAppContext: ", now.Sub(profilingTimers["GetAllAppContext"]))
	}

	return appContext, nil
}

/////////////////////////////////////////////////////////////////////////

func (am *DaiMgr) processAppInfoListRecord(rows *sql.Rows, appInfoListEntry *AppInfoList) (*AppInfoList, error) {
	var appCharcsAppDId sql.NullString
	appCharcs := new(AppCharcs)
	err := rows.Scan(
		&appInfoListEntry.AppDId,
		&appInfoListEntry.AppName,
		&appInfoListEntry.AppProvider,
		&appInfoListEntry.AppSoftVersion,
		&appInfoListEntry.AppDVersion,
		&appInfoListEntry.AppDescription,
		&appCharcsAppDId, // AppCharcs reference
		&appCharcs.Memory,
		&appCharcs.Storage,
		&appCharcs.Latency,
		&appCharcs.Bandwidth,
		&appCharcs.ServiceCont,
	)
	if err != nil {
		log.Error(err.Error())
		return appInfoListEntry, err
	}
	//log.Debug("==> First step: appInfoList: ", appInfoListEntry)
	//log.Debug("==> First step: appCharcs: ", appCharcs)
	if appCharcsAppDId.Valid {
		appInfoListEntry.AppCharcs = append(appInfoListEntry.AppCharcs, *appCharcs)
	}
	// Update AppLocation
	//log.Debug("==> Get appLocation for AppID: ", appInfoListEntry.AppDId)
	var rows_1 *sql.Rows
	rows_1, err = am.db.Query(
		`SELECT * FROM `+AppInfoListLocationConstraintsTable+`  WHERE appDId = ($1)`,
		appInfoListEntry.AppDId)
	if err != nil {
		log.Error(err.Error())
		return appInfoListEntry, err
	}
	// Scan results
	for rows_1.Next() {
		var applicationLocationAppDId string
		var area string
		var civicAddressElement string
		var countryCode string
		err = rows_1.Scan(
			&applicationLocationAppDId,
			&area,
			&civicAddressElement,
			&countryCode,
		)
		if err != nil {
			log.Error(err.Error())
			return appInfoListEntry, err
		}
		locationItem := new(LocationConstraintsItem)
		//log.Debug("==> area: ", area)
		locationItem.Area = convertJsonToPolygon(area)
		//log.Debug("==> len(locationItem.Area.Coordinates): ", len(locationItem.Area.Coordinates))
		if len(locationItem.Area.Coordinates) == 0 {
			locationItem.Area = nil
		}
		//log.Debug("==> area: ", area)
		locationItem.CivicAddressElement = convertJsonToCivicAddressElement(civicAddressElement)
		if len(*locationItem.CivicAddressElement) == 0 {
			locationItem.CivicAddressElement = nil
		}
		//log.Debug("==> civicAddressElement: ", civicAddressElement)
		if countryCode == "" {
			locationItem.CountryCode = nil
		} else {
			locationItem.CountryCode = new(string)
			*locationItem.CountryCode = countryCode
		}
		//log.Debug("==> countryCode: ", countryCode)
		//log.Debug("==> locationItem: ", locationItem)

		appInfoListEntry.AppLocation = append(appInfoListEntry.AppLocation, *locationItem)
		//log.Debug("==>  len(appInfoListEntry.AppLocation): ", len(appInfoListEntry.AppLocation))

	} // End of 'for' statement

	return appInfoListEntry, err
}

/////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////
func (am *DaiMgr) processAppContextRecord(rows *sql.Rows, appContext *AppContext, appInfoEntry *AppInfo, userAppInstanceInfoItem *UserAppInstanceInfoItem) (*AppContext, *AppInfo, *UserAppInstanceInfoItem, error) {
	var userAppInstanceInfoAppDId string
	//userAppInstanceInfoItem := new(UserAppInstanceInfoItem)
	err := rows.Scan(
		&appContext.ContextId,
		&appContext.AssociateDevAppId,
		&appContext.CallbackReference,
		&appContext.AppLocationUpdates,
		&appContext.AppAutoInstantiation,
		&appInfoEntry.AppDId, // AppContext.AppInfo reference
		&appInfoEntry.AppDId, // AppInfo reference
		&appInfoEntry.AppName,
		&appInfoEntry.AppProvider,
		&appInfoEntry.AppSoftVersion,
		&appInfoEntry.AppDVersion,
		&appInfoEntry.AppDescription,
		&appInfoEntry.AppPackageSource,
		&userAppInstanceInfoAppDId, // UserAppInstanceInfo reference
		&userAppInstanceInfoItem.AppInstanceId,
		&userAppInstanceInfoItem.ReferenceURI,
	)
	if err != nil {
		log.Error(err.Error())
		return nil, nil, nil, err
	}
	appInfoEntry.AppDId = EmptyToNilString(appInfoEntry.AppDId)
	appInfoEntry.AppSoftVersion = EmptyToNilString(appInfoEntry.AppSoftVersion)
	appInfoEntry.AppPackageSource = EmptyToNilUri(appInfoEntry.AppPackageSource)
	userAppInstanceInfoItem.AppInstanceId = EmptyToNilString(userAppInstanceInfoItem.AppInstanceId)
	userAppInstanceInfoItem.ReferenceURI = EmptyToNilUri(userAppInstanceInfoItem.ReferenceURI)
	// log.Debug("==> First step: appContext: ", appContext)
	// log.Debug("==> First step: userAppInstanceInfoItem: ", userAppInstanceInfoItem)
	// log.Debug("==> First step: len(appContext.AppInfo.UserAppInstanceInfo): ", len(appContext.AppInfo.UserAppInstanceInfo))
	// Update AppLocation
	//log.Debug("==> Get locationConstraints for AppID: ", userAppInstanceInfoAppDId, " and AppInstanceId ", *userAppInstanceInfoItem.AppInstanceId)
	var rows_1 *sql.Rows
	rows_1, err = am.db.Query(
		`SELECT * FROM `+LocationConstraintsTable+`  WHERE appDId = ($1) AND appInstanceId = ($2)`,
		userAppInstanceInfoAppDId, *userAppInstanceInfoItem.AppInstanceId)
	if err != nil {
		log.Error(err.Error())
		return nil, nil, nil, err
	}
	// Scan results
	for rows_1.Next() {
		var applicationLocationConstraintsAppDId string
		var applicationLocationConstraintsAppInstanceId string
		var area string
		var civicAddressElement string
		var countryCode string
		err = rows_1.Scan(
			&applicationLocationConstraintsAppDId,
			&applicationLocationConstraintsAppInstanceId,
			&area,
			&civicAddressElement,
			&countryCode,
		)
		if err != nil {
			log.Error(err.Error())
			return nil, nil, nil, err
		}
		locationConstraintsItem := new(LocationConstraintsItem)
		//log.Debug("==> area: ", area)
		locationConstraintsItem.Area = convertJsonToPolygon(area)
		//log.Debug("==> len(locationConstraintsItem.Area.Coordinates): ", len(locationConstraintsItem.Area.Coordinates))
		if len(locationConstraintsItem.Area.Coordinates) == 0 {
			locationConstraintsItem.Area = nil
		}
		//log.Debug("==> area: ", area)
		locationConstraintsItem.CivicAddressElement = convertJsonToCivicAddressElement(civicAddressElement)
		if len(*locationConstraintsItem.CivicAddressElement) == 0 {
			locationConstraintsItem.CivicAddressElement = nil
		}
		//log.Debug("==> civicAddressElement: ", civicAddressElement)
		if countryCode == "" {
			locationConstraintsItem.CountryCode = nil
		} else {
			locationConstraintsItem.CountryCode = new(string)
			*locationConstraintsItem.CountryCode = countryCode
		}
		//log.Debug("==> countryCode: ", countryCode)
		//log.Debug("==> locationConstraintsItem: ", locationConstraintsItem)

		userAppInstanceInfoItem.AppLocation = append(userAppInstanceInfoItem.AppLocation, *locationConstraintsItem)
		//log.Debug("==>  len(userAppInstanceInfoItem.AppLocation): ", len(userAppInstanceInfoItem.AppLocation))
	} // End of 'for' statement

	return appContext, appInfoEntry, userAppInstanceInfoItem, err
}

///////////////////////////////////////////////////////////////////////
