/*******************************************************************************
** Qt Advanced Docking System
** Copyright (C) 2017 Uwe Kindler
** 
** This library is free software; you can redistribute it and/or
** modify it under the terms of the GNU Lesser General Public
** License as published by the Free Software Foundation; either
** version 2.1 of the License, or (at your option) any later version.
** 
** This library 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
** Lesser General Public License for more details.
** 
** You should have received a copy of the GNU Lesser General Public
** License along with this library; If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/


//============================================================================
/// \file   DockWidgetTab.cpp
/// \author Uwe Kindler
/// \date   27.02.2017
/// \brief  Implementation of CDockWidgetTab class
//============================================================================


//============================================================================
//                                   INCLUDES
//============================================================================
#include <AutoHideDockContainer.h>
#include "FloatingDragPreview.h"
#include "ElidingLabel.h"
#include "DockWidgetTab.h"

#include <QBoxLayout>
#include <QLabel>
#include <QMouseEvent>
#include <QStyle>
#include <QApplication>
#include <QSplitter>
#include <QDebug>
#include <QToolButton>
#include <QPushButton>
#include <QMenu>

#include "ads_globals.h"
#include "DockWidget.h"
#include "DockAreaWidget.h"
#include "FloatingDockContainer.h"
#include "DockOverlay.h"
#include "DockManager.h"
#include "IconProvider.h"
#include "DockFocusController.h"


namespace ads
{
using tTabLabel = CElidingLabel;

/**
 * Private data class of CDockWidgetTab class (pimpl)
 */
struct DockWidgetTabPrivate
{
	CDockWidgetTab* _this;
	CDockWidget* DockWidget;
	QLabel* IconLabel = nullptr;
	tTabLabel* TitleLabel;
	QPoint GlobalDragStartMousePosition;
	QPoint DragStartMousePosition;
	bool IsActiveTab = false;
	CDockAreaWidget* DockArea = nullptr;
	eDragState DragState = DraggingInactive;
	IFloatingWidget* FloatingWidget = nullptr;
	QIcon Icon;
	QAbstractButton* CloseButton = nullptr;
	QSpacerItem* IconTextSpacer;
	QPoint TabDragStartPosition;
	QSize IconSize;

	/**
	 * Private data constructor
	 */
	DockWidgetTabPrivate(CDockWidgetTab* _public);

	/**
	 * Creates the complete layout including all controls
	 */
	void createLayout();

	/**
	 * Moves the tab depending on the position in the given mouse event
	 */
	void moveTab(QMouseEvent* ev);

	/**
	 * Test function for current drag state
	 */
	bool isDraggingState(eDragState dragState) const
	{
		return this->DragState == dragState;
	}


	/**
	 * Starts floating of the dock widget that belongs to this title bar
	 * Returns true, if floating has been started and false if floating
	 * is not possible for any reason
	 */
	bool startFloating(eDragState DraggingState = DraggingFloatingWidget);

	/**
	 * Returns true if the given config flag is set
	 */
	bool testConfigFlag(CDockManager::eConfigFlag Flag) const
	{
		return CDockManager::testConfigFlag(Flag);
	}

	/**
	 * Creates the close button as QPushButton or as QToolButton
	 */
	QAbstractButton* createCloseButton() const
	{
		if (testConfigFlag(CDockManager::TabCloseButtonIsToolButton))
		{
			auto Button = new QToolButton();
			Button->setAutoRaise(true);
			return Button;
		}
		else
		{
			return new QPushButton();
		}
	}

	/**
	 * Update the close button visibility from current feature/config
	 */
	void updateCloseButtonVisibility(bool active)
	{
		bool DockWidgetClosable = DockWidget->features().testFlag(CDockWidget::DockWidgetClosable);
		bool ActiveTabHasCloseButton = testConfigFlag(CDockManager::ActiveTabHasCloseButton);
		bool AllTabsHaveCloseButton = testConfigFlag(CDockManager::AllTabsHaveCloseButton);
		bool TabHasCloseButton = (ActiveTabHasCloseButton && active) | AllTabsHaveCloseButton;
		CloseButton->setVisible(DockWidgetClosable && TabHasCloseButton);
	}

	/**
	 * Update the size policy of the close button depending on the
	 * RetainTabSizeWhenCloseButtonHidden feature
	 */
	void updateCloseButtonSizePolicy()
	{
		auto Features = DockWidget->features();
		auto SizePolicy = CloseButton->sizePolicy();
		SizePolicy.setRetainSizeWhenHidden(Features.testFlag(CDockWidget::DockWidgetClosable)
			&& testConfigFlag(CDockManager::RetainTabSizeWhenCloseButtonHidden));
		CloseButton->setSizePolicy(SizePolicy);
	}

	template <typename T>
	IFloatingWidget* createFloatingWidget(T* Widget, bool CreateContainer)
	{
		if (CreateContainer)
		{
			return new CFloatingDockContainer(Widget);
		}
		else
		{
			auto w = new CFloatingDragPreview(Widget);
			_this->connect(w, &CFloatingDragPreview::draggingCanceled, [=]()
			{
				DragState = DraggingInactive;
			});
			return w;
		}
	}

	/**
	 * Saves the drag start position in global and local coordinates
	 */
	void saveDragStartMousePosition(const QPoint& GlobalPos)
	{
		GlobalDragStartMousePosition = GlobalPos;
		DragStartMousePosition = _this->mapFromGlobal(GlobalPos);
	}

	/**
	 * Update the icon in case the icon size changed
	 */
	void updateIcon()
	{
		if (!IconLabel || Icon.isNull())
		{
			return;
		}

		if (IconSize.isValid())
		{
			IconLabel->setPixmap(Icon.pixmap(IconSize));
		}
		else
		{
			IconLabel->setPixmap(Icon.pixmap(_this->style()->pixelMetric(QStyle::PM_SmallIconSize, nullptr, _this)));
		}
		IconLabel->setVisible(true);
	}

	/**
	 * Convenience function for access to the dock manager dock focus controller
	 */
	CDockFocusController* focusController() const
	{
		return DockWidget->dockManager()->dockFocusController();
	}

	/**
	 * Helper function to create and initialize the menu entries for
	 * the "Auto Hide Group To..." menu
	 */
	QAction* createAutoHideToAction(const QString& Title, SideBarLocation Location,
		QMenu* Menu)
	{
		auto Action = Menu->addAction(Title);
		Action->setProperty(internal::LocationProperty, Location);
		QObject::connect(Action, &QAction::triggered, _this, &CDockWidgetTab::onAutoHideToActionClicked);
		return Action;
	}
};
// struct DockWidgetTabPrivate


//============================================================================
DockWidgetTabPrivate::DockWidgetTabPrivate(CDockWidgetTab* _public) :
	_this(_public)
{

}


//============================================================================
void DockWidgetTabPrivate::createLayout()
{
	TitleLabel = new tTabLabel();
	if (CDockManager::testConfigFlag(CDockManager::DisableTabTextEliding))
	{
		TitleLabel->setElideMode(Qt::ElideNone);
	}
	else
	{
		TitleLabel->setElideMode(Qt::ElideRight);
	}
	TitleLabel->setText(DockWidget->windowTitle());
	TitleLabel->setObjectName("dockWidgetTabLabel");
	TitleLabel->setAlignment(Qt::AlignCenter);
	_this->connect(TitleLabel, SIGNAL(elidedChanged(bool)), SIGNAL(elidedChanged(bool)));


	CloseButton = createCloseButton();
	CloseButton->setObjectName("tabCloseButton");
	internal::setButtonIcon(CloseButton, QStyle::SP_TitleBarCloseButton, TabCloseIcon);
    CloseButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
    CloseButton->setFocusPolicy(Qt::NoFocus);
    updateCloseButtonSizePolicy();
	internal::setToolTip(CloseButton, QObject::tr("Close Tab"));
	_this->connect(CloseButton, SIGNAL(clicked()), SIGNAL(closeRequested()));

	QFontMetrics fm(TitleLabel->font());
	int Spacing = qRound(fm.height() / 4.0);

	// Fill the layout
	QBoxLayout* Layout = new QBoxLayout(QBoxLayout::LeftToRight);
	Layout->setContentsMargins(2 * Spacing,0,0,0);
	Layout->setSpacing(0);
	_this->setLayout(Layout);
	Layout->addWidget(TitleLabel, 1);
	Layout->addSpacing(Spacing);
	Layout->addWidget(CloseButton);
	Layout->addSpacing(qRound(Spacing * 4.0 / 3.0));
	Layout->setAlignment(Qt::AlignCenter);

	TitleLabel->setVisible(true);
}

//============================================================================
void DockWidgetTabPrivate::moveTab(QMouseEvent* ev)
{
    ev->accept();
    QPoint Distance = internal::globalPositionOf(ev) - GlobalDragStartMousePosition;
    Distance.setY(0);
    auto TargetPos = Distance + TabDragStartPosition;
    TargetPos.rx() = qMax(TargetPos.x(), 0);
    TargetPos.rx() = qMin(_this->parentWidget()->rect().right() - _this->width() + 1, TargetPos.rx());
    _this->move(TargetPos);
    _this->raise();
}


//============================================================================
bool DockWidgetTabPrivate::startFloating(eDragState DraggingState)
{
	auto dockContainer = DockWidget->dockContainer();
    ADS_PRINT("isFloating " << dockContainer->isFloating());
    ADS_PRINT("areaCount " << dockContainer->dockAreaCount());
    ADS_PRINT("widgetCount " << DockWidget->dockAreaWidget()->dockWidgetsCount());
	// if this is the last dock widget inside of this floating widget,
	// then it does not make any sense, to make it floating because
	// it is already floating
	 if (dockContainer->isFloating()
	 && (dockContainer->visibleDockAreaCount() == 1)
	 && (DockWidget->dockAreaWidget()->dockWidgetsCount() == 1))
	{
		return false;
	}

    ADS_PRINT("startFloating");
	DragState = DraggingState;
	IFloatingWidget* FloatingWidget = nullptr;
	bool CreateContainer = (DraggingFloatingWidget != DraggingState);

	// If section widget has multiple tabs, we take only one tab
	// If it has only one single tab, we can move the complete
	// dock area into floating widget
	QSize Size;
	if (DockArea->dockWidgetsCount() > 1)
	{
		FloatingWidget = createFloatingWidget(DockWidget, CreateContainer);
		Size = DockWidget->size();
	}
	else
	{
		FloatingWidget = createFloatingWidget(DockArea, CreateContainer);
		Size = DockArea->size();
	}

    if (DraggingFloatingWidget == DraggingState)
    {
        FloatingWidget->startFloating(DragStartMousePosition, Size, DraggingFloatingWidget, _this);
        auto DockManager = DockWidget->dockManager();
    	auto Overlay = DockManager->containerOverlay();
    	Overlay->setAllowedAreas(OuterDockAreas);
    	this->FloatingWidget = FloatingWidget;
    	qApp->postEvent(DockWidget, new QEvent((QEvent::Type)internal::DockedWidgetDragStartEvent));
    }
    else
    {
     	FloatingWidget->startFloating(DragStartMousePosition, Size, DraggingInactive, nullptr);
    }

	return true;
}


//============================================================================
CDockWidgetTab::CDockWidgetTab(CDockWidget* DockWidget, QWidget *parent) :
	QFrame(parent),
	d(new DockWidgetTabPrivate(this))
{
	setAttribute(Qt::WA_NoMousePropagation, true);
	d->DockWidget = DockWidget;
	d->createLayout();
	setFocusPolicy(Qt::NoFocus);
}

//============================================================================
CDockWidgetTab::~CDockWidgetTab()
{
    ADS_PRINT("~CDockWidgetTab()");
	delete d;
}


//============================================================================
void CDockWidgetTab::mousePressEvent(QMouseEvent* ev)
{
	if (ev->button() == Qt::LeftButton)
	{
		ev->accept();
        d->saveDragStartMousePosition(internal::globalPositionOf(ev));
        d->DragState = DraggingMousePressed;
        if (CDockManager::testConfigFlag(CDockManager::FocusHighlighting))
        {
        	d->focusController()->setDockWidgetTabPressed(true);
        	d->focusController()->setDockWidgetTabFocused(this);
        }
        Q_EMIT clicked();
		return;
	}
	Super::mousePressEvent(ev);
}



//============================================================================
void CDockWidgetTab::mouseReleaseEvent(QMouseEvent* ev)
{
	if (ev->button() == Qt::LeftButton)
	{
		auto CurrentDragState = d->DragState;
		d->GlobalDragStartMousePosition = QPoint();
		d->DragStartMousePosition = QPoint();
		d->DragState = DraggingInactive;

		switch (CurrentDragState)
		{
		case DraggingTab:
			// End of tab moving, emit signal
			if (d->DockArea)
			{
				ev->accept();
                Q_EMIT moved(internal::globalPositionOf(ev));
			}
			break;

		case DraggingFloatingWidget:
			 ev->accept();
			 d->FloatingWidget->finishDragging();
			 break;

		default:
			break;
		}

		if (CDockManager::testConfigFlag(CDockManager::FocusHighlighting))
		{
			d->focusController()->setDockWidgetTabPressed(false);
		}
	} 
	else if (ev->button() == Qt::MiddleButton)
	{
		if (CDockManager::testConfigFlag(CDockManager::MiddleMouseButtonClosesTab) && d->DockWidget->features().testFlag(CDockWidget::DockWidgetClosable))
		{
			// Only attempt to close if the mouse is still
			// on top of the widget, to allow the user to cancel.
			if (rect().contains(mapFromGlobal(QCursor::pos()))) {
				ev->accept();
				Q_EMIT closeRequested();
			}
		}
	}

	Super::mouseReleaseEvent(ev);
}


//============================================================================
void CDockWidgetTab::mouseMoveEvent(QMouseEvent* ev)
{
    if (!(ev->buttons() & Qt::LeftButton) || d->isDraggingState(DraggingInactive))
    {
    	d->DragState = DraggingInactive;
        Super::mouseMoveEvent(ev);
        return;
    }

    // move floating window
    if (d->isDraggingState(DraggingFloatingWidget))
    {
        d->FloatingWidget->moveFloating();
        Super::mouseMoveEvent(ev);
        return;
    }

    // move tab
    if (d->isDraggingState(DraggingTab))
    {
        // Moving the tab is always allowed because it does not mean moving the
    	// dock widget around
    	d->moveTab(ev);
    }

    auto MappedPos = mapToParent(ev->pos());
    bool MouseOutsideBar = (MappedPos.x() < 0) || (MappedPos.x() > parentWidget()->rect().right());
    // Maybe a fixed drag distance is better here ?
    int DragDistanceY = qAbs(d->GlobalDragStartMousePosition.y() - internal::globalPositionOf(ev).y());
    if (DragDistanceY >= CDockManager::startDragDistance() || MouseOutsideBar)
	{
		// If this is the last dock area in a dock container with only
    	// one single dock widget it does not make  sense to move it to a new
    	// floating widget and leave this one empty
		if (d->DockArea->dockContainer()->isFloating()
		 && d->DockArea->openDockWidgetsCount() == 1
		 && d->DockArea->dockContainer()->visibleDockAreaCount() == 1)
		{
			return;
		}


    	// Floating is only allowed for widgets that are floatable
		// We can create the drag preview if the widget is movable.
		auto Features = d->DockWidget->features();
        if (Features.testFlag(CDockWidget::DockWidgetFloatable) || (Features.testFlag(CDockWidget::DockWidgetMovable)))
        {
        	// If we undock, we need to restore the initial position of this
        	// tab because it looks strange if it remains on its dragged position
        	if (d->isDraggingState(DraggingTab))
			{
        		parentWidget()->layout()->update();
			}
            d->startFloating();
        }
    	return;
	}
    else if (d->DockArea->openDockWidgetsCount() > 1
     && (internal::globalPositionOf(ev) - d->GlobalDragStartMousePosition).manhattanLength() >= QApplication::startDragDistance()) // Wait a few pixels before start moving
	{
    	// If we start dragging the tab, we save its initial position to
    	// restore it later
    	if (DraggingTab != d->DragState)
    	{
    		d->TabDragStartPosition = this->pos();
    	}
        d->DragState = DraggingTab;
		return;
	}

   Super::mouseMoveEvent(ev);
}


//============================================================================
void CDockWidgetTab::contextMenuEvent(QContextMenuEvent* ev)
{
	ev->accept();
	if (d->isDraggingState(DraggingFloatingWidget))
	{
		return;
	}

	d->saveDragStartMousePosition(ev->globalPos());

    const bool isFloatable = d->DockWidget->features().testFlag(CDockWidget::DockWidgetFloatable);
    const bool isNotOnlyTabInContainer =  !d->DockArea->dockContainer()->hasTopLevelDockWidget();
    const bool isTopLevelArea = d->DockArea->isTopLevelArea();
    const bool isDetachable = isFloatable && isNotOnlyTabInContainer;
	QAction* Action;
	QMenu Menu(this);

    if (!isTopLevelArea)
    {
		Action = Menu.addAction(tr("Detach"), this, SLOT(detachDockWidget()));
		Action->setEnabled(isDetachable);
		if (CDockManager::testAutoHideConfigFlag(CDockManager::AutoHideFeatureEnabled))
		{
			Action = Menu.addAction(tr("Pin"), this, SLOT(autoHideDockWidget()));
			auto IsPinnable = d->DockWidget->features().testFlag(CDockWidget::DockWidgetPinnable);
			Action->setEnabled(IsPinnable);

			auto menu = Menu.addMenu(tr("Pin To..."));
			menu->setEnabled(IsPinnable);
			d->createAutoHideToAction(tr("Top"), SideBarTop, menu);
			d->createAutoHideToAction(tr("Left"), SideBarLeft, menu);
			d->createAutoHideToAction(tr("Right"), SideBarRight, menu);
			d->createAutoHideToAction(tr("Bottom"), SideBarBottom, menu);
		}
    }

	Menu.addSeparator();
	Action = Menu.addAction(tr("Close"), this, SIGNAL(closeRequested()));
	Action->setEnabled(isClosable());
	if (d->DockArea->openDockWidgetsCount() > 1)
	{
		Action = Menu.addAction(tr("Close Others"), this, SIGNAL(closeOtherTabsRequested()));
	}
	Menu.exec(ev->globalPos());
}


//============================================================================
bool CDockWidgetTab::isActiveTab() const
{
	return d->IsActiveTab;
}


//============================================================================
void CDockWidgetTab::setActiveTab(bool active)
{
    d->updateCloseButtonVisibility(active);

	if(CDockManager::testConfigFlag(CDockManager::ShowTabTextOnlyForActiveTab) && !d->Icon.isNull())
	{
		if(active)
			d->TitleLabel->setVisible(true);
		else
			d->TitleLabel->setVisible(false);
	}

	// Focus related stuff
	if (CDockManager::testConfigFlag(CDockManager::FocusHighlighting) && !d->DockWidget->dockManager()->isRestoringState())
	{
		bool UpdateFocusStyle = false;
		if (active && !hasFocus())
		{
			//setFocus(Qt::OtherFocusReason);
			d->focusController()->setDockWidgetTabFocused(this);
			UpdateFocusStyle = true;
		}

		if (d->IsActiveTab == active)
		{
			if (UpdateFocusStyle)
			{
				updateStyle();
			}
			return;
		}
	}
	else if (d->IsActiveTab == active)
	{
		return;
	}

	d->IsActiveTab = active;
	updateStyle();
	update();
	updateGeometry();

	Q_EMIT activeTabChanged();
}


//============================================================================
CDockWidget* CDockWidgetTab::dockWidget() const
{
	return d->DockWidget;
}


//============================================================================
void CDockWidgetTab::setDockAreaWidget(CDockAreaWidget* DockArea)
{
	d->DockArea = DockArea;
}


//============================================================================
CDockAreaWidget* CDockWidgetTab::dockAreaWidget() const
{
	return d->DockArea;
}


//============================================================================
void CDockWidgetTab::setIcon(const QIcon& Icon)
{
	QBoxLayout* Layout = qobject_cast<QBoxLayout*>(layout());
	if (!d->IconLabel && Icon.isNull())
	{
		return;
	}

	if (!d->IconLabel)
	{
		d->IconLabel = new QLabel();
		d->IconLabel->setAlignment(Qt::AlignVCenter);
		d->IconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
		internal::setToolTip(d->IconLabel, d->TitleLabel->toolTip());
		Layout->insertWidget(0, d->IconLabel, Qt::AlignVCenter);
		Layout->insertSpacing(1, qRound(1.5 * Layout->contentsMargins().left() / 2.0));
	}
	else if (Icon.isNull())
	{
		// Remove icon label and spacer item
		Layout->removeWidget(d->IconLabel);
		Layout->removeItem(Layout->itemAt(0));
		delete d->IconLabel;
		d->IconLabel = nullptr;
	}

	d->Icon = Icon;
	d->updateIcon();
}


//============================================================================
const QIcon& CDockWidgetTab::icon() const
{
	return d->Icon;
}


//============================================================================
QString CDockWidgetTab::text() const
{
	return d->TitleLabel->text();
}


//============================================================================
void CDockWidgetTab::mouseDoubleClickEvent(QMouseEvent *event)
{
	if (event->button() == Qt::LeftButton) 
	{
		// If this is the last dock area in a dock container it does not make
		// sense to move it to a new floating widget and leave this one
		// empty
		if ((!d->DockArea->dockContainer()->isFloating() || d->DockArea->dockWidgetsCount() > 1)
			&& d->DockWidget->features().testFlag(CDockWidget::DockWidgetFloatable))
		{
			event->accept();
			d->saveDragStartMousePosition(internal::globalPositionOf(event));
			d->startFloating(DraggingInactive);
		}
	}

	Super::mouseDoubleClickEvent(event);
}


//============================================================================
void CDockWidgetTab::setVisible(bool visible)
{
	visible &= !d->DockWidget->features().testFlag(CDockWidget::NoTab);
    Super::setVisible(visible);
}


//============================================================================
void CDockWidgetTab::setText(const QString& title)
{
	d->TitleLabel->setText(title);
}


//============================================================================
bool CDockWidgetTab::isTitleElided() const
{
	return d->TitleLabel->isElided();
}



//============================================================================
bool CDockWidgetTab::isClosable() const
{
	return d->DockWidget && d->DockWidget->features().testFlag(CDockWidget::DockWidgetClosable);
}


//===========================================================================
void CDockWidgetTab::detachDockWidget()
{
	if (!d->DockWidget->features().testFlag(CDockWidget::DockWidgetFloatable))
	{
		return;
	}

	d->saveDragStartMousePosition(QCursor::pos());
	d->startFloating(DraggingInactive);
}


//===========================================================================
void CDockWidgetTab::autoHideDockWidget()
{
	d->DockWidget->setAutoHide(true);
}


//===========================================================================
void CDockWidgetTab::onAutoHideToActionClicked()
{
	int Location = sender()->property(internal::LocationProperty).toInt();
	d->DockWidget->toggleAutoHide((SideBarLocation)Location);
}


//============================================================================
bool CDockWidgetTab::event(QEvent *e)
{
#ifndef QT_NO_TOOLTIP
	if (e->type() == QEvent::ToolTipChange)
	{
		const auto text = toolTip();
		d->TitleLabel->setToolTip(text);
		if (d->IconLabel) {
			d->IconLabel->setToolTip(text);
		}
	}
#endif
	if (e->type() == QEvent::StyleChange)
	{
		d->updateIcon();
	}
	return Super::event(e);
}


//============================================================================
void CDockWidgetTab::onDockWidgetFeaturesChanged()
{
    d->updateCloseButtonSizePolicy();
    d->updateCloseButtonVisibility(isActiveTab());
}


//============================================================================
void CDockWidgetTab::setElideMode(Qt::TextElideMode mode)
{
	d->TitleLabel->setElideMode(mode);
}


//============================================================================
void CDockWidgetTab::updateStyle()
{
	internal::repolishStyle(this, internal::RepolishDirectChildren);
}


//============================================================================
QSize CDockWidgetTab::iconSize() const
{
	return d->IconSize;
}

//============================================================================
void CDockWidgetTab::setIconSize(const QSize& Size)
{
	d->IconSize = Size;
	d->updateIcon();
}

} // namespace ads
//---------------------------------------------------------------------------
// EOF DockWidgetTab.cpp
