package tunnelhostnamemapper

import (
	"sync"

	"github.com/cloudflare/cloudflared/h2mux"
	"github.com/cloudflare/cloudflared/originservice"
	"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
)

// TunnelHostnameMapper maps TunnelHostname to an OriginService
type TunnelHostnameMapper struct {
	sync.RWMutex
	tunnelHostnameToOrigin map[h2mux.TunnelHostname]originservice.OriginService
}

func NewTunnelHostnameMapper() *TunnelHostnameMapper {
	return &TunnelHostnameMapper{
		tunnelHostnameToOrigin: make(map[h2mux.TunnelHostname]originservice.OriginService),
	}
}

// Get an OriginService given a TunnelHostname
func (om *TunnelHostnameMapper) Get(key h2mux.TunnelHostname) (originservice.OriginService, bool) {
	om.RLock()
	defer om.RUnlock()
	originService, ok := om.tunnelHostnameToOrigin[key]
	return originService, ok
}

// Add a mapping. If there is already an OriginService with this key, shutdown the old origin service and replace it
// with the new one
func (om *TunnelHostnameMapper) Add(key h2mux.TunnelHostname, os originservice.OriginService) {
	om.Lock()
	defer om.Unlock()
	if oldOS, ok := om.tunnelHostnameToOrigin[key]; ok {
		oldOS.Shutdown()
	}
	om.tunnelHostnameToOrigin[key] = os
}

// Delete a mapping, and shutdown its OriginService
func (om *TunnelHostnameMapper) Delete(key h2mux.TunnelHostname) (keyFound bool) {
	om.Lock()
	defer om.Unlock()
	if os, ok := om.tunnelHostnameToOrigin[key]; ok {
		os.Shutdown()
		delete(om.tunnelHostnameToOrigin, key)
		return true
	}
	return false
}

// ToRemove finds all keys that should be removed from the TunnelHostnameMapper.
func (om *TunnelHostnameMapper) ToRemove(newConfigs []*pogs.ReverseProxyConfig) (toRemove []h2mux.TunnelHostname) {
	om.Lock()
	defer om.Unlock()

	// Convert into a set, for O(1) lookups instead of O(n)
	newConfigSet := toSet(newConfigs)

	// If a config in `om` isn't in `newConfigs`, it must be removed.
	for hostname := range om.tunnelHostnameToOrigin {
		if _, ok := newConfigSet[hostname]; !ok {
			toRemove = append(toRemove, hostname)
		}
	}

	return
}

// ToAdd filters the given configs, keeping those that should be added to the TunnelHostnameMapper.
func (om *TunnelHostnameMapper) ToAdd(newConfigs []*pogs.ReverseProxyConfig) (toAdd []*pogs.ReverseProxyConfig) {
	om.Lock()
	defer om.Unlock()

	// If a config in `newConfigs` isn't in `om`, it must be added.
	for _, config := range newConfigs {
		if _, ok := om.tunnelHostnameToOrigin[config.TunnelHostname]; !ok {
			toAdd = append(toAdd, config)
		}
	}

	return
}

func toSet(configs []*pogs.ReverseProxyConfig) map[h2mux.TunnelHostname]*pogs.ReverseProxyConfig {
	m := make(map[h2mux.TunnelHostname]*pogs.ReverseProxyConfig)
	for _, config := range configs {
		m[config.TunnelHostname] = config
	}
	return m
}