// Backwards-compatible protocol version.
const VERSION = 1;

// From the frontend to the relay.
struct CommandMessage {
	// The command sequence number will be repeated in responses
	// in the respective fields.
	u32 command_seq;
	union CommandData switch (enum Command {
		HELLO,
		ACTIVE,
		BUFFER_ACTIVATE,
		BUFFER_INPUT,
		BUFFER_TOGGLE_UNIMPORTANT,
		PING_RESPONSE,
		PING,
		BUFFER_COMPLETE,
		BUFFER_LOG,
	} command) {
	// If the version check succeeds, the client will receive
	// an initial stream of SERVER_UPDATE, BUFFER_UPDATE, BUFFER_STATS,
	// BUFFER_LINE, and finally a BUFFER_ACTIVATE message.
	case HELLO:
		u32 version;
	case ACTIVE:
		void;
	case BUFFER_ACTIVATE:
		string buffer_name;
	case BUFFER_INPUT:
		string buffer_name;
		string text;
	// XXX: Perhaps this should rather be handled through a /buffer command.
	case BUFFER_TOGGLE_UNIMPORTANT:
		string buffer_name;
	case PING_RESPONSE:
		u32 event_seq;

	// Only these commands may produce Event.RESPONSE, as below,
	// but any command may produce an error.
	case PING:
		void;
	case BUFFER_COMPLETE:
		string buffer_name;
		string text;
		u32 position;
	case BUFFER_LOG:
		string buffer_name;
	} data;
};

// From the relay to the frontend.
struct EventMessage {
	u32 event_seq;
	union EventData switch (enum Event {
		PING,
		BUFFER_LINE,
		BUFFER_UPDATE,
		BUFFER_STATS,
		BUFFER_RENAME,
		BUFFER_REMOVE,
		BUFFER_ACTIVATE,
		BUFFER_INPUT,
		BUFFER_CLEAR,
		SERVER_UPDATE,
		SERVER_RENAME,
		SERVER_REMOVE,
		ERROR,
		RESPONSE,
	} event) {
	case PING:
		void;

	case BUFFER_LINE:
		string buffer_name;
		// Whether the line should also be displayed in the active buffer.
		bool leak_to_active;
		bool is_unimportant;
		bool is_highlight;
		enum Rendition {
			BARE,
			INDENT,
			STATUS,
			ERROR,
			JOIN,
			PART,
			ACTION,
		} rendition;
		// Unix timestamp in milliseconds.
		u64 when;
		// Broken-up text, with in-band formatting.
		union ItemData switch (enum Item {
			TEXT,
			RESET,
			FG_COLOR,
			BG_COLOR,
			FLIP_BOLD,
			FLIP_ITALIC,
			FLIP_UNDERLINE,
			FLIP_INVERSE,
			FLIP_CROSSED_OUT,
			FLIP_MONOSPACE,
		} kind) {
		case TEXT:
			string text;
		case RESET:
			void;
		case FG_COLOR:
			i16 color;
		case BG_COLOR:
			i16 color;
		case FLIP_BOLD:
		case FLIP_ITALIC:
		case FLIP_UNDERLINE:
		case FLIP_INVERSE:
		case FLIP_CROSSED_OUT:
		case FLIP_MONOSPACE:
			void;
		} items<>;
	case BUFFER_UPDATE:
		string buffer_name;
		bool hide_unimportant;
		union BufferContext switch (enum BufferKind {
			GLOBAL,
			SERVER,
			CHANNEL,
			PRIVATE_MESSAGE,
		} kind) {
		case GLOBAL:
			void;
		case SERVER:
			string server_name;
		case CHANNEL:
			string server_name;
			ItemData topic<>;
			// This includes parameters, separated by spaces.
			string modes;
		case PRIVATE_MESSAGE:
			string server_name;
		} context;
	case BUFFER_STATS:
		string buffer_name;
		// These are cumulative, even for lines flushed out from buffers.
		// Updates to these values aren't broadcasted, thus handle:
		//  - BUFFER_LINE by bumping/setting them as appropriate,
		//  - BUFFER_ACTIVATE by clearing them for the previous buffer
		//    (this way, they can be used to mark unread messages).
		u32 new_messages;
		u32 new_unimportant_messages;
		bool highlighted;
	case BUFFER_RENAME:
		string buffer_name;
		string new;
	case BUFFER_REMOVE:
		string buffer_name;
	case BUFFER_ACTIVATE:
		string buffer_name;
	case BUFFER_INPUT:
		string buffer_name;
		string text;
	case BUFFER_CLEAR:
		string buffer_name;

	case SERVER_UPDATE:
		string server_name;
		union ServerData switch (enum ServerState {
			DISCONNECTED,
			CONNECTING,
			CONNECTED,
			REGISTERED,
			DISCONNECTING,
		} state) {
		case DISCONNECTED:
		case CONNECTING:
		case CONNECTED:
			void;
		case REGISTERED:
			string user;
			string user_modes;
		// Theoretically, we could also send user information in this state,
		// but we'd have to duplicate both fields.
		case DISCONNECTING:
			void;
		} data;
	case SERVER_RENAME:
		// Buffers aren't sent updates for in this circumstance,
		// as that wouldn't be sufficiently atomic anyway.
		string server_name;
		string new;
	case SERVER_REMOVE:
		string server_name;

	// Restriction: command_seq strictly follows the sequence received
	// by the relay, across both of these replies.
	case ERROR:
		u32 command_seq;
		string error;
	case RESPONSE:
		u32 command_seq;
		union ResponseData switch (Command command) {
		case PING:
			void;
		case BUFFER_COMPLETE:
			u32 start;
			string completions<>;
		case BUFFER_LOG:
			// UTF-8, but not guaranteed.
			u8 log<>;
		} data;
	} data;
};