// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "ui/layers/box_layer_widget.h"

#include "ui/effects/radial_animation.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/painter.h"
#include "base/timer.h"
#include "styles/style_layers.h"
#include "styles/palette.h"

#include <QtGui/QWindow>

#include <limits>

namespace Ui {

struct BoxLayerWidget::LoadingProgress {
	LoadingProgress(
		Fn<void()> &&callback,
		const style::InfiniteRadialAnimation &st);

	InfiniteRadialAnimation animation;
	base::Timer removeTimer;
};

BoxLayerWidget::LoadingProgress::LoadingProgress(
	Fn<void()> &&callback,
	const style::InfiniteRadialAnimation &st)
: animation(std::move(callback), st) {
}

BoxLayerWidget::BoxLayerWidget(
	QWidget *parent,
	not_null<LayerStackDelegate*> delegate,
	object_ptr<BoxContent> content)
: LayerWidget(parent)
, _layer(delegate)
, _content(std::move(content))
, _roundRect(st::boxRadius, st().bg) {
	_content->setParent(this);

	_additionalTitle.changes(
	) | rpl::on_next([=] {
		updateSize();
		update();
	}, lifetime());

	updateMaxRealHeight();
	_content->setDelegate(this);
}

BoxLayerWidget::~BoxLayerWidget() = default;

void BoxLayerWidget::setLayerType(bool layerType) {
	if (_layerType == layerType) {
		return;
	}
	_layerType = layerType;
	updateTitlePosition();
	if (_maxContentHeight) {
		setDimensions(width(), _maxContentHeight);
	}
}

int BoxLayerWidget::titleHeight() const {
	return st::boxTitleHeight;
}

const style::Box &BoxLayerWidget::st() const {
	if (_st) {
		return *_st;
	}
	const auto override = _layerType
		? _layer->boxStyleOverrideLayer()
		: _layer->boxStyleOverride();
	if (override) {
		return *override;
	}
	return _layerType ? st::layerBox : st::defaultBox;
}

void BoxLayerWidget::setStyle(const style::Box &st) {
	_st = &st;
	_roundRect.setColor(st.bg);
	updateMaxRealHeight();
}

const style::Box &BoxLayerWidget::style() {
	return st();
}

int BoxLayerWidget::buttonsHeight() const {
	const auto padding = st().buttonPadding;
	return padding.top() + st().buttonHeight + padding.bottom();
}

int BoxLayerWidget::buttonsTop() const {
	const auto padding = st().buttonPadding;
	return height() - padding.bottom() - st().buttonHeight;
}

QRect BoxLayerWidget::loadingRect() const {
	const auto padding = st().buttonPadding;
	const auto size = st::boxLoadingSize;
	const auto skipx = st::boxTitlePosition.x();
	const auto skipy = (st().buttonHeight - size) / 2;
	return QRect(
		skipx,
		height() - padding.bottom() - skipy - size,
		size,
		size);
}

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

	const auto clip = e->rect();
	const auto paintTopRounded = !(_customCornersFilling & RectPart::FullTop)
		&& clip.intersects(QRect(0, 0, width(), st::boxRadius));
	const auto paintBottomRounded = !(_customCornersFilling
		& RectPart::FullBottom)
		&& clip.intersects(
			QRect(0, height() - st::boxRadius, width(), st::boxRadius));
	if (paintTopRounded || paintBottomRounded) {
		_roundRect.paint(p, rect(), RectPart::None
			| (paintTopRounded ? RectPart::FullTop : RectPart::None)
			| (paintBottomRounded ? RectPart::FullBottom : RectPart::None));
	}
	const auto other = e->region().intersected(
		QRect(0, st::boxRadius, width(), height() - 2 * st::boxRadius));
	if (!other.isEmpty()) {
		for (const auto &rect : other) {
			p.fillRect(rect, st().bg);
		}
	}
	if (!_additionalTitle.current().isEmpty()
		&& clip.intersects(QRect(0, 0, width(), titleHeight()))) {
		paintAdditionalTitle(p);
	}
	if (_loadingProgress) {
		const auto rect = loadingRect();
		_loadingProgress->animation.draw(
			p,
			rect.topLeft(),
			rect.size(),
			width());
	}
}

void BoxLayerWidget::paintAdditionalTitle(Painter &p) {
	p.setFont(st::boxTitleAdditionalFont);
	p.setPen(st().titleAdditionalFg);
	p.drawTextLeft(
		_titleLeft + (_title ? _title->width() : 0) + st::boxTitleAdditionalSkip,
		_titleTop + st::boxTitleFont->ascent - st::boxTitleAdditionalFont->ascent,
		width(),
		_additionalTitle.current());
}

void BoxLayerWidget::parentResized() {
	const auto parent = parentWidget();
	if (!parent || !_layer->centerWithinOuter()) {
		return;
	}
	updateMaxRealHeight();
	auto newHeight = countRealHeight();
	auto parentSize = parent->size();
	setGeometry(
		(parentSize.width() - width()) / 2,
		(parentSize.height() - newHeight) / 2,
		width(),
		newHeight);
	update();
}

void BoxLayerWidget::updateMaxRealHeight() {
	const auto &margin = st().margin;
	const auto outer = _layer->layerOuterSize();
	const auto parent = parentWidget();
	const auto containerHeight = outer
		? outer->height()
		: parent
		? parent->height()
		: std::numeric_limits<int>::max() / 2;
	const auto max = containerHeight - margin.top() - margin.bottom();
	_realHeightMax = max;
	_contentHeightMax = max - contentTop() - buttonsHeight();
}

void BoxLayerWidget::setTitle(
		rpl::producer<TextWithEntities> title,
		Text::MarkedContext context) {
	const auto wasTitle = hasTitle();
	if (title) {
		_title.create(
			this,
			rpl::duplicate(title),
			st().title,
			st::defaultPopupMenu,
			context);
		_title->show();
		std::move(
			title
		) | rpl::on_next([=] {
			updateTitlePosition();
		}, _title->lifetime());
	} else {
		_title.destroy();
	}
	if (wasTitle != hasTitle()) {
		updateSize();
	}
}

void BoxLayerWidget::setAdditionalTitle(rpl::producer<QString> additional) {
	_additionalTitle = std::move(additional);
}

void BoxLayerWidget::triggerButton(int index) {
	if (index < _buttons.size()) {
		_buttons[index]->clicked(Qt::KeyboardModifiers(), Qt::LeftButton);
	}
}

void BoxLayerWidget::setCloseByOutsideClick(bool close) {
	_closeByOutsideClick = close;
}

bool BoxLayerWidget::closeByOutsideClick() const {
	return _closeByOutsideClick;
}

rpl::producer<int> BoxLayerWidget::layerHeightMaxValue() {
	return _realHeightMax.value();
}

rpl::producer<int> BoxLayerWidget::contentHeightMaxValue() {
	return _contentHeightMax.value();
}

bool BoxLayerWidget::hasTitle() const {
	return (_title != nullptr) || !_additionalTitle.current().isEmpty();
}

void BoxLayerWidget::showBox(
		object_ptr<BoxContent> box,
		LayerOptions options,
		anim::type animated) {
	_layer->showBox(std::move(box), options, animated);
}

void BoxLayerWidget::hideLayer() {
	_layer->hideLayers(anim::type::normal);
}

ShowFactory BoxLayerWidget::showFactory() {
	return _layer->showFactory();
}

QPointer<QWidget> BoxLayerWidget::outerContainer() {
	if (const auto fromDelegate = _layer->layerOuterContainer()) {
		return fromDelegate;
	}
	if (const auto parent = parentWidget()) {
		return parent;
	}
	return this;
}

void BoxLayerWidget::updateSize() {
	setDimensions(width(), _maxContentHeight);
}

void BoxLayerWidget::updateButtonsPositions() {
	if (!_buttons.empty() || _leftButton) {
		auto padding = st().buttonPadding;
		auto right = padding.right();
		auto top = buttonsTop();
		if (_leftButton) {
			_leftButton->moveToLeft(right, top);
		}
		for (const auto &button : _buttons) {
			button->moveToRight(right, top);
			right += button->width() + padding.left();
		}
	}
	auto right = 0;
	for (const auto &button : _topButtons) {
		button->moveToRight(right, 0);
		right += button->width();
	}
}

void BoxLayerWidget::updateTitlePosition() {
	_titleLeft = st::boxTitlePosition.x();
	_titleTop = st::boxTitlePosition.y();
	if (_title) {
		auto topButtonsSkip = 0;
		for (const auto &button : _topButtons) {
			topButtonsSkip += button->width();
		}
		_title->resizeToNaturalWidth(
			width() - _titleLeft * 2 - topButtonsSkip);
		_title->moveToLeft(_titleLeft, _titleTop);
	}
}

void BoxLayerWidget::setCustomCornersFilling(RectParts corners) {
	_customCornersFilling = corners;
	update();
}

void BoxLayerWidget::clearButtons() {
	for (auto &button : base::take(_buttons)) {
		button.destroy();
	}
	_leftButton.destroy();
	base::take(_topButtons);
}

void BoxLayerWidget::addButton(object_ptr<AbstractButton> button) {
	_buttons.push_back(std::move(button));
	const auto raw = _buttons.back().data();
	raw->setParent(this);
	raw->show();
	if (st().buttonWide) {
		widthValue() | rpl::on_next([=](int width) {
			const auto buttonWidth = width
				- st().buttonPadding.left()
				- st().buttonPadding.right();
			if (buttonWidth > 0) {
				raw->resizeToWidth(buttonWidth);
			}
		}, raw->lifetime());
	}
	raw->widthValue(
	) | rpl::on_next([=] {
		if (st().buttonWide) {
			const auto buttonWidth = width()
				- st().buttonPadding.left()
				- st().buttonPadding.right();
			if (buttonWidth > 0 && raw->width() != buttonWidth) {
				raw->resizeToWidth(buttonWidth);
			}
		}
		updateButtonsPositions();
	}, raw->lifetime());
}

void BoxLayerWidget::addLeftButton(object_ptr<AbstractButton> button) {
	_leftButton = std::move(button);
	const auto raw = _leftButton.data();
	raw->setParent(this);
	raw->show();
	raw->widthValue(
	) | rpl::on_next([=] {
		updateButtonsPositions();
	}, raw->lifetime());
}

void BoxLayerWidget::addTopButton(object_ptr<AbstractButton> button) {
	_topButtons.push_back(base::unique_qptr<AbstractButton>(button.release()));
	const auto raw = _topButtons.back().get();
	raw->setParent(this);
	raw->show();
	updateButtonsPositions();
	updateTitlePosition();
}

void BoxLayerWidget::showLoading(bool show) {
	const auto &st = st::boxLoadingAnimation;
	if (!show) {
		if (_loadingProgress && !_loadingProgress->removeTimer.isActive()) {
			_loadingProgress->removeTimer.callOnce(
				st.sineDuration + st.sinePeriod);
			_loadingProgress->animation.stop();
		}
		return;
	}
	if (!_loadingProgress) {
		const auto callback = [=] {
			if (!anim::Disabled()) {
				const auto t = st::boxLoadingAnimation.thickness;
				update(loadingRect().marginsAdded({ t, t, t, t }));
			}
		};
		_loadingProgress = std::make_unique<LoadingProgress>(
			callback,
			st::boxLoadingAnimation);
		_loadingProgress->removeTimer.setCallback([=] {
			_loadingProgress = nullptr;
		});
	} else {
		_loadingProgress->removeTimer.cancel();
	}
	_loadingProgress->animation.start();
}


void BoxLayerWidget::setDimensions(
		int newWidth,
		int maxHeight,
		bool forceCenterPosition) {
	_maxContentHeight = maxHeight;

	auto fullHeight = countFullHeight();
	if (width() != newWidth || _fullHeight != fullHeight) {
		_fullHeight = fullHeight;
		const auto parent = parentWidget();
		if (parent && _layer->centerWithinOuter()) {
			auto oldGeometry = geometry();
			resize(newWidth, countRealHeight());
			auto newGeometry = geometry();
			auto parentHeight = parent->height();
			const auto bottomMargin = st().margin.bottom();
			if (newGeometry.top() + newGeometry.height() + bottomMargin > parentHeight
				|| forceCenterPosition) {
				const auto top1 = parentHeight - bottomMargin - newGeometry.height();
				const auto top2 = (parentHeight - newGeometry.height()) / 2;
				const auto newTop = forceCenterPosition
					? std::min(top1, top2)
					: std::max(top1, top2);
				if (newTop != newGeometry.top()) {
					move(newGeometry.left(), newTop);
					resizeEvent(0);
				}
			}
			parent->update(oldGeometry.united(geometry()).marginsAdded(st::boxRoundShadow.extend));
		} else {
			resize(newWidth, countRealHeight());
		}
	}
}

int BoxLayerWidget::countRealHeight() const {
	return std::min(_fullHeight, _realHeightMax.current());
}

int BoxLayerWidget::countFullHeight() const {
	return contentTop() + _maxContentHeight + buttonsHeight();
}

int BoxLayerWidget::contentTop() const {
	return hasTitle()
		? titleHeight()
		: _noContentMargin
		? 0
		: st::boxTopMargin;
}

void BoxLayerWidget::resizeEvent(QResizeEvent *e) {
	updateButtonsPositions();
	updateTitlePosition();

	const auto top = contentTop();
	_content->resize(width(), height() - top - buttonsHeight());
	_content->moveToLeft(0, top);

	LayerWidget::resizeEvent(e);
}

void BoxLayerWidget::keyPressEvent(QKeyEvent *e) {
	if (e->key() == Qt::Key_Escape) {
		closeBox();
	} else {
		LayerWidget::keyPressEvent(e);
	}
}

bool BoxLayerWidget::closeByBackButton() {
	if (_content->closeByEscape()) {
		closeBox();
	}
	return true;
}

void BoxLayerWidget::mousePressEvent(QMouseEvent *e) {
	if (e->button() == Qt::LeftButton
		&& _layer->dragByTitle()
		&& e->pos().y() < titleHeight()) {
		if (const auto top = window()) {
			if (const auto handle = top->windowHandle()) {
				if (handle->startSystemMove()) {
					e->accept();
					return;
				}
			}
		}
	}
	LayerWidget::mousePressEvent(e);
}

} // namespace Ui
