package protocol

// Message types
const (
	MsgRegister       = "register"
	MsgDeviceList     = "device_list"
	MsgRequestDevice  = "request_device"
	MsgDeviceGranted  = "device_granted"
	MsgDeviceDenied   = "device_denied"
	MsgReleaseDevice  = "release_device"
	MsgDeviceReleased = "device_released"
	MsgClientJoined   = "client_joined"
	MsgClientLeft     = "client_left"
	MsgForceRelease   = "force_release"
	MsgPing           = "ping"
	MsgPong           = "pong"
	MsgError          = "error"
)

// Client modes.
//
// ModeBoth lets a single client offer its own devices and consume other
// clients' devices at the same time, which is the normal case for a peer
// group where every machine both lends and borrows hardware.
const (
	ModeShare = "share"
	ModeUse   = "use"
	ModeBoth  = "both"
)

// ValidMode reports whether mode is one this build understands.
func ValidMode(mode string) bool {
	switch mode {
	case ModeShare, ModeUse, ModeBoth:
		return true
	}
	return false
}

// CanShare reports whether a client in this mode offers devices to others.
func CanShare(mode string) bool { return mode == ModeShare || mode == ModeBoth }

// CanUse reports whether a client in this mode consumes devices from others.
func CanUse(mode string) bool { return mode == ModeUse || mode == ModeBoth }

// Device status
const (
	StatusAvailable = "available"
	StatusInUse     = "in_use"
)

// Envelope is the top-level message wrapper
type Envelope struct {
	Type string `json:"type"`
}

// Register is sent by a client when connecting to the relay
type Register struct {
	Type     string `json:"type"`
	Hash     string `json:"hash"`
	Mode     string `json:"mode"`
	ClientID string `json:"client_id"`
	Name     string `json:"name"`

	// DirectPort is the TCP port this client listens on for direct tunnel
	// connections, or 0 if it accepts none. Peers use it to skip the relay.
	DirectPort int `json:"direct_port,omitempty"`

	// LocalEndpoints are host:port addresses on this client's own interfaces.
	// They let two machines on the same network find each other directly
	// instead of sending USB traffic out to a relay and back.
	LocalEndpoints []string `json:"local_endpoints,omitempty"`
}

// USBDevice describes a USB device
type USBDevice struct {
	BusID         string `json:"bus_id"`
	BusNum        uint32 `json:"bus_num"`
	DevNum        uint32 `json:"dev_num"`
	Speed         uint32 `json:"speed"`
	VendorID      string `json:"vendor_id"`
	ProductID     string `json:"product_id"`
	DeviceBCD     string `json:"device_bcd,omitempty"`
	Class         uint8  `json:"class"`
	SubClass      uint8  `json:"sub_class"`
	Protocol      uint8  `json:"protocol"`
	Name          string `json:"name"`
	Manufacturer  string `json:"manufacturer,omitempty"`
	NumInterfaces uint8  `json:"num_interfaces"`
	Status        string `json:"status"`
	UsedBy        string `json:"used_by,omitempty"`
}

// DeviceList is sent by share clients to announce available devices
type DeviceList struct {
	Type             string      `json:"type"`
	ClientID         string      `json:"client_id"`
	ClientName       string      `json:"client_name"`
	Devices          []USBDevice `json:"devices"`
	AllowForceDetach bool        `json:"allow_force_detach,omitempty"`
}

// RequestDevice is sent by use clients to request a specific device
type RequestDevice struct {
	Type         string `json:"type"`
	TargetClient string `json:"target_client"`
	BusID        string `json:"bus_id"`
	RequestID    string `json:"request_id"`
}

// DeviceGranted is sent by share clients when a device is ready
type DeviceGranted struct {
	Type      string `json:"type"`
	BusID     string `json:"bus_id"`
	TunnelID  string `json:"tunnel_id"`
	RequestID string `json:"request_id"`
	DevID     uint32 `json:"dev_id"` // (busnum << 16) | devnum
	Speed     uint32 `json:"speed"`

	// Endpoints are addresses at which the granting client accepts a direct
	// tunnel connection for this device. The client contributes its own
	// interface addresses; the relay appends the public address it sees,
	// which is the only part neither peer can determine for itself.
	Endpoints []string `json:"endpoints,omitempty"`

	// Encrypted reports whether the granting client will encrypt tunnel
	// frames. It is false only for clients configured with a bare group hash
	// and no tokens, which cannot derive the key.
	Encrypted bool `json:"encrypted,omitempty"`
}

// DeviceDenied is sent when a device request is rejected
type DeviceDenied struct {
	Type      string `json:"type"`
	BusID     string `json:"bus_id"`
	RequestID string `json:"request_id"`
	Reason    string `json:"reason"`
}

// ReleaseDevice is sent by use clients to release a device
type ReleaseDevice struct {
	Type         string `json:"type"`
	TargetClient string `json:"target_client"`
	BusID        string `json:"bus_id"`
}

// DeviceReleased is sent when a device is released
type DeviceReleased struct {
	Type     string `json:"type"`
	BusID    string `json:"bus_id"`
	ClientID string `json:"client_id,omitempty"`
}

// ForceRelease is sent by use clients to force-release a device from another user
type ForceRelease struct {
	Type         string `json:"type"`
	TargetClient string `json:"target_client"`
	BusID        string `json:"bus_id"`
}

// ClientJoined is broadcast when a new client joins the group
type ClientJoined struct {
	Type     string `json:"type"`
	ClientID string `json:"client_id"`
	Mode     string `json:"mode"`
	Name     string `json:"name"`
}

// ClientLeft is broadcast when a client leaves the group
type ClientLeft struct {
	Type     string `json:"type"`
	ClientID string `json:"client_id"`
}

// Ping/Pong for keepalive
type Ping struct {
	Type string `json:"type"`
}

type Pong struct {
	Type string `json:"type"`
}

// Error message
type ErrorMsg struct {
	Type    string `json:"type"`
	Message string `json:"message"`
}

// TunnelHeader is prepended to binary WebSocket frames for tunnel data.
// Format: [16 bytes UUID][payload]
const TunnelHeaderSize = 16

// ShortID truncates an identifier for logging without panicking on short or
// empty input. Slicing IDs directly is a real hazard here: a client that
// registers with an empty hash would otherwise take down the relay.
func ShortID(id string) string {
	if len(id) <= 8 {
		return id
	}
	return id[:8]
}
