/*
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-2017 John Preston, https://desktop.telegram.org
*/
#pragma once

#include "styles/style_widgets.h"
#include "ui/rp_widget.h"

namespace Ui {

template <typename Widget, typename ParentType = RpWidget>
class Wrap;

namespace details {

struct UnwrapHelper {
	struct Large {
		char data[2];
	};
	static char Check(...);
	template <typename Widget, typename Parent>
	static Large Check(Wrap<Widget, Parent>*);
	template <typename Widget, typename Parent>
	static Large Check(const Wrap<Widget, Parent>*);

	template <typename Entity>
	static constexpr bool Is() {
		return sizeof(Check(std::declval<Entity>()))
			== sizeof(Large);
	}
	template <typename Entity>
	static auto Unwrap(Entity *entity, std::true_type) {
		return entity
			? entity->entity()
			: nullptr;
	}
	template <typename Entity>
	static Entity *Unwrap(Entity *entity, std::false_type) {
		return entity;
	}
	template <typename Entity>
	static auto Unwrap(Entity *entity) {
		return Unwrap(
			entity,
			std::integral_constant<bool, Is<Entity*>()>());
	}
};

} // namespace details

template <typename Widget>
class Wrap<Widget, RpWidget> : public RpWidget {
public:
	Wrap(QWidget *parent, object_ptr<Widget> child);

	Widget *wrapped() {
		return _wrapped;
	}
	const Widget *wrapped() const {
		return _wrapped;
	}
	auto entity() {
		return details::UnwrapHelper::Unwrap(wrapped());
	}
	auto entity() const {
		return details::UnwrapHelper::Unwrap(wrapped());
	}

	QMargins getMargins() const override {
		if (auto weak = wrapped()) {
			return weak->getMargins();
		}
		return RpWidget::getMargins();
	}
	int naturalWidth() const override {
		if (auto weak = wrapped()) {
			return weak->naturalWidth();
		}
		return RpWidget::naturalWidth();
	}

private:
	object_ptr<Widget> _wrapped;

};

template <typename Widget>
Wrap<Widget, RpWidget>::Wrap(QWidget *parent, object_ptr<Widget> child)
: RpWidget(parent)
, _wrapped(std::move(child)) {
	if (_wrapped) {
		resize(_wrapped->size());
		AttachParentChild(this, _wrapped);
		_wrapped->move(0, 0);
		_wrapped->alive()
			| rpl::on_done([this] {
				_wrapped->setParent(nullptr);
				_wrapped = nullptr;
				delete this;
			})
			| rpl::start(lifetime());
	}
}

template <typename Widget, typename ParentType>
class Wrap : public ParentType {
public:
	using ParentType::ParentType;

	Widget *wrapped() {
		return static_cast<Widget*>(ParentType::wrapped());
	}
	const Widget *wrapped() const {
		return static_cast<const Widget*>(ParentType::wrapped());
	}
	auto entity() {
		return details::UnwrapHelper::Unwrap(wrapped());
	}
	auto entity() const {
		return details::UnwrapHelper::Unwrap(wrapped());
	}

};

template <typename Widget>
class PaddingWrap;

template <>
class PaddingWrap<RpWidget> : public Wrap<RpWidget> {
	using Parent = Wrap<RpWidget>;

public:
	PaddingWrap(
		QWidget *parent,
		object_ptr<RpWidget> child,
		const style::margins &padding);

	PaddingWrap(
		QWidget *parent,
		const style::margins &padding)
		: PaddingWrap(parent, nullptr, padding) {
	}

	int naturalWidth() const override;

protected:
	int resizeGetHeight(int newWidth) override;

private:
	void updateSize();

	int _innerWidth = 0;
	style::margins _padding;

};

template <typename Widget>
class PaddingWrap : public Wrap<Widget, PaddingWrap<RpWidget>> {
	using Parent = Wrap<Widget, PaddingWrap<RpWidget>>;

public:
	PaddingWrap(
		QWidget *parent,
		object_ptr<Widget> child,
		const style::margins &padding)
	: Parent(parent, std::move(child), padding) {
	}

	PaddingWrap(QWidget *parent, const style::margins &padding)
	: Parent(parent, padding) {
	}

};

template <typename Widget>
class SlideWrap;

template <>
class SlideWrap<RpWidget> : public Wrap<PaddingWrap<RpWidget>> {
	using Parent = Wrap<PaddingWrap<RpWidget>>;

public:
	SlideWrap(QWidget *parent, object_ptr<RpWidget> child);
	SlideWrap(
		QWidget *parent,
		object_ptr<RpWidget> child,
		const style::margins &padding);
	SlideWrap(
		QWidget *parent,
		object_ptr<RpWidget> child,
		int duration);
	SlideWrap(
		QWidget *parent,
		object_ptr<RpWidget> child,
		const style::margins &padding,
		int duration);

	void toggleAnimated(bool visible);
	void toggleFast(bool visible);

	void showAnimated() {
		toggleAnimated(true);
	}
	void hideAnimated() {
		toggleAnimated(false);
	}

	void showFast() {
		toggleFast(true);
	}
	void hideFast() {
		toggleFast(false);
	}

	bool animating() const {
		return _slideAnimation.animating();
	}
	void finishAnimations();

	QMargins getMargins() const override;

	bool isHiddenOrHiding() const {
		return !_visible;
	}

protected:
	int resizeGetHeight(int newWidth) override;

private:
	void animationStep();

	bool _visible = true;
	Animation _slideAnimation;
	int _duration = 0;

};

template <typename Widget>
class SlideWrap : public Wrap<PaddingWrap<Widget>, SlideWrap<RpWidget>> {
	using Parent = Wrap<PaddingWrap<Widget>, SlideWrap<RpWidget>>;

public:
	SlideWrap(QWidget *parent, object_ptr<Widget> child)
	: Parent(parent, std::move(child)) {
	}
	SlideWrap(
		QWidget *parent,
		object_ptr<Widget> child,
		const style::margins &padding)
	: Parent(parent, std::move(child), padding) {
	}
	SlideWrap(
		QWidget *parent,
		object_ptr<Widget> child,
		int duration)
	: Parent(parent, std::move(child), duration) {
	}
	SlideWrap(
		QWidget *parent,
		object_ptr<Widget> child,
		const style::margins &padding,
		int duration)
	: Parent(parent, std::move(child), padding, duration) {
	}

};

} // namespace Ui
