package models

import (
	"git.wamblee.org/converge/pkg/comms"
	"sort"
	"sync/atomic"
	"time"
)

type RendezVousId string
type AgentGuid string
type ClientGuid string
type ClientId string
type SessionType string
type RemoteAddr string

type Agent struct {
	Guid            AgentGuid
	RemoteAddr      RemoteAddr
	PublicId        RendezVousId
	StartTime       time.Time
	EnvironmentInfo comms.EnvironmentInfo
	expiryTime      int64
}

func (agent *Agent) SetExpiryTime(t time.Time) {
	atomic.StoreInt64(&agent.expiryTime, t.UnixNano())
}

func (agent *Agent) GetExpiryTime() time.Time {
	t := atomic.LoadInt64(&agent.expiryTime)
	return time.Unix(0, t)
}

type Client struct {
	Guid            ClientGuid
	RemoteAddr      RemoteAddr
	PublicId        RendezVousId
	ClientId        ClientId
	AgentGuid       AgentGuid
	StartTime       time.Time
	SessionType     SessionType
	EnvironmentInfo comms.EnvironmentInfo
}

// State is a description of the current state of converge.
// Created by the matchmaker and used for updating the web client
// and prometheus metrics.
//
// Concurrency design:
// 1. State is immutable
// 2. The MatchMaker uses copy-on-write and never modifies a state directly.
// 3. the matchmaker modifies the expiry time of the agent. This is dealt with using the
//    sync/atomic package by storing the expiry time as an int64 using time.Time.UnixNano()

type State struct {
	Agents  map[AgentGuid]*Agent
	Clients map[ClientGuid]*Client
}

func NewState() *State {
	return &State{
		Agents:  make(map[AgentGuid]*Agent),
		Clients: make(map[ClientGuid]*Client),
	}
}

// for copy on write
func (state *State) Copy() *State {
	res := NewState()
	for k, v := range state.Agents {
		res.Agents[k] = v
	}
	for k, v := range state.Clients {
		res.Clients[k] = v
	}
	return res
}

// Return agents and clients slices sorted on starttime ascending.
func (state *State) Slices() ([]*Agent, []*Client) {
	agents := make([]*Agent, 0, len(state.Agents))
	clients := make([]*Client, 0, len(state.Clients))
	for _, agent := range state.Agents {
		agents = append(agents, agent)
	}
	for _, client := range state.Clients {
		clients = append(clients, client)
	}
	sort.Slice(agents, func(i int, j int) bool {
		return agents[i].StartTime.Before(agents[j].StartTime)
	})
	sort.Slice(clients, func(i int, j int) bool {
		return clients[i].StartTime.Before(clients[j].StartTime)
	})
	return agents, clients
}