//package cwtch

package main

// //Needed to invoke C.free
// #include <stdlib.h>
import "C"

import (
	"cwtch.im/cwtch/event"
	"cwtch.im/cwtch/model/attr"
	"cwtch.im/cwtch/model/constants"
	"cwtch.im/cwtch/settings"
	"encoding/json"
	"fmt"
	"git.openprivacy.ca/cwtch.im/autobindings/utils"
	"git.openprivacy.ca/openprivacy/connectivity/tor"
	"git.openprivacy.ca/openprivacy/log"
	"os"
	"os/user"
	path "path/filepath"
	"runtime"
	"runtime/pprof"
	"strings"
	"strconv"
	"time"
	mrand "math/rand"
	"crypto/rand"
	"encoding/base64"
	"unsafe"
	_ "git.openprivacy.ca/openprivacy/go-sqlcipher"
	"cwtch.im/cwtch/app"
	"git.openprivacy.ca/openprivacy/connectivity"
	"sync"

	"cwtch.im/cwtch/functionality/inter"
"cwtch.im/cwtch/functionality/servers"
"cwtch.im/cwtch/functionality/filesharing"
"git.openprivacy.ca/cwtch.im/autobindings/experiments/server_hosting"
"git.openprivacy.ca/cwtch.im/autobindings/experiments/group_manager"
"cwtch.im/cwtch/model"
"cwtch.im/cwtch/functionality/hybrid"

)

// supplied by make
var (
	buildVer  string
	buildDate string
)

var application app.Application
var globalAppDir string
var globalTorPath string
var eventHandler *utils.EventHandler
var globalACN connectivity.ProxyACN

// Dangerous function. Should only be used as documented in `MEMORY.md`
//
//export c_FreePointer
func c_FreePointer(ptr *C.char) {
	C.free(unsafe.Pointer(ptr))
}

//export c_Started
func c_Started() C.int {
	return C.int(Started())
}

// Started returns 1 if application is initialized and 0 if it is null
func Started() int {
	if application == nil {
		return 0
	}
	return 1
}

//export c_StartCwtch
func c_StartCwtch(dir_c *C.char, len C.int, tor_c *C.char, torLen C.int) C.int {
	applicationDirectory := C.GoStringN(dir_c, len)
	torDirectory := C.GoStringN(tor_c, torLen)
	return C.int(StartCwtch(applicationDirectory, torDirectory))
}

// StartCwtch starts cwtch in the library and initlaizes all data structures
//
//		 GetAppbusEvents is always safe to use
//	  the rest of functions are unsafe until the CwtchStarted event has been received indicating StartCwtch has completed
//	  returns:
//	    message: CwtchStarted when start up is complete and app is safe to use
//	             CwtchStartError message when start up fails (includes event.Error data field)
func StartCwtch(appDir string, torPath string) int {
	if logfile := os.Getenv("LOG_FILE"); logfile != "" {
		filelog, err := log.NewFile(log.LevelInfo, logfile)
		if err == nil {
			filelog.SetUseColor(false)
			log.SetStd(filelog)
		} else {
			// not so likely to be seen since we're usually creating file log in situations we can't access console logs...
			log.Errorf("could not create file log: %v\n", err)
		}
	}
	if runtime.GOOS == "android" {
		log.SetUseColor(false)
	}
	log.SetLevel(log.LevelInfo)
	if logLevel := os.Getenv("LOG_LEVEL"); strings.ToLower(logLevel) == "debug" {
		log.SetLevel(log.LevelDebug)
	}
	log.Infof("StartCwtch(...)")
	log.Debugf("builddate: %v buildver: %v", buildDate, buildVer)

	// Quick hack check that we're being called with the correct params
	// On android a stale worker could be calling us with "last apps" directory. Best to abort fast so the app can make a new worker
	if runtime.GOOS == "android" {
		fh, err := os.Open(torPath)
		if err != nil {
			log.Errorf("%v", err)
			log.Errorf("failed to stat tor, skipping StartCwtch(). potentially normal if the app was reinstalled or the device was restarted; this workorder should get canceled soon")
			return 1
		}
		_ = fh.Close()
	}
	go _startCwtch(appDir, torPath)
	return 0
}

func _startCwtch(appDir string, torPath string) {
	log.Infof("application: %v eventHandler: %v", application, eventHandler)

	if application != nil {
		log.Infof("_startCwtch detected existing application; resuming instead of relaunching")
		ReconnectCwtchForeground()
		return
	}

	log.Infof("Creating new EventHandler()")
	eventHandler = utils.NewEventHandler()


	// Exclude Tapir wire Messages
	//(We need a TRACE level)
	log.ExcludeFromPattern("service.go")

	// Environment variables don't get '~' expansion so if CWTCH_DIR was set, it likely needs manual handling
	usr, _ := user.Current()
	homeDir := usr.HomeDir
	if appDir == "~" {
		appDir = homeDir
	} else if strings.HasPrefix(appDir, "~/") {
		appDir = path.Join(homeDir, appDir[2:])
	}

	// Ensure that the application directory exists...and then initialize settings..
	err := os.MkdirAll(appDir, 0700)
	if err != nil {
		log.Errorf("Error creating appDir %v: %v\n", appDir, err)
		eventHandler.Push(event.NewEventList(CwtchStartError, event.Error, fmt.Sprintf("Error creating appDir %v: %v", appDir, err)))
		return
	}

	log.Infof("Loading Cwtch Directory %v and tor path: %v", appDir, torPath)
	settings.InitGlobalSettingsFile(appDir, app.DefactoPasswordForUnencryptedProfiles)

	log.Infof("making directory %v", appDir)
	err = os.MkdirAll(path.Join(appDir, "tor"), 0700)

	if err != nil {
		log.Errorf("error creating tor data directory: %v. Aborting app start up", err)
		eventHandler.Push(event.NewEventList(CwtchStartError, event.Error, fmt.Sprintf("Error connecting to Tor: %v", err)))
		return
	}

	// Allow the user of a custom torrc
	globalAppDir = appDir
	globalTorPath = torPath
	settingsFile := app.LoadAppSettings(appDir)
	// start with an Error ACN
	erracn := connectivity.NewErrorACN(fmt.Errorf("initializing tor"))
	globalACN = connectivity.NewProxyACN(&erracn)
	application = app.NewApp(&globalACN, appDir, settingsFile)

	// Subscribe to all App Events...
	eventHandler.HandleApp(application)

	// FIXME: This code exists to allow the Splash Screen test in the new UI integration tests to pass
	// it doesn't actually fix the problem in theory, and we should get around to ensuring that application
	// is safe to access even if shutdown is called concurrently...
	if application == nil {
		log.Errorf("startCwtch: primary application object has gone away. assuming application is closing.")
		return
	}
	// Send global settings to the UI...
	globalSettings := application.ReadSettings()
	settingsJson, _ := json.Marshal(globalSettings)
	application.GetPrimaryBus().Publish(event.NewEvent(settings.UpdateGlobalSettings, map[event.Field]string{event.Data: string(settingsJson)}))
	log.Infof("libcwtch-go application launched")
	application.GetPrimaryBus().Publish(event.NewEvent(settings.CwtchStarted, map[event.Field]string{}))
	application.QueryACNVersion()

	
	serverExperiment = server_hosting.Init(&globalACN, appDir)
	eventHandler.AddModule(serverExperiment)
	serverExperiment.Enable(application, &globalACN)

	groupManagerExperiment = group_manager.Init(&globalACN, appDir)
	eventHandler.AddModule(groupManagerExperiment)
	groupManagerExperiment.Enable(application, &globalACN)


	
	// Finally attempt to set up a proper Tor
	// Note: ResetTor launches an internal goroutine so this is non-blocking...
	ResetTor()
}

// the pointer returned from this function **must** be freed using c_Free
//
//export c_GetAppBusEvent
func c_GetAppBusEvent() *C.char {
	return C.CString(GetAppBusEvent())
}

// GetAppBusEvent blocks until an event
func GetAppBusEvent() string {
	for eventHandler == nil {
		log.Debugf("waiting for eventHandler != nil")
		time.Sleep(time.Second)
	}

	var json = ""
	for json == "" {
		json = eventHandler.GetNextEvent()
	}
	return json
}

//export c_ReconnectCwtchForeground
func c_ReconnectCwtchForeground() {
	ReconnectCwtchForeground()
}

// Like StartCwtch, but StartCwtch has already been called so we don't need to restart Tor etc (probably)
// Do need to re-send initial state tho, eg profiles that are already loaded
func ReconnectCwtchForeground() {
	log.Infof("Reconnecting cwtch foreground")
	if application == nil {
		log.Errorf("ReconnectCwtchForeground: Application is nil, presuming stale thread, EXITING Reconnect\n")
		return
	}

	// Repopulate the UI on Android by iterating through all loaded profiles and treating them as New Peer loads. The ReloadEvent field
	// suppresses any listen/connection actions and simply sends updated attributes to the UI.
	// The UI is designed in such a way that "NewPeer" events are treated as updates.
	// TODO: if/when we break apart NewPeer into smaller chunks for the UI to fetch, the need to do this here will go away.
	peerList := application.ListNormalProfiles()
	for _, onion := range peerList {
		eventHandler.Push(event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: onion, utils.ReloadEvent: event.True}))
	}



	settingsJson, _ := json.Marshal(application.ReadSettings())
	application.GetPrimaryBus().Publish(event.NewEvent(settings.UpdateGlobalSettings, map[event.Field]string{event.Data: string(settingsJson)}))
	application.GetPrimaryBus().Publish(event.NewEvent(settings.CwtchStarted, map[event.Field]string{utils.ReloadEvent: event.True}))
	application.QueryACNStatus()
	application.QueryACNVersion()
}

//export c_ShutdownCwtch
func c_ShutdownCwtch() {
	ShutdownCwtch()
}

// ShutdownCwtch is a safe way to shutdown any active cwtch applications and associated ACNs
func ShutdownCwtch() {
	if application != nil {
		// Kill the isolate
		eventHandler.Push(event.NewEvent(event.Shutdown, map[event.Field]string{}))
		// Allow for the shutdown events to go through and then purge everything else...
		log.Infof("Shutting Down Application...")
		application.Shutdown()
		log.Infof("Shutting Down ACN...")
		globalACN.Close()
		log.Infof("Library Shutdown Complete!")
		// do not remove - important for state checks elsewhere
		application = nil
		eventHandler = nil
	}
}

// TODO: At some point these functions should also be autogenerated

// Attribute is a struct to return the dual values of an attempt at a Get*Attribute API call, meant to be json serialized
type Attribute struct {
	Exists bool
	Value  string
}

//export c_SetProfileAttribute
func c_SetProfileAttribute(profile_ptr *C.char, profile_len C.int, key_ptr *C.char, key_len C.int, val_ptr *C.char, val_len C.int) {
	profileOnion := C.GoStringN(profile_ptr, profile_len)
	key := C.GoStringN(key_ptr, key_len)
	value := C.GoStringN(val_ptr, val_len)
	SetProfileAttribute(profileOnion, key, value)
}

// SetProfileAttribute provides a wrapper around profile.SetScopedZonedAttribute
// WARNING: Because this function is potentially dangerous all keys and zones must be added
// explicitly. If you are attempting to added behaviour to the UI that requires the existence of new keys
// you probably want to be building out functionality/subsystem in the UI itself.
// Key must have the format zone.key where Zone is defined in Cwtch. Unknown zones are not permitted.
func SetProfileAttribute(profileOnion string, key string, value string) {
	profile := application.GetPeer(profileOnion)
	if profile != nil {
		zone, key := attr.ParseZone(key)

		// TODO We only allow certain public.profile.key to be set for now.
		// All other scopes and zones need to be added explicitly or handled by Cwtch.
		if zone == attr.ProfileZone && key == constants.Name {
			profile.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name, value)
		} else if zone == attr.ProfileZone && key == constants.ProfileAttribute1 {
			profile.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.ProfileAttribute1, value)
		} else if zone == attr.ProfileZone && key == constants.ProfileAttribute2 {
			profile.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.ProfileAttribute2, value)
		} else if zone == attr.ProfileZone && key == constants.ProfileAttribute3 {
			profile.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.ProfileAttribute3, value)
		} else if zone == attr.ProfileZone && key == constants.ProfileStatus {
			profile.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.ProfileStatus, value)
		} else if zone == attr.ProfileZone && key == constants.PeerAutostart {
			profile.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.PeerAutostart, value)
		} else if zone == attr.ProfileZone && key == constants.PeerAppearOffline {
			profile.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.PeerAppearOffline, value)
		} else if zone == attr.ProfileZone && key == constants.PrivateName {
				profile.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.PrivateName, value)
		} else {
			log.Errorf("attempted to set an attribute with an unknown zone: %v", key)
		}
	}
}

//export c_GetProfileAttribute
func c_GetProfileAttribute(profile_ptr *C.char, profile_len C.int, key_ptr *C.char, key_len C.int) *C.char {
	profileOnion := C.GoStringN(profile_ptr, profile_len)
	key := C.GoStringN(key_ptr, key_len)
	return C.CString(GetProfileAttribute(profileOnion, key))
}

// GetProfileAttribute provides a wrapper around profile.GetScopedZonedAttribute
// Key must have the format zone.key where Zone is defined in Cwtch. Unknown zones are not permitted.
// Currently forcing the Public Scope
// Returns json of Attribute
func GetProfileAttribute(profileOnion string, key string) string {
	profile := application.GetPeer(profileOnion)
	if profile != nil {
		zone, key := attr.ParseZone(key)

		res, exists := profile.GetScopedZonedAttribute(attr.PublicScope, zone, key)
		attr := Attribute{exists, res}
		json, _ := json.Marshal(attr)
		return string(json)

	}
	empty := Attribute{false, ""}
	json, _ := json.Marshal(empty)
	return (string(json))
}

//export c_SetConversationAttribute
func c_SetConversationAttribute(profile_ptr *C.char, profile_len C.int, conversation_id C.int, key_ptr *C.char, key_len C.int, val_ptr *C.char, val_len C.int) {
	profileOnion := C.GoStringN(profile_ptr, profile_len)
	key := C.GoStringN(key_ptr, key_len)
	value := C.GoStringN(val_ptr, val_len)
	SetConversationAttribute(profileOnion, int(conversation_id), key, value)
}

// SetConversationAttribute provides a wrapper around profile.SetProfileAttribute
// key is of format Zone.Key, and the API forces the Local Scope
func SetConversationAttribute(profileOnion string, conversationID int, key string, value string) {
	profile := application.GetPeer(profileOnion)
	zone, key := attr.ParseZone(key)
	profile.SetConversationAttribute(conversationID, attr.LocalScope.ConstructScopedZonedPath(zone.ConstructZonedPath(key)), value)
}

//export c_GetConversationAttribute
func c_GetConversationAttribute(profile_ptr *C.char, profile_len C.int, conversation_id C.int, key_ptr *C.char, key_len C.int) *C.char {
	profileOnion := C.GoStringN(profile_ptr, profile_len)
	key := C.GoStringN(key_ptr, key_len)
	return C.CString(GetConversationAttribute(profileOnion, int(conversation_id), key))
}

// GetGonversationAttribute provides a wrapper around profile.GetGonversationAttribute
// key is of format Scope.Zone.Key
// Returns json of an Attribute
func GetConversationAttribute(profileOnion string, conversationID int, key string) string {
	profile := application.GetPeer(profileOnion)
	if profile != nil {
		scope, zonekey := attr.ParseScope(key)
		zone, key := attr.ParseZone(zonekey)

		res, err := profile.GetConversationAttribute(conversationID, scope.ConstructScopedZonedPath(zone.ConstructZonedPath(key)))
		attr := Attribute{err == nil, res}
		json, _ := json.Marshal(attr)
		return string(json)
	}
	empty := Attribute{false, ""}
	json, _ := json.Marshal(empty)
	return (string(json))
}


//export c_ResetTor
func c_ResetTor() {
	ResetTor()
}


var torLock sync.Mutex

func ResetTor() {
	go func() {
		// prevent concurrent calls to this method...
		torLock.Lock()
		defer torLock.Unlock()
		log.Infof("Replacing ACN with new Tor...")
		settings := application.ReadSettings()
	
		globalACN.Close() // we need to close first if dateDir is the same, otherwise buildACN can't launch tor.
		newAcn, settings, err := buildACN(settings, globalTorPath, globalAppDir)
		// only update settings if successful.
		if err == nil {
			// Only update Tor specific settings...
			currentSettings := application.ReadSettings()
			currentSettings.TorCacheDir = settings.TorCacheDir
			currentSettings.CustomControlPort = settings.CustomControlPort
			currentSettings.CustomSocksPort = settings.CustomSocksPort
			currentSettings.CustomTorrc = settings.CustomTorrc
			application.UpdateSettings(currentSettings)
	
			// We need to update settings on reset as buildACN can alter settings, otherwise the next reset will be broken...
			settings = application.ReadSettings()
			settingsJson, _ := json.Marshal(settings)
			application.GetPrimaryBus().Publish(event.NewEvent(UpdateGlobalSettings, map[event.Field]string{event.Data: string(settingsJson)}))
		} 

		// replace ACN regardlesss
		globalACN.ReplaceACN(newAcn)
		application.QueryACNStatus()
		application.QueryACNVersion()
		log.Infof("Restarted")
	}()
}

const (
	CwtchStarted         = event.Type("CwtchStarted")
	CwtchStartError      = event.Type("CwtchStartError")
	UpdateGlobalSettings = event.Type("UpdateGlobalSettings")
)

func buildACN(globalSettings settings.GlobalSettings, torPath string, appDir string) (connectivity.ACN, settings.GlobalSettings, error) {

	socksPort := mrand.Intn(1000) + 9600
	controlPort := socksPort + 1

	// generate a random password (actually random, stored in memory, for the control port)
	key := make([]byte, 64)
	_, err := rand.Read(key)
	if err != nil {
		panic(err)
	}

	log.Infof("making directory %v", appDir)
	err = os.MkdirAll(path.Join(appDir, "tor"), 0700)

	if err != nil {
		log.Errorf("error creating tor data directory: %v. Aborting app start up", err)
		eventHandler.Push(event.NewEventList(CwtchStartError, event.Error, fmt.Sprintf("Error connecting to Tor: %v", err)))
		erracn := connectivity.NewErrorACN(err)
		return &erracn, globalSettings, err
	}

	if globalSettings.AllowAdvancedTorConfig {
		controlPort = globalSettings.CustomControlPort
		socksPort = globalSettings.CustomSocksPort
	}

	
	// Override Ports if on Tails...
	if cwtchTails := os.Getenv("CWTCH_TAILS"); strings.ToLower(cwtchTails) == "true" {
		log.Infof("CWTCH_TAILS environment variable set... overriding tor config...")

		controlPort = 9051

		// In tails 5.13 the control port was changed to 951
		// so read the Tails Version File and if it exists...
		b, err := os.ReadFile("/etc/amnesia/version")
		if err == nil {
				// the file should start with the version followed
				// by a space...
				versionEnd := strings.Index(string(b), " ")
				if versionEnd == -1 {
						log.Infof("Assuming Tails Version > 6")
					controlPort = 951 
				} else {
				versionStr := string(b)[:versionEnd]
				version, err := strconv.ParseFloat(versionStr, 64)
				if err == nil {
					log.Infof("Confirming Tails Version: %v", version)
					// assert the control port if we are at the dedicated version...
					// we know this change happened sometime after 5.11
					if version >= 5.13 {
							controlPort = 951
					}
				} else {
					log.Errorf("Unable to confirm Tails version. CWTCH_TAILS options may not function correctly.")
					controlPort = 951
				}
			}
		} else {
			// version file is missing post 6.x 
			log.Infof("Assuming Tails Version > 6")
			controlPort = 951 
		}
	
		socksPort = 9050
		globalSettings.CustomControlPort = controlPort
		globalSettings.CustomSocksPort = socksPort
		globalSettings.AllowAdvancedTorConfig = true
	}

	torrc := tor.NewTorrc().WithSocksPort(socksPort).WithOnionTrafficOnly().WithControlPort(controlPort).WithHashedPassword(base64.StdEncoding.EncodeToString(key))
	// torrc.WithLog(path.Join(appDir, "tor", "tor.log"), tor.TorLogLevelNotice)
	if globalSettings.UseCustomTorrc {
		customTorrc := globalSettings.CustomTorrc
		torrc.WithCustom(strings.Split(customTorrc, "\n"))
	} else {
		// Fallback to showing the freshly generated torrc for this session.
		globalSettings.CustomTorrc = torrc.Preview()
		globalSettings.CustomControlPort = controlPort
		globalSettings.CustomSocksPort = socksPort
	}

	err = torrc.Build(path.Join(appDir, "tor", "torrc"))

	if err != nil {
		log.Errorf("error constructing torrc: %v", err)
		eventHandler.Push(event.NewEventList(CwtchStartError, event.Error, fmt.Sprintf("Error connecting to Tor: %v", err)))
		erracn := connectivity.NewErrorACN(err)
		return &erracn, globalSettings, err
	}

	dataDir := globalSettings.TorCacheDir
	if !globalSettings.UseTorCache {

		// purge data dir directories if we are not using them for a cache
		torDir := path.Join(appDir, "tor")
		files, err := path.Glob(path.Join(torDir, "data-dir-*"))
		if err != nil {
			log.Errorf("could not construct filesystem glob: %v", err)
		}
		for _, f := range files {
			if err := os.RemoveAll(f); err != nil {
				log.Errorf("could not remove data-dir: %v", err)
			}
		}

		if dataDir, err = os.MkdirTemp(torDir, "data-dir-"); err != nil {
			eventHandler.Push(event.NewEventList(CwtchStartError, event.Error, fmt.Sprintf("Error connecting to Tor: %v", err)))
			erracn := connectivity.NewErrorACN(err)
			return &erracn, globalSettings, err
		}
	}

	// Persist Current Data Dir as Tor Cache...
	globalSettings.TorCacheDir = dataDir

	acn, err := tor.NewTorACNWithAuth(appDir, torPath, dataDir, controlPort, tor.HashedPasswordAuthenticator{Password: base64.StdEncoding.EncodeToString(key)})
	if err != nil {
		log.Errorf("Error connecting to Tor replacing with ErrorACN: %v\n", err)
		eventHandler.Push(event.NewEventList(CwtchStartError, event.Error, fmt.Sprintf("Error connecting to Tor: %v", err)))
		erracn := connectivity.NewErrorACN(err)
		acn = &erracn
	}
	return acn, globalSettings, err
}


//export c_UpdateSettings
func c_UpdateSettings(json_ptr *C.char, json_len C.int) {
	settingsJson := C.GoStringN(json_ptr, json_len)
	UpdateSettings(settingsJson)
}

func UpdateSettings(settingsJson string) {
	var newSettings settings.GlobalSettings
	json.Unmarshal([]byte(settingsJson), &newSettings)
	application.UpdateSettings(newSettings)
	
	serverExperiment.UpdateSettings(application, &globalACN)

	groupManagerExperiment.UpdateSettings(application, &globalACN)

}

//export c_GetDebugInfo
func c_GetDebugInfo() *C.char {
	return C.CString(GetDebugInfo())
}

type DebugInfo struct {
	BuildVersion  string
	BuildDate     string
	HeapAllocated float64
	HeapInUse     float64
	HeapReleased  float64
	HeapObjects   uint64
	NumThreads    uint64
	SystemMemory  float64
}

func GetDebugInfo() string {

	var memstats runtime.MemStats
	runtime.ReadMemStats(&memstats)

	const MegaByte = 1024.0 * 1024.0

	debugInfo := new(DebugInfo)
	debugInfo.BuildVersion = buildVer
	debugInfo.BuildDate = buildDate
	debugInfo.HeapAllocated = float64(memstats.HeapAlloc) / MegaByte
	debugInfo.HeapObjects = memstats.HeapObjects
	debugInfo.NumThreads = uint64(runtime.NumGoroutine())
	debugInfo.HeapReleased = float64(memstats.HeapReleased) / MegaByte
	debugInfo.HeapInUse = float64(memstats.HeapInuse) / MegaByte
	debugInfo.SystemMemory = float64(memstats.Sys) / MegaByte

	if os.Getenv("CWTCH_PROFILE") == "1" {
		pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
		f, _ := os.Create("mem.prof")
		pprof.WriteHeapProfile(f)
	}

	data, _ := json.Marshal(debugInfo)
	return string(data)
}


// Turn on a Peer Engine
//export c_ActivatePeerEngine
func c_ActivatePeerEngine(onion_ptr *C.char, onion_len C.int) {
	ActivatePeerEngine(C.GoStringN(onion_ptr, onion_len))
}

func ActivatePeerEngine(profile string) {
	application.ActivatePeerEngine(profile)
}

// Turn off a Peer Engine
//export c_DeactivatePeerEngine
func c_DeactivatePeerEngine(onion_ptr *C.char, onion_len C.int) {
	DeactivatePeerEngine(C.GoStringN(onion_ptr, onion_len))
}

func DeactivatePeerEngine(profile string) {
	application.DeactivatePeerEngine(profile)
}

// Configure which kinds of connections on a peer engine are permitted
// i.e listening to inbound connections / making outbound peer connections / making outbound server connections
//export c_ConfigureConnections
func c_ConfigureConnections(onion_ptr *C.char, onion_len C.int,listen1 C.char,peers2 C.char,servers3 C.char) {
	ConfigureConnections(C.GoStringN(onion_ptr, onion_len),listen1 == 1,peers2 == 1,servers3 == 1)
}

func ConfigureConnections(profile string,listen1 bool,peers2 bool,servers3 bool) {
	application.ConfigureConnections(profile,listen1,peers2,servers3)
}

//export c_CreateProfile
func c_CreateProfile(name_ptr *C.char, name_len C.int,password_ptr *C.char, password_len C.int,autostart4 C.char) *C.char  {
	return C.CString(CreateProfile(C.GoStringN(name_ptr, name_len),C.GoStringN(password_ptr, password_len),autostart4 == 1))
}

func CreateProfile(name string,password string,autostart4 bool) string {
	return application.CreateProfile(name,password,autostart4)
}

//export c_LoadProfiles
func c_LoadProfiles(password_ptr *C.char, password_len C.int) {
	LoadProfiles(C.GoStringN(password_ptr, password_len))
}

func LoadProfiles(password string) {
	application.LoadProfiles(password)
}

//export c_DeleteProfile
func c_DeleteProfile(onion_ptr *C.char, onion_len C.int,password_ptr *C.char, password_len C.int) {
	DeleteProfile(C.GoStringN(onion_ptr, onion_len),C.GoStringN(password_ptr, password_len))
}

func DeleteProfile(profile string,password string) {
	application.DeleteProfile(profile,password)
}

//export c_ImportProfile
func c_ImportProfile(file5_ptr *C.char, file5_len C.int,password_ptr *C.char, password_len C.int) *C.char  {
	return C.CString(ImportProfile(C.GoStringN(file5_ptr, file5_len),C.GoStringN(password_ptr, password_len)))
}

func ImportProfile(file5 string,password string) string {
	return application.EnhancedImportProfile(file5,password)
}

//export c_ChangePassword
func c_ChangePassword(onion_ptr *C.char, onion_len C.int,current6_ptr *C.char, current6_len C.int,newPassword7_ptr *C.char, newPassword7_len C.int,newPasswordAgain8_ptr *C.char, newPasswordAgain8_len C.int) {
	ChangePassword(C.GoStringN(onion_ptr, onion_len),C.GoStringN(current6_ptr, current6_len),C.GoStringN(newPassword7_ptr, newPassword7_len),C.GoStringN(newPasswordAgain8_ptr, newPasswordAgain8_len))
}

func ChangePassword(profile string,current6 string,newPassword7 string,newPasswordAgain8 string) {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		cwtchProfile.ChangePassword(current6,newPassword7,newPasswordAgain8)
	}
}

//export c_ExportProfile
func c_ExportProfile(onion_ptr *C.char, onion_len C.int,file9_ptr *C.char, file9_len C.int) {
	ExportProfile(C.GoStringN(onion_ptr, onion_len),C.GoStringN(file9_ptr, file9_len))
}

func ExportProfile(profile string,file9 string) {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		cwtchProfile.ExportProfile(file9)
	}
}

//export c_ImportBundle
func c_ImportBundle(onion_ptr *C.char, onion_len C.int,bundle10_ptr *C.char, bundle10_len C.int)  *C.char  {
	return C.CString(ImportBundle(C.GoStringN(onion_ptr, onion_len),C.GoStringN(bundle10_ptr, bundle10_len)))
}

func ImportBundle(profile string,bundle10 string) string {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		functionality := inter.FunctionalityGate()
		if functionality != nil {
			return functionality.EnhancedImportBundle(cwtchProfile, bundle10)
		}
	}
	return ""
}

// Sends a message via the most appropriate route, Returns EnhancedGetMessageById(newmessage) on success
// msg is a json encoded string represending a model.MessageWrapper
//export c_SendMessage
func c_SendMessage(onion_ptr *C.char, onion_len C.int,conversation C.int,msg11_ptr *C.char, msg11_len C.int)  *C.char  {
	return C.CString(SendMessage(C.GoStringN(onion_ptr, onion_len),int(conversation),C.GoStringN(msg11_ptr, msg11_len)))
}

func SendMessage(profile string,conversation int,msg11 string) string {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		functionality := inter.FunctionalityGate()
		if functionality != nil {
			return functionality.EnhancedSendMessage(cwtchProfile, conversation,msg11)
		}
	}
	return ""
}

//export c_ArchiveConversation
func c_ArchiveConversation(onion_ptr *C.char, onion_len C.int,conversation C.int) {
	ArchiveConversation(C.GoStringN(onion_ptr, onion_len),int(conversation))
}

func ArchiveConversation(profile string,conversation int) {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		cwtchProfile.ArchiveConversation(conversation)
	}
}

//export c_AcceptConversation
func c_AcceptConversation(onion_ptr *C.char, onion_len C.int,conversation C.int) {
	AcceptConversation(C.GoStringN(onion_ptr, onion_len),int(conversation))
}

func AcceptConversation(profile string,conversation int) {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		cwtchProfile.AcceptConversation(conversation)
	}
}

//export c_BlockConversation
func c_BlockConversation(onion_ptr *C.char, onion_len C.int,conversation C.int) {
	BlockConversation(C.GoStringN(onion_ptr, onion_len),int(conversation))
}

func BlockConversation(profile string,conversation int) {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		cwtchProfile.BlockConversation(conversation)
	}
}

//export c_UnblockConversation
func c_UnblockConversation(onion_ptr *C.char, onion_len C.int,conversation C.int) {
	UnblockConversation(C.GoStringN(onion_ptr, onion_len),int(conversation))
}

func UnblockConversation(profile string,conversation int) {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		cwtchProfile.UnblockConversation(conversation)
	}
}

//export c_DeleteConversation
func c_DeleteConversation(onion_ptr *C.char, onion_len C.int,conversation C.int) {
	DeleteConversation(C.GoStringN(onion_ptr, onion_len),int(conversation))
}

func DeleteConversation(profile string,conversation int) {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		cwtchProfile.DeleteConversation(conversation)
	}
}

//export c_PeerWithOnion
func c_PeerWithOnion(onion_ptr *C.char, onion_len C.int,handle12_ptr *C.char, handle12_len C.int) {
	PeerWithOnion(C.GoStringN(onion_ptr, onion_len),C.GoStringN(handle12_ptr, handle12_len))
}

func PeerWithOnion(profile string,handle12 string) {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		cwtchProfile.PeerWithOnion(handle12)
	}
}

//export c_DisconnectFromPeer
func c_DisconnectFromPeer(onion_ptr *C.char, onion_len C.int,handle13_ptr *C.char, handle13_len C.int) {
	DisconnectFromPeer(C.GoStringN(onion_ptr, onion_len),C.GoStringN(handle13_ptr, handle13_len))
}

func DisconnectFromPeer(profile string,handle13 string) {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		cwtchProfile.DisconnectFromPeer(handle13)
	}
}

//export c_GetConversationAccessControlList
func c_GetConversationAccessControlList(onion_ptr *C.char, onion_len C.int,conversation C.int)  *C.char {
	return C.CString(GetConversationAccessControlList(C.GoStringN(onion_ptr, onion_len),int(conversation)))
}

func GetConversationAccessControlList(profile string,conversation int) string {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
				res,err :=  cwtchProfile.EnhancedGetConversationAccessControlList(conversation)
													if err != nil {
															log.Errorf("could not GetConversationAccessControlList %v", err)
													}
												 return res
	}
	return ""
}

//export c_UpdateConversationAccessControlList
func c_UpdateConversationAccessControlList(onion_ptr *C.char, onion_len C.int,conversation C.int,json14_ptr *C.char, json14_len C.int) {
	UpdateConversationAccessControlList(C.GoStringN(onion_ptr, onion_len),int(conversation),C.GoStringN(json14_ptr, json14_len))
}

func UpdateConversationAccessControlList(profile string,conversation int,json14 string) {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		cwtchProfile.EnhancedUpdateConversationAccessControlList(conversation,json14)
	}
}

//export c_SearchConversations
func c_SearchConversations(onion_ptr *C.char, onion_len C.int,pattern15_ptr *C.char, pattern15_len C.int)  *C.char {
	return C.CString(SearchConversations(C.GoStringN(onion_ptr, onion_len),C.GoStringN(pattern15_ptr, pattern15_len)))
}

func SearchConversations(profile string,pattern15 string) string {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
				return cwtchProfile.SearchConversations(pattern15)
	}
	return ""
}

//export c_GetMessageById
func c_GetMessageById(onion_ptr *C.char, onion_len C.int,conversation C.int,message_id C.int)  *C.char {
	return C.CString(GetMessageById(C.GoStringN(onion_ptr, onion_len),int(conversation),int(message_id)))
}

func GetMessageById(profile string,conversation int,message_id int) string {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
				return cwtchProfile.EnhancedGetMessageById(conversation,message_id)
	}
	return ""
}

//export c_GetMessageByContentHash
func c_GetMessageByContentHash(onion_ptr *C.char, onion_len C.int,conversation C.int,contentHash16_ptr *C.char, contentHash16_len C.int)  *C.char {
	return C.CString(GetMessageByContentHash(C.GoStringN(onion_ptr, onion_len),int(conversation),C.GoStringN(contentHash16_ptr, contentHash16_len)))
}

func GetMessageByContentHash(profile string,conversation int,contentHash16 string) string {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
				return cwtchProfile.EnhancedGetMessageByContentHash(conversation,contentHash16)
	}
	return ""
}

//export c_GetMessages
func c_GetMessages(onion_ptr *C.char, onion_len C.int,conversation C.int,index C.int,count C.uint)  *C.char {
	return C.CString(GetMessages(C.GoStringN(onion_ptr, onion_len),int(conversation),int(index),int(count)))
}

func GetMessages(profile string,conversation int,index int,count int) string {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
				return cwtchProfile.EnhancedGetMessages(conversation,index,uint(count))
	}
	return ""
}

//export c_SendInviteMessage
func c_SendInviteMessage(onion_ptr *C.char, onion_len C.int,conversation C.int,target C.int)  *C.char {
	return C.CString(SendInviteMessage(C.GoStringN(onion_ptr, onion_len),int(conversation),int(target)))
}

func SendInviteMessage(profile string,conversation int,target int) string {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
				return cwtchProfile.EnhancedSendInviteMessage(conversation,target)
	}
	return ""
}

//export c_UpdateMessageAttribute
func c_UpdateMessageAttribute(onion_ptr *C.char, onion_len C.int,conversation C.int,channel_id C.int,message_id C.int,attributeKey17_ptr *C.char, attributeKey17_len C.int,attributeValue18_ptr *C.char, attributeValue18_len C.int) {
	UpdateMessageAttribute(C.GoStringN(onion_ptr, onion_len),int(conversation),int(channel_id),int(message_id),C.GoStringN(attributeKey17_ptr, attributeKey17_len),C.GoStringN(attributeValue18_ptr, attributeValue18_len))
}

func UpdateMessageAttribute(profile string,conversation int,channel_id int,message_id int,attributeKey17 string,attributeValue18 string) {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		cwtchProfile.UpdateMessageAttribute(conversation,channel_id,message_id,attributeKey17,attributeValue18)
	}
}

//export c_StartGroup
func c_StartGroup(onion_ptr *C.char, onion_len C.int,name19_ptr *C.char, name19_len C.int,server20_ptr *C.char, server20_len C.int) {
	StartGroup(C.GoStringN(onion_ptr, onion_len),C.GoStringN(name19_ptr, name19_len),C.GoStringN(server20_ptr, server20_len))
}

func StartGroup(profile string,name19 string,server20 string) {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		cwtchProfile.StartGroup(name19,server20)
	}
}

//export c_QueueJoinServer
func c_QueueJoinServer(onion_ptr *C.char, onion_len C.int,handle21_ptr *C.char, handle21_len C.int) {
	QueueJoinServer(C.GoStringN(onion_ptr, onion_len),C.GoStringN(handle21_ptr, handle21_len))
}

func QueueJoinServer(profile string,handle21 string) {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		cwtchProfile.QueueJoinServer(handle21)
	}
}

//export c_DisconnectFromServer
func c_DisconnectFromServer(onion_ptr *C.char, onion_len C.int,handle22_ptr *C.char, handle22_len C.int) {
	DisconnectFromServer(C.GoStringN(onion_ptr, onion_len),C.GoStringN(handle22_ptr, handle22_len))
}

func DisconnectFromServer(profile string,handle22 string) {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		cwtchProfile.DisconnectFromServer(handle22)
	}
}

//export c_PublishServerUpdate
func c_PublishServerUpdate(onion_ptr *C.char, onion_len C.int,) {
	PublishServerUpdate(C.GoStringN(onion_ptr, onion_len),)
}

func PublishServerUpdate(profile string,) {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		functionality := servers.FunctionalityGate()
		if functionality != nil {
			err := functionality.PublishServerUpdate(cwtchProfile, )
			if err != nil {
				log.Errorf("error calling experimental feature PublishServerUpdate: %v", err)
			}
		}
	}
}

//export c_GetServerInfoList
func c_GetServerInfoList(onion_ptr *C.char, onion_len C.int,) {
	GetServerInfoList(C.GoStringN(onion_ptr, onion_len),)
}

func GetServerInfoList(profile string,) {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		functionality := servers.FunctionalityGate()
		if functionality != nil {
			err := functionality.GetServerInfoList(cwtchProfile, )
			if err != nil {
				log.Errorf("error calling experimental feature GetServerInfoList: %v", err)
			}
		}
	}
}

//export c_DeleteServerInfo
func c_DeleteServerInfo(onion_ptr *C.char, onion_len C.int,serverOnion23_ptr *C.char, serverOnion23_len C.int) {
	DeleteServerInfo(C.GoStringN(onion_ptr, onion_len),C.GoStringN(serverOnion23_ptr, serverOnion23_len))
}

func DeleteServerInfo(profile string,serverOnion23 string) {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		functionality := servers.FunctionalityGate()
		if functionality != nil {
			err := functionality.DeleteServerInfo(cwtchProfile, serverOnion23)
			if err != nil {
				log.Errorf("error calling experimental feature DeleteServerInfo: %v", err)
			}
		}
	}
}

//export c_DownloadFileDefaultLimit
func c_DownloadFileDefaultLimit(onion_ptr *C.char, onion_len C.int,conversation C.int,filepath24_ptr *C.char, filepath24_len C.int,manifest25_ptr *C.char, manifest25_len C.int,filekey26_ptr *C.char, filekey26_len C.int) {
	DownloadFileDefaultLimit(C.GoStringN(onion_ptr, onion_len),int(conversation),C.GoStringN(filepath24_ptr, filepath24_len),C.GoStringN(manifest25_ptr, manifest25_len),C.GoStringN(filekey26_ptr, filekey26_len))
}

func DownloadFileDefaultLimit(profile string,conversation int,filepath24 string,manifest25 string,filekey26 string) {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		functionality := filesharing.FunctionalityGate()
		if functionality != nil {
			err := functionality.DownloadFileDefaultLimit(cwtchProfile, conversation,filepath24,manifest25,filekey26)
			if err != nil {
				log.Errorf("error calling experimental feature DownloadFileDefaultLimit: %v", err)
			}
		}
	}
}

//export c_RestartFileShare
func c_RestartFileShare(onion_ptr *C.char, onion_len C.int,filekey27_ptr *C.char, filekey27_len C.int) {
	RestartFileShare(C.GoStringN(onion_ptr, onion_len),C.GoStringN(filekey27_ptr, filekey27_len))
}

func RestartFileShare(profile string,filekey27 string) {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		functionality := filesharing.FunctionalityGate()
		if functionality != nil {
			err := functionality.RestartFileShare(cwtchProfile, filekey27)
			if err != nil {
				log.Errorf("error calling experimental feature RestartFileShare: %v", err)
			}
		}
	}
}

//export c_StopFileShare
func c_StopFileShare(onion_ptr *C.char, onion_len C.int,filekey28_ptr *C.char, filekey28_len C.int) {
	StopFileShare(C.GoStringN(onion_ptr, onion_len),C.GoStringN(filekey28_ptr, filekey28_len))
}

func StopFileShare(profile string,filekey28 string) {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		functionality := filesharing.FunctionalityGate()
		if functionality != nil {
			err := functionality.StopFileShare(cwtchProfile, filekey28)
			if err != nil {
				log.Errorf("error calling experimental feature StopFileShare: %v", err)
			}
		}
	}
}

//export c_CheckDownloadStatus
func c_CheckDownloadStatus(onion_ptr *C.char, onion_len C.int,filekey29_ptr *C.char, filekey29_len C.int) {
	CheckDownloadStatus(C.GoStringN(onion_ptr, onion_len),C.GoStringN(filekey29_ptr, filekey29_len))
}

func CheckDownloadStatus(profile string,filekey29 string) {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		functionality := filesharing.FunctionalityGate()
		if functionality != nil {
			err := functionality.CheckDownloadStatus(cwtchProfile, filekey29)
			if err != nil {
				log.Errorf("error calling experimental feature CheckDownloadStatus: %v", err)
			}
		}
	}
}

//export c_VerifyOrResumeDownloadDefaultLimit
func c_VerifyOrResumeDownloadDefaultLimit(onion_ptr *C.char, onion_len C.int,conversation C.int,filekey30_ptr *C.char, filekey30_len C.int) {
	VerifyOrResumeDownloadDefaultLimit(C.GoStringN(onion_ptr, onion_len),int(conversation),C.GoStringN(filekey30_ptr, filekey30_len))
}

func VerifyOrResumeDownloadDefaultLimit(profile string,conversation int,filekey30 string) {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		functionality := filesharing.FunctionalityGate()
		if functionality != nil {
			err := functionality.VerifyOrResumeDownloadDefaultLimit(cwtchProfile, conversation,filekey30)
			if err != nil {
				log.Errorf("error calling experimental feature VerifyOrResumeDownloadDefaultLimit: %v", err)
			}
		}
	}
}

//export c_ShareFile
func c_ShareFile(onion_ptr *C.char, onion_len C.int,conversation C.int,filepath31_ptr *C.char, filepath31_len C.int)  *C.char  {
	return C.CString(ShareFile(C.GoStringN(onion_ptr, onion_len),int(conversation),C.GoStringN(filepath31_ptr, filepath31_len)))
}

func ShareFile(profile string,conversation int,filepath31 string) string {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		functionality := filesharing.FunctionalityGate()
		if functionality != nil {
			return functionality.EnhancedShareFile(cwtchProfile, conversation,filepath31)
		}
	}
	return ""
}

//export c_GetSharedFiles
func c_GetSharedFiles(onion_ptr *C.char, onion_len C.int,conversation C.int)  *C.char  {
	return C.CString(GetSharedFiles(C.GoStringN(onion_ptr, onion_len),int(conversation)))
}

func GetSharedFiles(profile string,conversation int) string {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		functionality := filesharing.FunctionalityGate()
		if functionality != nil {
			return functionality.EnhancedGetSharedFiles(cwtchProfile, conversation)
		}
	}
	return ""
}
var serverExperiment *server_hosting.ServersFunctionality

//export c_CreateServer
func c_CreateServer(password_ptr *C.char, password_len C.int,description32_ptr *C.char, description32_len C.int,autostart33 C.char) {
	CreateServer(C.GoStringN(password_ptr, password_len),C.GoStringN(description32_ptr, description32_len),autostart33 == 1)
}

func CreateServer(password string,description32 string,autostart33 bool) {
	serverExperiment.CreateServer(application,password,description32,autostart33)
}

//export c_SetServerAttribute
func c_SetServerAttribute(handle34_ptr *C.char, handle34_len C.int,key35_ptr *C.char, key35_len C.int,val36_ptr *C.char, val36_len C.int) {
	SetServerAttribute(C.GoStringN(handle34_ptr, handle34_len),C.GoStringN(key35_ptr, key35_len),C.GoStringN(val36_ptr, val36_len))
}

func SetServerAttribute(handle34 string,key35 string,val36 string) {
	serverExperiment.SetServerAttribute(application,handle34,key35,val36)
}

//export c_LoadServers
func c_LoadServers(password_ptr *C.char, password_len C.int) {
	LoadServers(C.GoStringN(password_ptr, password_len))
}

func LoadServers(password string) {
	serverExperiment.LoadServers(application,&globalACN,password)
}

//export c_LaunchServers
func c_LaunchServers() {
	LaunchServers()
}

func LaunchServers() {
	serverExperiment.LaunchServers(application,&globalACN)
}

//export c_LaunchServer
func c_LaunchServer(handle37_ptr *C.char, handle37_len C.int) {
	LaunchServer(C.GoStringN(handle37_ptr, handle37_len))
}

func LaunchServer(handle37 string) {
	serverExperiment.LaunchServer(application,handle37)
}

//export c_StopServer
func c_StopServer(handle38_ptr *C.char, handle38_len C.int) {
	StopServer(C.GoStringN(handle38_ptr, handle38_len))
}

func StopServer(handle38 string) {
	serverExperiment.StopServer(application,handle38)
}

//export c_StopServers
func c_StopServers() {
	StopServers()
}

func StopServers() {
	serverExperiment.StopServers(application)
}

//export c_DestroyServers
func c_DestroyServers() {
	DestroyServers()
}

func DestroyServers() {
	serverExperiment.DestroyServers()
}

//export c_DeleteServer
func c_DeleteServer(handle39_ptr *C.char, handle39_len C.int,password_ptr *C.char, password_len C.int) {
	DeleteServer(C.GoStringN(handle39_ptr, handle39_len),C.GoStringN(password_ptr, password_len))
}

func DeleteServer(handle39 string,password string) {
	serverExperiment.DeleteServer(application,handle39,password)
}
var groupManagerExperiment *group_manager.GroupManagerFunctionality

//export c_CreateHybridGroup
func c_CreateHybridGroup(password_ptr *C.char, password_len C.int,description40_ptr *C.char, description40_len C.int,autostart41 C.char,groupAdmin42_ptr *C.char, groupAdmin42_len C.int) {
	CreateHybridGroup(C.GoStringN(password_ptr, password_len),C.GoStringN(description40_ptr, description40_len),autostart41 == 1,C.GoStringN(groupAdmin42_ptr, groupAdmin42_len))
}

func CreateHybridGroup(password string,description40 string,autostart41 bool,groupAdmin42 string) {
	groupManagerExperiment.CreateHybridGroup(application,password,description40,autostart41,groupAdmin42)
}

//export c_SetHybridGroupAttribute
func c_SetHybridGroupAttribute(handle43_ptr *C.char, handle43_len C.int,key44_ptr *C.char, key44_len C.int,val45_ptr *C.char, val45_len C.int) {
	SetHybridGroupAttribute(C.GoStringN(handle43_ptr, handle43_len),C.GoStringN(key44_ptr, key44_len),C.GoStringN(val45_ptr, val45_len))
}

func SetHybridGroupAttribute(handle43 string,key44 string,val45 string) {
	groupManagerExperiment.SetHybridGroupAttribute(application,handle43,key44,val45)
}

//export c_LoadHybridGroups
func c_LoadHybridGroups(password_ptr *C.char, password_len C.int) {
	LoadHybridGroups(C.GoStringN(password_ptr, password_len))
}

func LoadHybridGroups(password string) {
	groupManagerExperiment.LoadHybridGroups(application,&globalACN,password)
}

//export c_LaunchHybridGroups
func c_LaunchHybridGroups() {
	LaunchHybridGroups()
}

func LaunchHybridGroups() {
	groupManagerExperiment.LaunchHybridGroups(application,&globalACN)
}

//export c_LaunchHybridGroup
func c_LaunchHybridGroup(handle46_ptr *C.char, handle46_len C.int) {
	LaunchHybridGroup(C.GoStringN(handle46_ptr, handle46_len))
}

func LaunchHybridGroup(handle46 string) {
	groupManagerExperiment.LaunchHybridGroup(application,handle46)
}

//export c_StopHybridGroup
func c_StopHybridGroup(handle47_ptr *C.char, handle47_len C.int) {
	StopHybridGroup(C.GoStringN(handle47_ptr, handle47_len))
}

func StopHybridGroup(handle47 string) {
	groupManagerExperiment.StopHybridGroup(application,handle47)
}

//export c_StopHybridGroups
func c_StopHybridGroups() {
	StopHybridGroups()
}

func StopHybridGroups() {
	groupManagerExperiment.StopHybridGroups(application)
}

//export c_DestroyHybridGroups
func c_DestroyHybridGroups() {
	DestroyHybridGroups()
}

func DestroyHybridGroups() {
	groupManagerExperiment.DestroyHybridGroups()
}

//export c_DeleteHybridGroup
func c_DeleteHybridGroup(handle48_ptr *C.char, handle48_len C.int,password_ptr *C.char, password_len C.int) {
	DeleteHybridGroup(C.GoStringN(handle48_ptr, handle48_len),C.GoStringN(password_ptr, password_len))
}

func DeleteHybridGroup(handle48 string,password string) {
	groupManagerExperiment.DeleteHybridGroup(application,handle48,password)
}

//export c_ChangeGroupMode
func c_ChangeGroupMode(onion_ptr *C.char, onion_len C.int,conversation C.int,partialMode49_ptr *C.char, partialMode49_len C.int) {
	ChangeGroupMode(C.GoStringN(onion_ptr, onion_len),int(conversation),C.GoStringN(partialMode49_ptr, partialMode49_len))
}

func ChangeGroupMode(profile string,conversation int,partialMode49 string) {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		functionality := hybrid.FunctionalityGate()
		if functionality != nil {
			err := functionality.ChangeGroupMode(cwtchProfile, conversation,partialMode49)
			if err != nil {
				log.Errorf("error calling experimental feature ChangeGroupMode: %v", err)
			}
		}
	}
}

//export c_ChangeRole
func c_ChangeRole(onion_ptr *C.char, onion_len C.int,conversation C.int,handle50_ptr *C.char, handle50_len C.int,role51_ptr *C.char, role51_len C.int) {
	ChangeRole(C.GoStringN(onion_ptr, onion_len),int(conversation),C.GoStringN(handle50_ptr, handle50_len),model.Role(C.GoStringN(role51_ptr, role51_len)))
}

func ChangeRole(profile string,conversation int,handle50 string,role51 model.Role) {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		functionality := hybrid.FunctionalityGate()
		if functionality != nil {
			err := functionality.ChangeRole(cwtchProfile, conversation,handle50,role51)
			if err != nil {
				log.Errorf("error calling experimental feature ChangeRole: %v", err)
			}
		}
	}
}

//export c_InviteMember
func c_InviteMember(onion_ptr *C.char, onion_len C.int,conversation C.int,handle52_ptr *C.char, handle52_len C.int) {
	InviteMember(C.GoStringN(onion_ptr, onion_len),int(conversation),C.GoStringN(handle52_ptr, handle52_len))
}

func InviteMember(profile string,conversation int,handle52 string) {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		functionality := hybrid.FunctionalityGate()
		if functionality != nil {
			err := functionality.InviteMember(cwtchProfile, conversation,handle52)
			if err != nil {
				log.Errorf("error calling experimental feature InviteMember: %v", err)
			}
		}
	}
}

//export c_KickMember
func c_KickMember(onion_ptr *C.char, onion_len C.int,conversation C.int,handle53_ptr *C.char, handle53_len C.int) {
	KickMember(C.GoStringN(onion_ptr, onion_len),int(conversation),C.GoStringN(handle53_ptr, handle53_len))
}

func KickMember(profile string,conversation int,handle53 string) {
	cwtchProfile := application.GetPeer(profile)
	if cwtchProfile != nil {
		functionality := hybrid.FunctionalityGate()
		if functionality != nil {
			err := functionality.KickMember(cwtchProfile, conversation,handle53)
			if err != nil {
				log.Errorf("error calling experimental feature KickMember: %v", err)
			}
		}
	}
}


// Leave as is, needed by ffi
func main() {}
