// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE

#if VSTGUI_LIVE_EDITING

#include "uiselection.h"
#include "../iviewfactory.h"
#include "../uidescription.h"
#include "../cstream.h"
#include "../uiattributes.h"
#include "../../lib/cviewcontainer.h"
#include "../../lib/cframe.h"
#include "../../lib/ccolor.h"
#include "../../lib/coffscreencontext.h"
#include "../../lib/clayeredviewcontainer.h"
#include "../../lib/cbitmap.h"
#include <sstream>
#include <algorithm>

namespace VSTGUI {

//----------------------------------------------------------------------------------------------------
UISelection::UISelection (int32_t style)
: style (style)
{
}

//----------------------------------------------------------------------------------------------------
UISelection::~UISelection ()
{
	clear ();
}

//----------------------------------------------------------------------------------------------------
auto UISelection::begin () const -> const_iterator
{
	return viewList.begin ();
}

//----------------------------------------------------------------------------------------------------
auto  UISelection::end () const -> const_iterator
{
	return viewList.end ();
}

//----------------------------------------------------------------------------------------------------
auto UISelection::rbegin () const -> const_reverse_iterator
{
	return viewList.rbegin ();
}

//----------------------------------------------------------------------------------------------------
auto UISelection::rend () const -> const_reverse_iterator
{
	return viewList.rend ();
}

//----------------------------------------------------------------------------------------------------
void UISelection::setStyle (int32_t _style)
{
	style = _style;
}

//----------------------------------------------------------------------------------------------------
void UISelection::add (CView* view)
{
	vstgui_assert (view, "view cannot be nullptr");
	willChange ();
	if (style == kSingleSelectionStyle)
		clear ();
	viewList.emplace_back (view);
	didChange ();
}

//----------------------------------------------------------------------------------------------------
void UISelection::remove (CView* view)
{
	vstgui_assert (view, "view cannot be nullptr");
	if (contains (view))
	{
		willChange ();
		viewList.remove (view);
		didChange ();
	}
}

//----------------------------------------------------------------------------------------------------
void UISelection::setExclusive (CView* view)
{
	vstgui_assert (view, "view cannot be nullptr");
	if (viewList.size () == 1 && viewList.front () == view)
		return;
	UISelection::DeferChange dc (*this);
	viewList.clear ();
	add (view);
}

//----------------------------------------------------------------------------------------------------
void UISelection::clear ()
{
	willChange ();
	viewList.clear ();
	didChange ();
}

//----------------------------------------------------------------------------------------------------
bool UISelection::contains (CView* view) const
{
	return std::find (begin (), end (), view) != end ();
}

//----------------------------------------------------------------------------------------------------
bool UISelection::containsParent (CView* view) const
{
	CView* parent = view->getParentView ();
	if (parent)
	{
		if (contains (parent))
			return true;
		return containsParent (parent);
	}
	return false;
}

//----------------------------------------------------------------------------------------------------
int32_t UISelection::total () const
{
	return (int32_t)viewList.size ();
}

//----------------------------------------------------------------------------------------------------
CView* UISelection::first () const
{
	if (!viewList.empty ())
		return *begin ();
	return nullptr;
}

//----------------------------------------------------------------------------------------------------
CRect UISelection::getBounds () const
{
	CRect result;
	if (viewList.empty ())
		return result;
	const_iterator it = begin ();
	result = getGlobalViewCoordinates (*it);
	++it;
	while (it != end ())
	{
		CRect vs = getGlobalViewCoordinates (*it);
		result.unite (vs);
		++it;
	}
	return result;
}

//----------------------------------------------------------------------------------------------------
CRect UISelection::getGlobalViewCoordinates (CView* view)
{
	CRect result = view->translateToGlobal (view->getViewSize ());
	if (auto frame = view->getFrame ())
		return frame->getTransform ().inverse ().transform (result);
	return result;
}

//----------------------------------------------------------------------------------------------------
void UISelection::moveBy (const CPoint& p)
{
	viewsWillChange ();
	const_iterator it = begin ();
	while (it != end ())
	{
		if (!containsParent ((*it)))
		{
			CRect viewRect = (*it)->getViewSize ();
			viewRect.offset (p.x, p.y);
			(*it)->setViewSize (viewRect);
			(*it)->setMouseableArea (viewRect);
		}
		it++;
	}
	viewsDidChange ();
}

//----------------------------------------------------------------------------------------------------
void UISelection::sizeBy (const CRect& r)
{
	viewsWillChange ();
	for (auto view : *this)
	{
		auto viewSize = view->getViewSize ();
		viewSize.left += r.left;
		viewSize.top += r.top;
		viewSize.right += r.right;
		viewSize.bottom += r.bottom;
		view->setViewSize (viewSize);
		view->setMouseableArea (viewSize);
	}
	viewsDidChange ();
}

//----------------------------------------------------------------------------------------------------
void UISelection::invalidRects () const
{
	const_iterator it = begin ();
	while (it != end ())
	{
		if (!containsParent ((*it)))
		{
			(*it)->invalid ();
		}
		it++;
	}
}

//----------------------------------------------------------------------------------------------------
void UISelection::willChange ()
{
	if (++inChange == 1)
		forEachListener ([this] (IUISelectionListener* l) { l->selectionWillChange (this); });
}

//----------------------------------------------------------------------------------------------------
void UISelection::didChange ()
{
	if (--inChange == 0)
		forEachListener ([this] (IUISelectionListener* l) { l->selectionDidChange (this); });
}

//----------------------------------------------------------------------------------------------------
void UISelection::viewsWillChange ()
{
	if (++inViewsChange == 1)
	{
		invalidRects ();
		forEachListener ([this] (IUISelectionListener* l) { l->selectionViewsWillChange (this); });
	}
}

//----------------------------------------------------------------------------------------------------
void UISelection::viewsDidChange ()
{
	if (--inViewsChange == 0)
	{
		invalidRects ();
		forEachListener ([this] (IUISelectionListener* l) { l->selectionViewsDidChange (this); });
	}
}

//----------------------------------------------------------------------------------------------------
bool UISelection::store (OutputStream& stream, IUIDescription* uiDescription)
{
	UIDescription* desc = dynamic_cast<UIDescription*>(uiDescription);
	if (desc)
	{
		std::list<CView*> views;
		for (auto view : *this)
		{
			if (!containsParent (view))
			{
				views.emplace_back (view);
			}
		}
		
		auto attr = makeOwned<UIAttributes> ();
		attr->setPointAttribute ("selection-drag-offset", dragOffset);
		return desc->storeViews (views, stream, attr);
	}
	return false;
}

//----------------------------------------------------------------------------------------------------
bool UISelection::restore (InputStream& stream, IUIDescription* uiDescription)
{
	clear ();
	UIDescription* desc = dynamic_cast<UIDescription*>(uiDescription);
	if (desc)
	{
		UIAttributes* attr = nullptr;
		if (desc->restoreViews (stream, viewList, &attr))
		{
			if (attr)
			{
				attr->getPointAttribute ("selection-drag-offset", dragOffset);
				attr->forget ();
			}
			return true;
		}
	}
	return false;
}

//----------------------------------------------------------------------------------------------------
SharedPointer<CBitmap> createBitmapFromSelection (UISelection* selection, CFrame* frame,
                                                  CViewContainer* anchorView)
{
	CRect selectionRect = selection->getBounds ();
	auto scaleFactor = frame->getScaleFactor ();
	auto bitmap = renderBitmapOffscreen (selectionRect.getSize (), scaleFactor, [&] (auto& context) {
		CGraphicsTransform tm;
		CGraphicsTransform invTm;
		if (anchorView)
		{
			tm = anchorView->getTransform ();
			invTm = tm.inverse ();
			invTm.transform (selectionRect);
			tm = tm * CGraphicsTransform ().translate (-selectionRect.left, -selectionRect.top);
		}
		double originalZoom = 1.;
		if (anchorView && anchorView->isAttached ())
		{
			// reset frame zoom
			originalZoom = anchorView->getFrame ()->getZoom ();
			anchorView->getFrame ()->setZoom (1.);
		}
		CDrawContext::Transform tr (context, tm);
		for (auto view : *selection)
		{
			if (!selection->containsParent (view))
			{
				CPoint p;
				p = view->translateToGlobal (p);
				if (anchorView)
					invTm.transform (p);
				CDrawContext::Transform transform (context,
				                                   CGraphicsTransform ().translate (p.x, p.y));
				context.setClipRect (view->getViewSize ());
				view->drawRect (&context, view->getViewSize ());
			}
		}
		if (anchorView && anchorView->isAttached ())
		{
			anchorView->getFrame ()->setZoom (originalZoom);
		}
	});
	return bitmap;
}

} // VSTGUI

#endif // VSTGUI_LIVE_EDITING
