/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.

For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "calls/group/calls_group_viewport.h"

#include "calls/group/calls_group_viewport_tile.h"
#include "calls/group/calls_group_viewport_opengl.h"
#include "calls/group/calls_group_viewport_raster.h"
#include "calls/group/calls_group_common.h"
#include "calls/group/calls_group_call.h"
#include "calls/group/calls_group_members_row.h"
#include "media/view/media_view_pip.h"
#include "base/platform/base_platform_info.h"
#include "webrtc/webrtc_video_track.h"
#include "ui/integration.h"
#include "ui/painter.h"
#include "ui/abstract_button.h"
#include "ui/gl/gl_surface.h"
#include "ui/effects/animations.h"
#include "ui/effects/cross_line.h"
#include "data/data_group_call.h" // MuteButtonTooltip.
#include "lang/lang_keys.h"
#include "styles/style_calls.h"

#include <QtGui/QtEvents>
#include <QOpenGLShader>

namespace Calls::Group {
namespace {

[[nodiscard]] QRect InterpolateRect(QRect a, QRect b, float64 ratio) {
	const auto left = anim::interpolate(a.x(), b.x(), ratio);
	const auto top = anim::interpolate(a.y(), b.y(), ratio);
	const auto right = anim::interpolate(
		a.x() + a.width(),
		b.x() + b.width(),
		ratio);
	const auto bottom = anim::interpolate(
		a.y() + a.height(),
		b.y() + b.height(),
		ratio);
	return { left, top, right - left, bottom - top };
}

} // namespace

Viewport::Viewport(
	not_null<QWidget*> parent,
	PanelMode mode,
	Ui::GL::Backend backend)
: _mode(mode)
, _content(Ui::GL::CreateSurface(parent, chooseRenderer(backend))) {
	setup();
}

Viewport::~Viewport() = default;

not_null<QWidget*> Viewport::widget() const {
	return _content->rpWidget();
}

not_null<Ui::RpWidgetWrap*> Viewport::rp() const {
	return _content.get();
}

void Viewport::setup() {
	const auto raw = widget();

	raw->resize(0, 0);
	raw->setAttribute(Qt::WA_OpaquePaintEvent);
	raw->setMouseTracking(true);

	_content->sizeValue(
	) | rpl::filter([=] {
		return wide();
	}) | rpl::start_with_next([=] {
		updateTilesGeometry();
	}, lifetime());

	_content->events(
	) | rpl::start_with_next([=](not_null<QEvent*> e) {
		const auto type = e->type();
		if (type == QEvent::Enter) {
			Ui::Integration::Instance().registerLeaveSubscription(raw);
			_mouseInside = true;
		} else if (type == QEvent::Leave) {
			Ui::Integration::Instance().unregisterLeaveSubscription(raw);
			setSelected({});
			_mouseInside = false;
		} else if (type == QEvent::MouseButtonPress) {
			handleMousePress(
				static_cast<QMouseEvent*>(e.get())->pos(),
				static_cast<QMouseEvent*>(e.get())->button());
		} else if (type == QEvent::MouseButtonRelease) {
			handleMouseRelease(
				static_cast<QMouseEvent*>(e.get())->pos(),
				static_cast<QMouseEvent*>(e.get())->button());
		} else if (type == QEvent::MouseMove) {
			handleMouseMove(static_cast<QMouseEvent*>(e.get())->pos());
		}
	}, lifetime());
}

void Viewport::setGeometry(bool fullscreen, QRect geometry) {
	Expects(wide());

	const auto changed = (_fullscreen != fullscreen);
	if (changed) {
		_fullscreen = fullscreen;
	}
	if (widget()->geometry() != geometry) {
		_geometryStaleAfterModeChange = false;
		widget()->setGeometry(geometry);
	} else if (_geometryStaleAfterModeChange || changed) {
		_geometryStaleAfterModeChange = false;
		updateTilesGeometry();
	}
}

void Viewport::resizeToWidth(int width) {
	Expects(!wide());

	updateTilesGeometry(width);
}

void Viewport::setScrollTop(int scrollTop) {
	if (_scrollTop == scrollTop) {
		return;
	}
	_scrollTop = scrollTop;
	updateTilesGeometry();
}

bool Viewport::wide() const {
	return (_mode == PanelMode::Wide);
}

void Viewport::setMode(PanelMode mode, not_null<QWidget*> parent) {
	if (_mode == mode && widget()->parent() == parent) {
		return;
	}
	_mode = mode;
	_scrollTop = 0;
	setControlsShown(1.);
	if (widget()->parent() != parent) {
		const auto hidden = widget()->isHidden();
		widget()->setParent(parent);
		if (!hidden) {
			widget()->show();
		}
	}
	if (!wide()) {
		for (const auto &tile : _tiles) {
			tile->toggleTopControlsShown(false);
		}
	} else if (_selected.tile) {
		_selected.tile->toggleTopControlsShown(true);
	}
}

void Viewport::handleMousePress(QPoint position, Qt::MouseButton button) {
	handleMouseMove(position);
	setPressed(_selected);
}

void Viewport::handleMouseRelease(QPoint position, Qt::MouseButton button) {
	handleMouseMove(position);
	const auto pressed = _pressed;
	setPressed({});
	if (const auto tile = pressed.tile) {
		if (pressed == _selected) {
			if (button == Qt::RightButton) {
				tile->row()->showContextMenu();
			} else if (!wide()
				|| (_hasTwoOrMore && !_large)
				|| pressed.element != Selection::Element::PinButton) {
				_clicks.fire_copy(tile->endpoint());
			} else if (pressed.element == Selection::Element::PinButton) {
				_pinToggles.fire(!tile->pinned());
			}
		}
	}
}

void Viewport::handleMouseMove(QPoint position) {
	updateSelected(position);
}

void Viewport::updateSelected(QPoint position) {
	if (!widget()->rect().contains(position)) {
		setSelected({});
		return;
	}
	for (const auto &tile : _tiles) {
		const auto geometry = tile->visible()
			? tile->geometry()
			: QRect();
		if (geometry.contains(position)) {
			const auto pin = wide()
				&& tile->pinOuter().contains(position - geometry.topLeft());
			const auto back = wide()
				&& tile->backOuter().contains(position - geometry.topLeft());
			setSelected({
				.tile = tile.get(),
				.element = (pin
					? Selection::Element::PinButton
					: back
					? Selection::Element::BackButton
					: Selection::Element::Tile),
			});
			return;
		}
	}
	setSelected({});
}

void Viewport::updateSelected() {
	updateSelected(widget()->mapFromGlobal(QCursor::pos()));
}

void Viewport::setControlsShown(float64 shown) {
	_controlsShownRatio = shown;
	widget()->update();
}

void Viewport::setCursorShown(bool shown) {
	if (_cursorHidden == shown) {
		_cursorHidden = !shown;
		updateCursor();
	}
}

void Viewport::add(
		const VideoEndpoint &endpoint,
		VideoTileTrack track,
		rpl::producer<QSize> trackSize,
		rpl::producer<bool> pinned,
		bool self) {
	_tiles.push_back(std::make_unique<VideoTile>(
		endpoint,
		track,
		std::move(trackSize),
		std::move(pinned),
		[=] { widget()->update(); },
		self));

	_tiles.back()->trackSizeValue(
	) | rpl::filter([](QSize size) {
		return !size.isEmpty();
	}) | rpl::start_with_next([=] {
		updateTilesGeometry();
	}, _tiles.back()->lifetime());

	_tiles.back()->track()->stateValue(
	) | rpl::start_with_next([=] {
		updateTilesGeometry();
	}, _tiles.back()->lifetime());
}

void Viewport::remove(const VideoEndpoint &endpoint) {
	const auto i = ranges::find(_tiles, endpoint, &VideoTile::endpoint);
	if (i == end(_tiles)) {
		return;
	}
	const auto removing = i->get();
	const auto largeRemoved = (_large == removing);
	if (largeRemoved) {
		prepareLargeChangeAnimation();
		_large = nullptr;
	}
	if (_selected.tile == removing) {
		setSelected({});
	}
	if (_pressed.tile == removing) {
		setPressed({});
	}
	for (auto &geometry : _startTilesLayout.list) {
		if (geometry.tile == removing) {
			geometry.tile = nullptr;
		}
	}
	for (auto &geometry : _finishTilesLayout.list) {
		if (geometry.tile == removing) {
			geometry.tile = nullptr;
		}
	}
	_tiles.erase(i);
	if (largeRemoved) {
		startLargeChangeAnimation();
	} else {
		updateTilesGeometry();
	}
}

void Viewport::prepareLargeChangeAnimation() {
	if (!wide()) {
		return;
	} else if (_largeChangeAnimation.animating()) {
		updateTilesAnimated();
		const auto field = _finishTilesLayout.useColumns
			? &Geometry::columns
			: &Geometry::rows;
		for (auto &finish : _finishTilesLayout.list) {
			const auto tile = finish.tile;
			if (!tile) {
				continue;
			}
			finish.*field = tile->geometry();
		}
		_startTilesLayout = std::move(_finishTilesLayout);
		_largeChangeAnimation.stop();

		_startTilesLayout.list.erase(
			ranges::remove(_startTilesLayout.list, nullptr, &Geometry::tile),
			end(_startTilesLayout.list));
	} else {
		_startTilesLayout = applyLarge(std::move(_startTilesLayout));
	}
}

void Viewport::startLargeChangeAnimation() {
	Expects(!_largeChangeAnimation.animating());

	if (!wide()
		|| anim::Disabled()
		|| (_startTilesLayout.list.size() < 2)
		|| !_opengl
		|| widget()->size().isEmpty()) {
		updateTilesGeometry();
		return;
	}
	_finishTilesLayout = applyLarge(
		countWide(widget()->width(), widget()->height()));
	if (_finishTilesLayout.list.empty()
		|| _finishTilesLayout.outer != _startTilesLayout.outer) {
		updateTilesGeometry();
		return;
	}
	_largeChangeAnimation.start(
		[=] { updateTilesAnimated(); },
		0.,
		1.,
		st::slideDuration);
}

Viewport::Layout Viewport::applyLarge(Layout layout) const {
	auto &list = layout.list;
	if (!_large) {
		return layout;
	}
	const auto i = ranges::find(list, _large, &Geometry::tile);
	if (i == end(list)) {
		return layout;
	}
	const auto field = layout.useColumns
		? &Geometry::columns
		: &Geometry::rows;
	const auto fullWidth = layout.outer.width();
	const auto fullHeight = layout.outer.height();
	const auto largeRect = (*i).*field;
	const auto largeLeft = largeRect.x();
	const auto largeTop = largeRect.y();
	const auto largeRight = largeLeft + largeRect.width();
	const auto largeBottom = largeTop + largeRect.height();
	for (auto &geometry : list) {
		if (geometry.tile == _large) {
			geometry.*field = { QPoint(), layout.outer };
		} else if (layout.useColumns) {
			auto &rect = geometry.columns;
			const auto center = rect.center();
			if (center.x() < largeLeft) {
				rect = rect.translated(-largeLeft, 0);
			} else if (center.x() > largeRight) {
				rect = rect.translated(fullWidth - largeRight, 0);
			} else if (center.y() < largeTop) {
				rect = QRect(
					0,
					rect.y() - largeTop,
					fullWidth,
					rect.height());
			} else if (center.y() > largeBottom) {
				rect = QRect(
					0,
					rect.y() + (fullHeight - largeBottom),
					fullWidth,
					rect.height());
			}
		} else {
			auto &rect = geometry.rows;
			const auto center = rect.center();
			if (center.y() < largeTop) {
				rect = rect.translated(0, -largeTop);
			} else if (center.y() > largeBottom) {
				rect = rect.translated(0, fullHeight - largeBottom);
			} else if (center.x() < largeLeft) {
				rect = QRect(
					rect.x() - largeLeft,
					0,
					rect.width(),
					fullHeight);
			} else {
				rect = QRect(
					rect.x() + (fullWidth - largeRight),
					0,
					rect.width(),
					fullHeight);
			}
		}
	}
	return layout;
}

void Viewport::updateTilesAnimated() {
	if (!_largeChangeAnimation.animating()) {
		updateTilesGeometry();
		return;
	}
	const auto ratio = _largeChangeAnimation.value(1.);
	const auto field = _finishTilesLayout.useColumns
		? &Geometry::columns
		: &Geometry::rows;
	for (const auto &finish : _finishTilesLayout.list) {
		const auto tile = finish.tile;
		if (!tile) {
			continue;
		}
		const auto i = ranges::find(
			_startTilesLayout.list,
			tile,
			&Geometry::tile);
		if (i == end(_startTilesLayout.list)) {
			LOG(("Tiles Animation Error 1!"));
			_largeChangeAnimation.stop();
			updateTilesGeometry();
			return;
		}
		const auto from = (*i).*field;
		const auto to = finish.*field;
		tile->setGeometry(
			InterpolateRect(from, to, ratio),
			TileAnimation{ from.size(), to.size(), ratio });
	}
	widget()->update();
}

Viewport::Layout Viewport::countWide(int outerWidth, int outerHeight) const {
	auto result = Layout{ .outer = QSize(outerWidth, outerHeight) };
	auto &sizes = result.list;
	sizes.reserve(_tiles.size());
	for (const auto &tile : _tiles) {
		const auto video = tile.get();
		const auto size = video->trackOrUserpicSize();
		if (!size.isEmpty()) {
			sizes.push_back(Geometry{ video, size });
		}
	}
	if (sizes.empty()) {
		return result;
	} else if (sizes.size() == 1) {
		sizes.front().rows = { 0, 0, outerWidth, outerHeight };
		return result;
	}

	auto columnsBlack = uint64();
	auto rowsBlack = uint64();
	const auto count = int(sizes.size());
	const auto skip = st::groupCallVideoLargeSkip;
	const auto slices = int(std::ceil(std::sqrt(float64(count))));
	{
		auto index = 0;
		const auto columns = slices;
		const auto sizew = (outerWidth + skip) / float64(columns);
		for (auto column = 0; column != columns; ++column) {
			const auto left = int(base::SafeRound(column * sizew));
			const auto width = int(
				base::SafeRound(column * sizew + sizew - skip)) - left;
			const auto rows = int(base::SafeRound((count - index)
				/ float64(columns - column)));
			const auto sizeh = (outerHeight + skip) / float64(rows);
			for (auto row = 0; row != rows; ++row) {
				const auto top = int(base::SafeRound(row * sizeh));
				const auto height = int(base::SafeRound(
					row * sizeh + sizeh - skip)) - top;
				auto &geometry = sizes[index];
				geometry.columns = {
					left,
					top,
					width,
					height };
				const auto scaled = geometry.size.scaled(
					width,
					height,
					Qt::KeepAspectRatio);
				columnsBlack += (scaled.width() < width)
					? (width - scaled.width()) * height
					: (height - scaled.height()) * width;
				++index;
			}
		}
	}
	{
		auto index = 0;
		const auto rows = slices;
		const auto sizeh = (outerHeight + skip) / float64(rows);
		for (auto row = 0; row != rows; ++row) {
			const auto top = int(base::SafeRound(row * sizeh));
			const auto height = int(
				base::SafeRound(row * sizeh + sizeh - skip)) - top;
			const auto columns = int(base::SafeRound((count - index)
				/ float64(rows - row)));
			const auto sizew = (outerWidth + skip) / float64(columns);
			for (auto column = 0; column != columns; ++column) {
				const auto left = int(base::SafeRound(column * sizew));
				const auto width = int(base::SafeRound(
					column * sizew + sizew - skip)) - left;
				auto &geometry = sizes[index];
				geometry.rows = {
					left,
					top,
					width,
					height };
				const auto scaled = geometry.size.scaled(
					width,
					height,
					Qt::KeepAspectRatio);
				rowsBlack += (scaled.width() < width)
					? (width - scaled.width()) * height
					: (height - scaled.height()) * width;
				++index;
			}
		}
	}
	result.useColumns = (columnsBlack < rowsBlack);
	return result;
}

void Viewport::showLarge(const VideoEndpoint &endpoint) {
	// If a video get's switched off, GroupCall first unpins it,
	// then removes it from Large endpoint, then removes from active tracks.
	//
	// If we want to animate large video removal properly, we need to
	// delay this update and start animation directly from removing of the
	// track from the active list. Otherwise final state won't be correct.
	_updateLargeScheduled = [=] {
		const auto i = ranges::find(_tiles, endpoint, &VideoTile::endpoint);
		const auto large = (i != end(_tiles)) ? i->get() : nullptr;
		if (_large != large) {
			prepareLargeChangeAnimation();
			_large = large;
			updateTopControlsVisibility();
			startLargeChangeAnimation();
		}

		Ensures(!_large || !_large->trackOrUserpicSize().isEmpty());
	};
	crl::on_main(widget(), [=] {
		if (!_updateLargeScheduled) {
			return;
		}
		base::take(_updateLargeScheduled)();
	});
}

void Viewport::updateTilesGeometry() {
	updateTilesGeometry(widget()->width());
}

void Viewport::updateTilesGeometry(int outerWidth) {
	const auto mouseInside = _mouseInside.current();
	const auto guard = gsl::finally([&] {
		if (mouseInside) {
			updateSelected();
		}
		widget()->update();
	});

	const auto outerHeight = widget()->height();
	if (_tiles.empty() || !outerWidth) {
		_fullHeight = 0;
		return;
	}

	if (wide()) {
		updateTilesGeometryWide(outerWidth, outerHeight);
		refreshHasTwoOrMore();
		_fullHeight = 0;
	} else {
		updateTilesGeometryNarrow(outerWidth);
	}
}

void Viewport::refreshHasTwoOrMore() {
	auto hasTwoOrMore = false;
	auto oneFound = false;
	for (const auto &tile : _tiles) {
		if (!tile->trackOrUserpicSize().isEmpty()) {
			if (oneFound) {
				hasTwoOrMore = true;
				break;
			}
			oneFound = true;
		}
	}
	if (_hasTwoOrMore == hasTwoOrMore) {
		return;
	}
	_hasTwoOrMore = hasTwoOrMore;
	updateCursor();
	updateTopControlsVisibility();
}

void Viewport::updateTopControlsVisibility() {
	if (_selected.tile) {
		_selected.tile->toggleTopControlsShown(
			_hasTwoOrMore && wide() && _large && _large == _selected.tile);
	}
}

void Viewport::updateTilesGeometryWide(int outerWidth, int outerHeight) {
	if (!outerHeight) {
		return;
	} else if (_largeChangeAnimation.animating()) {
		if (_startTilesLayout.outer == QSize(outerWidth, outerHeight)) {
			return;
		}
		_largeChangeAnimation.stop();
	}

	_startTilesLayout = countWide(outerWidth, outerHeight);
	if (_large && !_large->trackOrUserpicSize().isEmpty()) {
		for (const auto &geometry : _startTilesLayout.list) {
			if (geometry.tile == _large) {
				setTileGeometry(_large, { 0, 0, outerWidth, outerHeight });
			} else {
				geometry.tile->hide();
			}
		}
	} else {
		const auto field = _startTilesLayout.useColumns
			? &Geometry::columns
			: &Geometry::rows;
		for (const auto &geometry : _startTilesLayout.list) {
			if (const auto video = geometry.tile) {
				setTileGeometry(video, geometry.*field);
			}
		}
	}
}

void Viewport::updateTilesGeometryNarrow(int outerWidth) {
	if (outerWidth <= st::groupCallNarrowMembersWidth) {
		updateTilesGeometryColumn(outerWidth);
		return;
	}

	const auto y = -_scrollTop;
	auto sizes = base::flat_map<not_null<VideoTile*>, QSize>();
	sizes.reserve(_tiles.size());
	for (const auto &tile : _tiles) {
		const auto video = tile.get();
		const auto size = video->trackOrUserpicSize();
		if (size.isEmpty()) {
			video->hide();
		} else {
			sizes.emplace(video, size);
		}
	}
	if (sizes.empty()) {
		_fullHeight = 0;
		return;
	} else if (sizes.size() == 1) {
		const auto size = sizes.front().second;
		const auto heightMin = (outerWidth * 9) / 16;
		const auto heightMax = (outerWidth * 3) / 4;
		const auto scaled = size.scaled(
			QSize(outerWidth, heightMax),
			Qt::KeepAspectRatio);
		const auto height = std::max(scaled.height(), heightMin);
		const auto skip = st::groupCallVideoSmallSkip;
		setTileGeometry(sizes.front().first, { 0, y, outerWidth, height });
		_fullHeight = height + skip;
		return;
	}
	const auto min = (st::groupCallWidth
		- st::groupCallMembersMargin.left()
		- st::groupCallMembersMargin.right()
		- st::groupCallVideoSmallSkip) / 2;
	const auto square = (outerWidth - st::groupCallVideoSmallSkip) / 2;
	const auto skip = (outerWidth - 2 * square);
	const auto put = [&](not_null<VideoTile*> tile, int column, int row) {
		setTileGeometry(tile, {
			(column == 2) ? 0 : column ? (outerWidth - square) : 0,
			y + row * (min + skip),
			(column == 2) ? outerWidth : square,
			min,
		});
	};
	const auto rows = (sizes.size() + 1) / 2;
	if (sizes.size() == 3) {
		put(sizes.front().first, 2, 0);
		put((sizes.begin() + 1)->first, 0, 1);
		put((sizes.begin() + 2)->first, 1, 1);
	} else {
		auto row = 0;
		auto column = 0;
		for (const auto &[video, endpoint] : sizes) {
			put(video, column, row);
			if (column) {
				++row;
				column = (row + 1 == rows && sizes.size() % 2) ? 2 : 0;
			} else {
				column = 1;
			}
		}
	}
	_fullHeight = rows * (min + skip);
}

void Viewport::updateTilesGeometryColumn(int outerWidth) {
	const auto y = -_scrollTop;
	auto top = 0;
	const auto layoutNext = [&](not_null<VideoTile*> tile) {
		const auto size = tile->trackOrUserpicSize();
		const auto shown = !size.isEmpty() && _large && tile != _large;
		const auto height = st::groupCallNarrowVideoHeight;
		if (!shown) {
			tile->hide();
		} else {
			setTileGeometry(tile, { 0, y + top, outerWidth, height });
			top += height + st::groupCallVideoSmallSkip;
		}
	};
	const auto topPeer = _large ? _large->row()->peer().get() : nullptr;
	const auto reorderNeeded = [&] {
		if (!topPeer) {
			return false;
		}
		for (const auto &tile : _tiles) {
			if (tile.get() != _large && tile->row()->peer() == topPeer) {
				return (tile.get() != _tiles.front().get())
					&& !tile->trackOrUserpicSize().isEmpty();
			}
		}
		return false;
	}();
	if (reorderNeeded) {
		_tilesForOrder.clear();
		_tilesForOrder.reserve(_tiles.size());
		for (const auto &tile : _tiles) {
			_tilesForOrder.push_back(tile.get());
		}
		ranges::stable_partition(
			_tilesForOrder,
			[&](not_null<VideoTile*> tile) {
				return (tile->row()->peer() == topPeer);
			});
		for (const auto &tile : _tilesForOrder) {
			layoutNext(tile);
		}
	} else {
		for (const auto &tile : _tiles) {
			layoutNext(tile.get());
		}
	}
	_fullHeight = top;
}

void Viewport::setTileGeometry(not_null<VideoTile*> tile, QRect geometry) {
	tile->setGeometry(geometry);

	const auto min = std::min(geometry.width(), geometry.height());
	const auto kMedium = style::ConvertScale(540);
	const auto kSmall = style::ConvertScale(240);
	const auto &endpoint = tile->endpoint();
	const auto forceThumbnailQuality = !wide()
		&& (ranges::count(_tiles, false, &VideoTile::hidden) > 1);
	const auto forceFullQuality = wide() && (tile.get() == _large);
	const auto quality = forceThumbnailQuality
		? VideoQuality::Thumbnail
		: (forceFullQuality || min >= kMedium)
		? VideoQuality::Full
		: (min >= kSmall)
		? VideoQuality::Medium
		: VideoQuality::Thumbnail;
	if (tile->updateRequestedQuality(quality)) {
		_qualityRequests.fire(VideoQualityRequest{
			.endpoint = endpoint,
			.quality = quality,
		});
	}
}

void Viewport::setSelected(Selection value) {
	if (_selected == value) {
		return;
	}
	if (_selected.tile) {
		_selected.tile->toggleTopControlsShown(false);
	}
	_selected = value;
	updateTopControlsVisibility();
	updateCursor();
}

void Viewport::updateCursor() {
	const auto pointer = _selected.tile && (!wide() || _hasTwoOrMore);
	widget()->setCursor(_cursorHidden
		? Qt::BlankCursor
		: pointer
		? style::cur_pointer
		: style::cur_default);
}

void Viewport::setPressed(Selection value) {
	if (_pressed == value) {
		return;
	}
	_pressed = value;
}

Ui::GL::ChosenRenderer Viewport::chooseRenderer(Ui::GL::Backend backend) {
	_opengl = (backend == Ui::GL::Backend::OpenGL);
	return {
		.renderer = (_opengl
			? std::unique_ptr<Ui::GL::Renderer>(
				std::make_unique<RendererGL>(this))
			: std::make_unique<RendererSW>(this)),
		.backend = backend,
	};
}

bool Viewport::requireARGB32() const {
	return !_opengl;
}

int Viewport::fullHeight() const {
	return _fullHeight.current();
}

rpl::producer<int> Viewport::fullHeightValue() const {
	return _fullHeight.value();
}

rpl::producer<bool> Viewport::pinToggled() const {
	return _pinToggles.events();
}

rpl::producer<VideoEndpoint> Viewport::clicks() const {
	return _clicks.events();
}

rpl::producer<VideoQualityRequest> Viewport::qualityRequests() const {
	return _qualityRequests.events();
}

rpl::producer<bool> Viewport::mouseInsideValue() const {
	return _mouseInside.value();
}

rpl::lifetime &Viewport::lifetime() {
	return _content->lifetime();
}

rpl::producer<QString> MuteButtonTooltip(not_null<GroupCall*> call) {
	//return rpl::single(std::make_tuple(
	//	(Data::GroupCall*)nullptr,
	//	call->scheduleDate()
	//)) | rpl::then(call->real(
	//) | rpl::map([](not_null<Data::GroupCall*> real) {
	//	using namespace rpl::mappers;
	//	return real->scheduleDateValue(
	//	) | rpl::map([=](TimeId scheduleDate) {
	//		return std::make_tuple(real.get(), scheduleDate);
	//	});
	//}) | rpl::flatten_latest(
	//)) | rpl::map([=](
	//		Data::GroupCall *real,
	//		TimeId scheduleDate) -> rpl::producer<QString> {
	//	if (scheduleDate) {
	//		return rpl::combine(
	//			call->canManageValue(),
	//			(real
	//				? real->scheduleStartSubscribedValue()
	//				: rpl::single(false))
	//		) | rpl::map([](bool canManage, bool subscribed) {
	//			return canManage
	//				? tr::lng_group_call_start_now()
	//				: subscribed
	//				? tr::lng_group_call_cancel_reminder()
	//				: tr::lng_group_call_set_reminder();
	//		}) | rpl::flatten_latest();
	//	}
	if (call->rtmp()) {
		return nullptr;
	}
		return call->mutedValue(
		) | rpl::map([](MuteState muted) {
			switch (muted) {
			case MuteState::Active:
			case MuteState::PushToTalk:
				return tr::lng_group_call_you_are_live();
			case MuteState::ForceMuted:
				return tr::lng_group_call_tooltip_force_muted();
			case MuteState::RaisedHand:
				return tr::lng_group_call_tooltip_raised_hand();
			case MuteState::Muted:
				return tr::lng_group_call_tooltip_microphone();
			}
			Unexpected("Value in MuteState in showNiceTooltip.");
		}) | rpl::flatten_latest();
	//}) | rpl::flatten_latest();
}

} // namespace Calls::Group
