/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org

Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.

Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "playerwidget.h"

#include "shortcuts.h"
#include "lang.h"
#include "boxes/addcontactbox.h"
#include "application.h"
#include "mainwindow.h"
#include "playerwidget.h"
#include "mainwidget.h"
#include "localstorage.h"
#include "media/media_audio.h"

PlayerWidget::PlayerWidget(QWidget *parent) : TWidget(parent)
, _a_state(animation(this, &PlayerWidget::step_state))
, _a_progress(animation(this, &PlayerWidget::step_progress)) {
	resize(st::wndMinWidth, st::playerHeight);
	setMouseTracking(true);
	memset(_stateHovers, 0, sizeof(_stateHovers));
}

void PlayerWidget::paintEvent(QPaintEvent *e) {
	Painter p(this);

	QRect r(e->rect()), checkr(myrtlrect(r));
	p.fillRect(r, st::playerBg->b);

	if (!_playbackRect.contains(checkr)) {
		if (_fullAvailable && checkr.intersects(_prevRect)) {
			if (_prevAvailable) {
				float64 o = _stateHovers[OverPrev];
				p.setOpacity(o * 1. + (1. - o) * st::playerInactiveOpacity);
			} else {
				p.setOpacity(st::playerUnavailableOpacity);
			}
			p.drawSpriteCenterLeft(_prevRect, width(), st::playerPrev);
		}
		if (checkr.intersects(_playRect)) {
			float64 o = _stateHovers[OverPlay];
			p.setOpacity(o * 1. + (1. - o) * st::playerInactiveOpacity);
			p.drawSpriteCenterLeft(_playRect, width(), (_showPause || _down == OverPlayback) ? st::playerPause : st::playerPlay);
		}
		if (_fullAvailable && checkr.intersects(_nextRect)) {
			if (_nextAvailable) {
				float64 o = _stateHovers[OverNext];
				p.setOpacity(o * 1. + (1. - o) * st::playerInactiveOpacity);
			} else {
				p.setOpacity(st::playerUnavailableOpacity);
			}
			p.drawSpriteCenterLeft(_nextRect, width(), st::playerNext);
		}
		if (checkr.intersects(_closeRect)) {
			float64 o = _stateHovers[OverClose];
			p.setOpacity(o * 1. + (1. - o) * st::playerInactiveOpacity);
			p.drawSpriteCenterLeft(_closeRect, width(), st::playerClose);
		}
		if (checkr.intersects(_volumeRect)) {
			float64 o = _stateHovers[OverVolume];
			p.setOpacity(o * 1. + (1. - o) * st::playerInactiveOpacity);
			int32 top = _volumeRect.y() + (_volumeRect.height() - st::playerVolume.pxHeight()) / 2;
			int32 left = _volumeRect.x() + (_volumeRect.width() - st::playerVolume.pxWidth()) / 2;
			int32 mid = left + qRound(st::playerVolume.pxWidth() * Global::SongVolume());
			int32 right = left + st::playerVolume.pxWidth();
			if (rtl()) {
				left = width() - left;
				mid = width() - mid;
				right = width() - right;
				if (mid < left) {
					p.drawPixmap(QRect(mid, top, left - mid, st::playerVolume.pxHeight()), App::sprite(), QRect(st::playerVolume.rect().x() + (mid - right) * cIntRetinaFactor(), st::playerVolume.rect().y(), (left - mid) * cIntRetinaFactor(), st::playerVolume.pxHeight() * cIntRetinaFactor()));
				}
				if (right < mid) {
					p.setOpacity(st::playerUnavailableOpacity);
					p.drawPixmap(QRect(right, top, mid - right, st::playerVolume.pxHeight()), App::sprite(), QRect(st::playerVolume.rect().x(), st::playerVolume.rect().y(), (mid - right) * cIntRetinaFactor(), st::playerVolume.pxHeight() * cIntRetinaFactor()));
				}
			} else {
				if (mid > left) {
					p.drawPixmap(QRect(left, top, mid - left, st::playerVolume.pxHeight()), App::sprite(), QRect(st::playerVolume.rect().x(), st::playerVolume.rect().y(), (mid - left) * cIntRetinaFactor(), st::playerVolume.pxHeight() * cIntRetinaFactor()));
				}
				if (right > mid) {
					p.setOpacity(st::playerUnavailableOpacity);
					p.drawPixmap(QRect(mid, top, right - mid, st::playerVolume.pxHeight()), App::sprite(), QRect(st::playerVolume.rect().x() + (mid - left) * cIntRetinaFactor(), st::playerVolume.rect().y(), (right - mid) * cIntRetinaFactor(), st::playerVolume.pxHeight() * cIntRetinaFactor()));
				}
			}
		}
		if (_fullAvailable && checkr.intersects(_fullRect)) {
			float64 o = _stateHovers[OverFull];
			p.setOpacity(o * 1. + (1. - o) * st::playerInactiveOpacity);
			p.drawSpriteCenterLeft(_fullRect, width(), st::playerFull);
		}
		if (checkr.intersects(_repeatRect)) {
			float64 o = _stateHovers[OverRepeat];
			p.setOpacity(_repeat ? 1. : (o * st::playerInactiveOpacity + (1. - o) * st::playerUnavailableOpacity));
			p.drawSpriteCenterLeft(_repeatRect, width(), st::playerRepeat);
		}
		p.setOpacity(1.);

		p.setPen(st::playerTimeFg->p);
		p.setFont(st::linkFont->f);
		p.drawTextLeft(_infoRect.x() + _infoRect.width() - _timeWidth, _infoRect.y() + (_infoRect.height() - st::linkFont->height) / 2, width(), _time, _timeWidth);

		textstyleSet(&st::playerNameStyle);
		p.setPen(st::playerFg->p);
		_name.drawElided(p, _infoRect.x() + (rtl() ? (_timeWidth + st::playerSkip) : 0), _infoRect.y() + (_infoRect.height() - st::linkFont->height) / 2, _infoRect.width() - _timeWidth - st::playerSkip);
		textstyleRestore();
	}

	if (_duration) {
		float64 prg = (_down == OverPlayback) ? _downProgress : a_progress.current();
		int32 from = _playbackRect.x(), mid = qRound(_playbackRect.x() + prg * _playbackRect.width()), end = _playbackRect.x() + _playbackRect.width();
		if (mid > from) {
			p.fillRect(rtl() ? (width() - mid) : from, height() - st::playerLineHeight, mid - from, _playbackRect.height(), st::playerLineActive->b);
		}
		if (end > mid) {
			p.fillRect(rtl() ? (width() - end) : mid, height() - st::playerLineHeight, end - mid, st::playerLineHeight, st::playerLineInactive->b);
		}
		if (_stateHovers[OverPlayback] > 0) {
			p.setOpacity(_stateHovers[OverPlayback]);

			int32 x = mid - (st::playerMoverSize.width() / 2);
			p.fillRect(rtl() ? (width() - x - st::playerMoverSize.width()) : x, height() - st::playerMoverSize.height(), st::playerMoverSize.width(), st::playerMoverSize.height(), st::playerLineActive->b);
		}
	} else if (a_loadProgress.current() > 0) {
		int32 from = _playbackRect.x(), mid = qRound(_playbackRect.x() + a_loadProgress.current() * _playbackRect.width());
		if (mid > from) {
			p.fillRect(rtl() ? (width() - mid) : from, height() - st::playerLineHeight, mid - from, _playbackRect.height(), st::playerLineInactive->b);
		}
	}
}

void PlayerWidget::mousePressEvent(QMouseEvent *e) {
	QPoint pos(myrtlpoint(e->pos()));

	if (e->button() == Qt::LeftButton) {
		_down = OverNone;
		if (_song && _over == OverPlay) {
			playPausePressed();
			return;
		} else if (_over == OverPrev) {
			prevPressed();
		} else if (_over == OverNext) {
			nextPressed();
		} else if (_over == OverClose) {
			_down = OverClose;
		} else if (_over == OverVolume) {
			_down = OverVolume;
			_downCoord = pos.x() - _volumeRect.x();
			Global::SetSongVolume(snap((_downCoord - ((_volumeRect.width() - st::playerVolume.pxWidth()) / 2)) / float64(st::playerVolume.pxWidth()), 0., 1.));
			emit audioPlayer()->songVolumeChanged();
			rtlupdate(_volumeRect);
		} else if (_over == OverPlayback) {
			AudioMsgId playing;
			auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song);
			if (playing == _song && playbackState.duration) {
				if (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerStarting || playbackState.state == AudioPlayerResuming) {
					audioPlayer()->pauseresume(AudioMsgId::Type::Song);
				}
				_down = OverPlayback;
				_downProgress = snap((pos.x() - _playbackRect.x()) / float64(_playbackRect.width()), 0., 1.);
				_downDuration = playbackState.duration;
				_downFrequency = (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency);

				rtlupdate(_playbackRect);
				updateDownTime();
			}
		} else if (_over == OverFull && _song) {
			if (HistoryItem *item = App::histItemById(_song.contextId())) {
				App::main()->showMediaOverview(item->history()->peer, OverviewMusicFiles);
			}
		} else if (_over == OverRepeat) {
			_repeat = !_repeat;
			updateOverRect(OverRepeat);
		}
	}
}

void PlayerWidget::updateDownTime() {
	QString time = formatDurationText(qRound(_downDuration * _downProgress) / _downFrequency);
	if (time != _time) {
		_time = time;
		_timeWidth = st::linkFont->width(_time);
		rtlupdate(_infoRect);
	}
}

void PlayerWidget::updateOverState(OverState newState) {
	bool result = true;
	if (_over != newState) {
		updateOverRect(_over);
		updateOverRect(newState);
		if (_over != OverNone) {
			_stateAnimations.remove(_over);
			_stateAnimations[-_over] = getms() - ((1. - _stateHovers[_over]) * st::playerDuration);
			if (!_a_state.animating()) _a_state.start();
		} else {
			result = false;
		}
		_over = newState;
		if (newState != OverNone) {
			_stateAnimations.remove(-_over);
			_stateAnimations[_over] = getms() - (_stateHovers[_over] * st::playerDuration);
			if (!_a_state.animating()) _a_state.start();
			setCursor(style::cur_pointer);
		} else {
			setCursor(style::cur_default);
		}
	}
}

void PlayerWidget::updateOverRect(OverState state) {
	switch (state) {
	case OverPrev: rtlupdate(_prevRect); break;
	case OverPlay: rtlupdate(_playRect); break;
	case OverNext: rtlupdate(_nextRect); break;
	case OverClose: rtlupdate(_closeRect); break;
	case OverVolume: rtlupdate(_volumeRect); break;
	case OverFull: rtlupdate(_fullRect); break;
	case OverRepeat: rtlupdate(_repeatRect); break;
	case OverPlayback: rtlupdate(_playbackRect); break;
	}
}

void PlayerWidget::updateControls() {
	_fullAvailable = (_index >= 0);

	History *history = _msgmigrated ? _migrated : _history;
	_prevAvailable = _fullAvailable && ((_index > 0) || (_index == 0 && _migrated && !_msgmigrated && !_migrated->overview[OverviewMusicFiles].isEmpty()));
	_nextAvailable = _fullAvailable && ((_index < history->overview[OverviewMusicFiles].size() - 1) || (_msgmigrated && _index == _migrated->overview[OverviewMusicFiles].size() - 1 && _history->overviewLoaded(OverviewMusicFiles) && _history->overviewCount(OverviewMusicFiles) > 0));
	resizeEvent(0);
	update();
	if (_index >= 0 && _index < MediaOverviewStartPerPage) {
		if (!_history->overviewLoaded(OverviewMusicFiles) || (_migrated && !_migrated->overviewLoaded(OverviewMusicFiles))) {
			if (App::main()) {
				if (_msgmigrated || (_migrated && _index == 0 && _history->overviewLoaded(OverviewMusicFiles))) {
					App::main()->loadMediaBack(_migrated->peer, OverviewMusicFiles);
				} else {
					App::main()->loadMediaBack(_history->peer, OverviewMusicFiles);
					if (_migrated && _index == 0 && _migrated->overview[OverviewMusicFiles].isEmpty() && !_migrated->overviewLoaded(OverviewMusicFiles)) {
						App::main()->loadMediaBack(_migrated->peer, OverviewMusicFiles);
					}
				}
				if (_msgmigrated && !_history->overviewCountLoaded(OverviewMusicFiles)) {
					App::main()->preloadOverview(_history->peer, OverviewMusicFiles);
				}
			}
		}
	}
}

void PlayerWidget::findCurrent() {
	_index = -1;
	if (!_history || !_song.contextId().msg) return;

	const History::MediaOverview *o = &(_msgmigrated ? _migrated : _history)->overview[OverviewMusicFiles];
	if ((_msgmigrated ? _migrated : _history)->channelId() == _song.contextId().channel) {
		for (int i = 0, l = o->size(); i < l; ++i) {
			if (o->at(i) == _song.contextId().msg) {
				_index = i;
				break;
			}
		}
	}
	preloadNext();
}

void PlayerWidget::preloadNext() {
	if (_index < 0) return;

	History *history = _msgmigrated ? _migrated : _history;
	const History::MediaOverview *o = &history->overview[OverviewMusicFiles];
	HistoryItem *next = 0;
	if (_index < o->size() - 1) {
		next = App::histItemById(history->channelId(), o->at(_index + 1));
	} else if (_msgmigrated && _index == o->size() - 1 && _history->overviewLoaded(OverviewMusicFiles) && _history->overviewCount(OverviewMusicFiles) > 0) {
		next = App::histItemById(_history->channelId(), _history->overview[OverviewMusicFiles].at(0));
	} else if (_msgmigrated && _index == o->size() - 1 && !_history->overviewCountLoaded(OverviewMusicFiles)) {
		if (App::main()) App::main()->preloadOverview(_history->peer, OverviewMusicFiles);
	}
	if (next) {
		if (HistoryDocument *document = static_cast<HistoryDocument*>(next->getMedia())) {
			DocumentData *d = document->getDocument();
			if (!d->loaded(DocumentData::FilePathResolveSaveFromDataSilent)) {
				DocumentOpenClickHandler::doOpen(d, ActionOnLoadNone);
			}
		}
	}
}

void PlayerWidget::startPlay(const FullMsgId &msgId) {
	if (HistoryItem *item = App::histItemById(msgId)) {
		if (HistoryDocument *doc = static_cast<HistoryDocument*>(item->getMedia())) {
			audioPlayer()->play(AudioMsgId(doc->getDocument(), item->fullId()));
			updateState();
		}
	}
}

void PlayerWidget::clearSelection() {
	for (StateAnimations::const_iterator i = _stateAnimations.cbegin(); i != _stateAnimations.cend(); ++i) {
		_stateHovers[qAbs(i.key())] = 0;
	}
	_stateAnimations.clear();
}

void PlayerWidget::mediaOverviewUpdated(PeerData *peer, MediaOverviewType type) {
	if (_history && (_history->peer == peer || (_migrated && _migrated->peer == peer)) && type == OverviewMusicFiles) {
		_index = -1;
		History *history = _msgmigrated ? _migrated : _history;
		if (history->channelId() == _song.contextId().channel && _song.contextId().msg) {
			for (int i = 0, l = history->overview[OverviewMusicFiles].size(); i < l; ++i) {
				if (history->overview[OverviewMusicFiles].at(i) == _song.contextId().msg) {
					_index = i;
					preloadNext();
					break;
				}
			}
		}
		updateControls();
	}
}

bool PlayerWidget::seekingSong(const AudioMsgId &song) const {
	return (_down == OverPlayback) && (song == _song);
}

void PlayerWidget::openPlayer() {
	_playerOpened = true;
	Shortcuts::enableMediaShortcuts();
}

bool PlayerWidget::isOpened() const {
	return _playerOpened;
}

void PlayerWidget::closePlayer() {
	_playerOpened = false;
	Shortcuts::disableMediaShortcuts();
}

void PlayerWidget::showPlayer() {
	TWidget::show();
}

void PlayerWidget::hidePlayer() {
	clearSelection();
	TWidget::hide();
}

void PlayerWidget::step_state(uint64 ms, bool timer) {
	for (StateAnimations::iterator i = _stateAnimations.begin(); i != _stateAnimations.cend();) {
		int32 over = qAbs(i.key());
		updateOverRect(OverState(over));

		float64 dt = float64(ms - i.value()) / st::playerDuration;
		if (dt >= 1) {
			_stateHovers[over] = (i.key() > 0) ? 1 : 0;
			i = _stateAnimations.erase(i);
		} else {
			_stateHovers[over] = (i.key() > 0) ? dt : (1 - dt);
			++i;
		}
	}
	if (_stateAnimations.isEmpty()) {
		_a_state.stop();
	}
}

void PlayerWidget::mouseMoveEvent(QMouseEvent *e) {
	_lastMousePos = e->globalPos();
	updateSelected();
}

void PlayerWidget::leaveEvent(QEvent *e) {
	_lastMousePos = QCursor::pos();
	updateSelected();
}

void PlayerWidget::updateSelected() {
	QPoint pos(myrtlpoint(mapFromGlobal(_lastMousePos)));

	if (_down == OverVolume) {
		int32 delta = (pos.x() - _volumeRect.x()) - _downCoord;
		float64 startFrom = snap((_downCoord - ((_volumeRect.width() - st::playerVolume.pxWidth()) / 2)) / float64(st::playerVolume.pxWidth()), 0., 1.);
		float64 add = delta / float64(4 * st::playerVolume.pxWidth()), result = snap(startFrom + add, 0., 1.);
		if (result != Global::SongVolume()) {
			Global::SetSongVolume(result);
			emit audioPlayer()->songVolumeChanged();
			rtlupdate(_volumeRect);
		}
	} else if (_down == OverPlayback) {
		_downProgress = snap((pos.x() - _playbackRect.x()) / float64(_playbackRect.width()), 0., 1.);
		rtlupdate(_playbackRect);
		updateDownTime();
	} else if (_down == OverNone) {
		bool inInfo = ((pos.x() >= _infoRect.x()) && (pos.x() < _fullRect.x() + _fullRect.width()) && (pos.y() >= _playRect.y()) && (pos.y() <= _playRect.y() + _playRect.height()));
		if (_prevAvailable && _prevRect.contains(pos)) {
			updateOverState(OverPrev);
		} else if (_nextAvailable && _nextRect.contains(pos)) {
			updateOverState(OverNext);
		} else if (_playRect.contains(pos)) {
			updateOverState(OverPlay);
		} else if (_closeRect.contains(pos)) {
			updateOverState(OverClose);
		} else if (_volumeRect.contains(pos)) {
			updateOverState(OverVolume);
		} else if (_repeatRect.contains(pos)) {
			updateOverState(OverRepeat);
		} else if (_duration && _playbackRect.contains(pos)) {
			updateOverState(OverPlayback);
		} else if (_fullAvailable && inInfo) {
			updateOverState(OverFull);
		} else if (_over != OverNone) {
			updateOverState(OverNone);
		}
	}
}

void PlayerWidget::mouseReleaseEvent(QMouseEvent *e) {
	if (_down == OverVolume) {
		mouseMoveEvent(e);
		Local::writeUserSettings();
	} else if (_down == OverPlayback) {
		mouseMoveEvent(e);
		AudioMsgId playing;
		auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song);
		if (playing == _song && playbackState.duration) {
			_downDuration = playbackState.duration;
			audioPlayer()->seek(qRound(_downProgress * _downDuration));

			_showPause = true;

			a_progress = anim::fvalue(_downProgress, _downProgress);
			_a_progress.stop();
		}
		update();
	} else if (_down == OverClose && _over == OverClose) {
		closePressed();
	}
	_down = OverNone;
}

void PlayerWidget::playPressed() {
	if (!_song || isHidden()) return;

	AudioMsgId playing;
	auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song);
	if (playing == _song && !(playbackState.state & AudioPlayerStoppedMask)) {
		if (playbackState.state == AudioPlayerPausing || playbackState.state == AudioPlayerPaused || playbackState.state == AudioPlayerPausedAtEnd) {
			audioPlayer()->pauseresume(AudioMsgId::Type::Song);
		}
	} else {
		audioPlayer()->play(_song);
		if (App::main()) App::main()->audioPlayProgress(_song);
	}
}

void PlayerWidget::pausePressed() {
	if (!_song || isHidden()) return;

	AudioMsgId playing;
	auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song);
	if (playing == _song && !(playbackState.state & AudioPlayerStoppedMask)) {
		if (playbackState.state == AudioPlayerStarting || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerFinishing) {
			audioPlayer()->pauseresume(AudioMsgId::Type::Song);
		}
	}
}

void PlayerWidget::playPausePressed() {
	if (!_song || isHidden()) return;

	AudioMsgId playing;
	auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song);
	if (playing == _song && !(playbackState.state & AudioPlayerStoppedMask)) {
		audioPlayer()->pauseresume(AudioMsgId::Type::Song);
	} else {
		audioPlayer()->play(_song);
		if (App::main()) App::main()->audioPlayProgress(_song);
	}
}

void PlayerWidget::prevPressed() {
	if (isHidden()) return;

	History *history = _msgmigrated ? _migrated : _history;
	const History::MediaOverview *o = history ? &history->overview[OverviewMusicFiles] : 0;
	if (audioPlayer() && o && _index > 0 && _index <= o->size() && !o->isEmpty()) {
		startPlay(FullMsgId(history->channelId(), o->at(_index - 1)));
	} else if (!_index && _history && _migrated && !_msgmigrated) {
		o = &_migrated->overview[OverviewMusicFiles];
		if (!o->isEmpty()) {
			startPlay(FullMsgId(_migrated->channelId(), o->at(o->size() - 1)));
		}
	}
}

void PlayerWidget::nextPressed() {
	if (isHidden()) return;

	History *history = _msgmigrated ? _migrated : _history;
	const History::MediaOverview *o = history ? &history->overview[OverviewMusicFiles] : 0;
	if (audioPlayer() && o && _index >= 0 && _index < o->size() - 1) {
		startPlay(FullMsgId(history->channelId(), o->at(_index + 1)));
	} else if (o && (_index == o->size() - 1) && _msgmigrated && _history->overviewLoaded(OverviewMusicFiles)) {
		o = &_history->overview[OverviewMusicFiles];
		if (!o->isEmpty()) {
			startPlay(FullMsgId(_history->channelId(), o->at(0)));
		}
	}
}

void PlayerWidget::stopPressed() {
	if (!_song || isHidden()) return;

	audioPlayer()->stop(AudioMsgId::Type::Song);
}

void PlayerWidget::closePressed() {
	stopPressed();
	if (App::main()) App::main()->closePlayer();
}

void PlayerWidget::resizeEvent(QResizeEvent *e) {
	int32 availh = (height() - st::playerLineHeight);
	int32 ch = st::playerPlay.pxHeight() + st::playerSkip, ct = (availh - ch) / 2;
	_playbackRect = QRect(Adaptive::OneColumn() ? 0 : st::lineWidth, height() - st::playerMoverSize.height(), width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::playerMoverSize.height());
	_prevRect = _fullAvailable ? QRect(st::playerSkip / 2, ct, st::playerPrev.pxWidth() + st::playerSkip, ch) : QRect();
	_playRect = QRect(_fullAvailable ? (_prevRect.x() + _prevRect.width()) : (st::playerSkip / 2), ct, st::playerPlay.pxWidth() + st::playerSkip, ch);
	_nextRect = _fullAvailable ? QRect(_playRect.x() + _playRect.width(), ct, st::playerNext.pxWidth() + st::playerSkip, ch) : QRect();

	_closeRect = QRect(width() - st::playerSkip / 2 - st::playerClose.pxWidth() - st::playerSkip, ct, st::playerClose.pxWidth() + st::playerSkip, ch);
	_volumeRect = QRect(_closeRect.x() - st::playerVolume.pxWidth() - st::playerSkip, ct, st::playerVolume.pxWidth() + st::playerSkip, ch);
	_repeatRect = QRect(_volumeRect.x() - st::playerRepeat.pxWidth() - st::playerSkip, ct, st::playerRepeat.pxWidth() + st::playerSkip, ch);
	_fullRect = _fullAvailable ? QRect(_repeatRect.x() - st::playerFull.pxWidth() - st::playerSkip, ct, st::playerFull.pxWidth() + st::playerSkip, ch) : QRect();

	int32 infoLeft = (_fullAvailable ? (_nextRect.x() + _nextRect.width()) : (_playRect.x() + _playRect.width()));
	_infoRect = QRect(infoLeft + st::playerSkip / 2, 0, (_fullAvailable ? _fullRect.x() : _repeatRect.x()) - infoLeft - st::playerSkip, availh);

	update();
}

void PlayerWidget::step_progress(float64 ms, bool timer) {
	float64 dt = ms / (2 * AudioVoiceMsgUpdateView);
	if (_duration && dt >= 1) {
		_a_progress.stop();
		a_progress.finish();
		a_loadProgress.finish();
	} else {
		a_progress.update(qMin(dt, 1.), anim::linear);
		a_loadProgress.update(1. - (st::radialDuration / (st::radialDuration + ms)), anim::linear);
	}
	if (timer) rtlupdate(_playbackRect);
}

void PlayerWidget::updateState() {
	updateState(AudioMsgId(), AudioPlaybackState());
}

void PlayerWidget::updateState(AudioMsgId playing, AudioPlaybackState playbackState) {
	if (!playing) {
		playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song);
	}

	bool songChanged = false;
	if (playing && _song != playing) {
		songChanged = true;
		_song = playing;
		if (HistoryItem *item = App::histItemById(_song.contextId())) {
			_history = item->history();
			if (_history->peer->migrateFrom()) {
				_migrated = App::history(_history->peer->migrateFrom()->id);
				_msgmigrated = false;
			} else if (_history->peer->migrateTo()) {
				_migrated = _history;
				_history = App::history(_migrated->peer->migrateTo()->id);
				_msgmigrated = true;
			}
			findCurrent();
		} else {
			_history = nullptr;
			_msgmigrated = false;
			_index = -1;
		}
		auto song = _song.audio()->song();
		if (song->performer.isEmpty()) {
			_name.setText(st::linkFont, song->title.isEmpty() ? (_song.audio()->name.isEmpty() ? qsl("Unknown Track") : _song.audio()->name) : song->title, _textNameOptions);
		} else {
			TextCustomTagsMap custom;
			custom.insert(QChar('c'), qMakePair(textcmdStartLink(1), textcmdStopLink()));
			_name.setRichText(st::linkFont, QString::fromUtf8("[c]%1[/c] \xe2\x80\x93 %2").arg(textRichPrepare(song->performer)).arg(song->title.isEmpty() ? qsl("Unknown Track") : textRichPrepare(song->title)), _textNameOptions, custom);
		}
		updateControls();
	}

	qint64 position = 0, duration = 0, display = 0;
	if (playing == _song) {
		if (!(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) {
			display = position = playbackState.position;
			duration = playbackState.duration;
		} else {
			display = playbackState.duration;
		}
		display = display / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency);
	} else if (_song) {
		display = _song.audio()->song()->duration;
	}
	bool showPause = false, stopped = ((playbackState.state & AudioPlayerStoppedMask) || playbackState.state == AudioPlayerFinishing);
	bool wasPlaying = (_duration != 0);
	if (!stopped) {
		showPause = (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting);
	}
	QString time;
	float64 progress = 0.;
	int32 loaded;
	float64 loadProgress = 1.;
	if (duration || !_song || !_song.audio() || !_song.audio()->loading()) {
		time = (_down == OverPlayback) ? _time : formatDurationText(display);
		progress = duration ? snap(float64(position) / duration, 0., 1.) : 0.;
		loaded = duration ? _song.audio()->size : 0;
	} else {
		loaded = _song.audio()->loading() ? _song.audio()->loadOffset() : 0;
		time = formatDownloadText(loaded, _song.audio()->size);
		loadProgress = snap(float64(loaded) / qMax(_song.audio()->size, 1), 0., 1.);
	}
	if (time != _time || showPause != _showPause) {
		if (_time != time) {
			_time = time;
			_timeWidth = st::linkFont->width(_time);
		}
		_showPause = showPause;
		if (duration != _duration || position != _position || loaded != _loaded) {
			if (!songChanged && ((!stopped && duration && _duration) || (!duration && _loaded != loaded))) {
				a_progress.start(progress);
				a_loadProgress.start(loadProgress);
				_a_progress.start();
			} else {
				a_progress = anim::fvalue(progress, progress);
				a_loadProgress = anim::fvalue(loadProgress, loadProgress);
				_a_progress.stop();
			}
			_position = position;
			_duration = duration;
			_loaded = loaded;
		}
		update();
	} else if (duration != _duration || position != _position || loaded != _loaded) {
		if (!songChanged && ((!stopped && duration && _duration) || (!duration && _loaded != loaded))) {
			a_progress.start(progress);
			a_loadProgress.start(loadProgress);
			_a_progress.start();
		} else {
			a_progress = anim::fvalue(progress, progress);
			a_loadProgress = anim::fvalue(loadProgress, loadProgress);
			_a_progress.stop();
		}
		_position = position;
		_duration = duration;
		_loaded = loaded;
	}

	if (wasPlaying && playbackState.state == AudioPlayerStoppedAtEnd) {
		if (_repeat) {
			if (_song.audio()) {
				audioPlayer()->play(_song, OverviewMusicFiles);
				updateState();
			}
		} else {
			nextPressed();
		}
	}

	if (songChanged) {
		emit playerSongChanged(_song.contextId());
	}
}