package euls

import (
	"sync"
)

var (
	globalClient *client
	globalLock   sync.Mutex
	async        *asyncEventPutter
	configured   bool
)

func init() {
	initGlobalClient()
}

func initGlobalClient() {
	configured = false
	async = &asyncEventPutter{}
	var err error
	// Use nil configuration as it will be deferred to the client to provide
	// such configuration. The async eventPutter will collect all of the
	// events in the mean time.
	globalClient, err = newClient(nil, async)
	if err != nil && err != errNilConfig {
		logger.Println("failed to configure global euls client:", err)
	}
}

// ClientType defines the types of clients that this package provides
type ClientType int

const (
	// DISABLED is a client that does not send any events
	DISABLED ClientType = iota
	// SYNC is a client that sends events synchronously
	SYNC
	// ASYNC is a client that sends events asynchronously
	ASYNC
)

// Configure configures the global state of the package with a client type
// and a Config. This can be called at any time. StartSession, StopSession,
// and PutEvent can be called before this is called. All calls to those methods
// will be executed once Configure is called.
func Configure(ct ClientType, config *Config) {
	globalLock.Lock()
	defer globalLock.Unlock()

	configured = true

	// TODO: Can we avoid this global configuration block?
	// The idea here is that we have configured our global client with
	// the async event putter with a dummy eventPutter implementation. Once the
	// user specifies what client type they wish to use, we make the necessary
	// modifies to globalClient and asyncEventPutter to begin using the desired
	// eventPutter.
	switch ct {
	case DISABLED:
		// Process all existing events
		async.eventPutter = &disableEventPutter{}
		async.SetConfig(nil)
		// Release resources
		async.Close()

		// Ensure further events are dropped
		globalClient.ep = async.eventPutter
	case ASYNC:
		// Configure async so existing events are processed as well as new ones
		async.eventPutter = &syncEventPutter{}
		if err := async.SetConfig(config); err != nil {
			logger.Println("failed to configure async client:", err)
		}
	case SYNC:
		async.eventPutter = &syncEventPutter{}
		// Configure async so existing events are processed
		if err := async.SetConfig(config); err != nil {
			// async configuration really configures sync.
			logger.Println("failed to configure sync client:", err)
		}
		// Release resources
		async.Close()

		// Ensure further events are handled synchronously
		globalClient.ep = async.eventPutter
	}
}

// StartSession starts a new session by sending the corresponding start session
// event to Mobile Analytics. Once a session is started, it must be ended with
// StopSession before StartSession can be invoked again.
func StartSession() error {
	globalLock.Lock()
	defer globalLock.Unlock()
	return globalClient.StartSession()
}

// StopSession stops the session by sending the corresponding stop session
// event to MobileAnalytics.
func StopSession() error {
	globalLock.Lock()
	defer globalLock.Unlock()

	if !configured {
		async.eventPutter = &disableEventPutter{}
		async.SetConfig(nil)
	}

	return globalClient.StopSession()
}

// PutEvent sends an event to Mobile Analytics given an event type, attributes,
// and metrics. A session must be created by StartSession for events to be sent.
func PutEvent(eventType string, attributes map[string]*string, metrics map[string]*float64) error {
	globalLock.Lock()
	defer globalLock.Unlock()
	return globalClient.PutEvent(eventType, attributes, metrics)
}
