package backend

import (
	"bytes"
	"encoding/json"
	"log"
	"sync"

	"0xacab.org/leap/bitmask-vpn/pkg/bitmask"
	"0xacab.org/leap/bitmask-vpn/pkg/config"
	"0xacab.org/leap/bitmask-vpn/pkg/snowflake"
)

const (
	offStr      = "off"
	startingStr = "starting"
	onStr       = "on"
	stoppingStr = "stopping"
	failedStr   = "failed"
)

// ctx will be our glorious global object.
// if we ever switch again to a provider-agnostic app, we should keep a map here.
var ctx *connectionCtx

// these mutexes protect setting and updating the global status in this go backend
var statusMutex sync.Mutex
var updateMutex sync.Mutex

// The connectionCtx keeps the global state that is passed around to C-land. It
// also serves as the primary way of passing requests from the frontend to the
// Go-core, by letting the UI write some of these variables and processing
// them.

type connectionCtx struct {
	AppName         string              `json:"appName"`
	Provider        string              `json:"provider"`
	TosURL          string              `json:"tosURL"`
	HelpURL         string              `json:"helpURL"`
	AskForDonations bool                `json:"askForDonations"`
	DonateDialog    bool                `json:"donateDialog"`
	DonateURL       string              `json:"donateURL"`
	LoginDialog     bool                `json:"loginDialog"`
	LoginOk         bool                `json:"loginOk"`
	Version         string              `json:"version"`
	Errors          string              `json:"errors"`
	Status          status              `json:"status"`
	Locations       map[string]float64  `json:"locations"`
	LocationLabels  map[string][]string `json:"locationLabels"`
	CurrentGateway  string              `json:"currentGateway"`
	CurrentLocation string              `json:"currentLocation"`
	CurrentCountry  string              `json:"currentCountry"`
	BestLocation    string              `json:"bestLocation"`
	Transport       string              `json:"transport"`
	UseUDP          bool                `json:"udp"`
	OffersUDP       bool                `json:"offersUdp"`
	ManualLocation  bool                `json:"manualLocation"`
	IsReady         bool                `json:"isReady"`
	CanUpgrade      bool                `json:"canUpgrade"`
	Motd            string              `json:"motd"`
	HasTor          bool                `json:"hasTor"`
	UseSnowflake    bool                `json:"snowflake"`
	bm              bitmask.Bitmask
	autostart       bitmask.Autostart
	cfg             *config.Config
}

func (c *connectionCtx) toJson() ([]byte, error) {
	statusMutex.Lock()
	if c.bm != nil {
		transport := c.bm.GetTransport()
		c.Locations = c.bm.ListLocationFullness(transport)
		c.LocationLabels = c.bm.ListLocationLabels(transport)
		c.CurrentGateway = c.bm.GetCurrentGateway()
		c.CurrentLocation = c.bm.GetCurrentLocation()
		c.CurrentCountry = c.bm.GetCurrentCountry()
		c.BestLocation = c.bm.GetBestLocation(transport)
		c.Transport = transport
		c.UseUDP = c.cfg.UDP // TODO initialize bitmask param?
		c.OffersUDP = c.bm.OffersUDP()
		c.UseSnowflake = c.cfg.Snowflake // TODO initialize bitmask param?
		c.ManualLocation = c.bm.IsManualLocation()
		c.CanUpgrade = c.bm.CanUpgrade()
		c.Motd = c.bm.GetMotd()
		c.HasTor = snowflake.HasTor()
	}
	defer statusMutex.Unlock()
	b, err := json.Marshal(c)
	if err != nil {
		log.Println(err)
		return nil, err
	}
	return b, nil
}

func (c connectionCtx) updateStatus() {
	updateMutex.Lock()
	defer updateMutex.Unlock()
	if stStr, err := c.bm.GetStatus(); err != nil {
		log.Printf("Error getting status: %v", err)
	} else {
		setStatusFromStr(stStr)
	}

	statusCh := c.bm.GetStatusCh()
	for {
		select {
		case stStr := <-statusCh:
			setStatusFromStr(stStr)
		}
	}
}

func setStatus(st status) {
	statusMutex.Lock()
	defer statusMutex.Unlock()
	ctx.Status = st
	go trigger(OnStatusChanged)
}

// the status type reflects the current VPN status. Go code is responsible for updating
// it; the C gui just watches its changes and pulls its updates via the serialized
// context object.

type status int

const (
	off status = iota
	starting
	on
	stopping
	failed
	unknown
)

func (s status) String() string {
	return [...]string{offStr, startingStr, onStr, stoppingStr, failedStr}[s]
}

func (s status) MarshalJSON() ([]byte, error) {
	b := bytes.NewBufferString(`"`)
	b.WriteString(s.String())
	b.WriteString(`"`)
	return b.Bytes(), nil
}

func (s status) fromString(st string) status {
	switch st {
	case offStr:
		return off
	case startingStr:
		return starting
	case onStr:
		return on
	case stoppingStr:
		return stopping
	case failedStr:
		return failed
	default:
		return unknown
	}
}

func setStatusFromStr(stStr string) {
	setStatus(unknown.fromString(stStr))
}
