#include <mof/widget/scroll_view.hpp>
#include <mof/widget/Menu.hpp>
#include <mof/GraphicsDevice.hpp>
#include <mof/ConsoleIO.hpp>
#include <mof/utilities.hpp>
#include <mof/stream/ReferenceWrapper.hpp>
#include <mof/stream/KeyFrameAnimation.hpp>
    
namespace mof
{
namespace widget
{
//{{{ struct impl
    struct scroll_view::impl
    {
        std::shared_ptr<Menu> menu_; 
		mof::Vector2D preferred_size_;
		mof::ReferenceWrapper<mof::Vector2D> ref_offset_;
		mof::Vector2D offset_;
		size_t last_selected_index_;
		frame_t delay_frame_;

        impl(std::shared_ptr<Menu> menu)
        : menu_(menu), offset_(0, 0), last_selected_index_(menu->getSelectedIndex()), delay_frame_(10)
        {
        }
    
        ~impl()
        {
        }

		void update_offset(const mof::Vector2D& offset)
		{
			KeyFrameAnimation<Vector2D>::KeyFrame keyframes[] = {
				makeKeyFrame<Vector2D>(0, offset_),
				makeKeyFrame<Vector2D>(delay_frame_, offset_ + offset)
			};
			auto tween = makeKeyFrameAnimationHandler(keyframes[0], lastOf(keyframes));
			offset_ = offset_ + offset;
			ref_offset_.replace(0, menu_->getView()->getPositionStream(), tween);

		}
    
    };
//}}}
//{{{ constructor
    scroll_view::scroll_view(std::shared_ptr<Menu> menu)
    : 
        pimpl_(new impl(menu)) 
    {
        pimpl_->menu_->getView()->getPositionStream() << getPositionStream() << pimpl_->ref_offset_.makeRef(mof::Vector2D(0, 0)); 
        pimpl_->menu_->getView()->getColorStream()    << getColorStream(); 
		pimpl_->preferred_size_ = pimpl_->menu_->getView()->getPreferredSize();// the initial scale depends on the body.
        getSizeStream() << pimpl_->preferred_size_;
        pimpl_->menu_->getView()->getSizeStream() << getSizeStream();
    }
//}}}
//{{{ destructor
    scroll_view::~scroll_view()
    {
    }
//}}}
//{{{ update
    void scroll_view::update( )
    {
		using namespace mof;
		
        m_positionStream.update( );
        m_sizeStream.update( );
        m_colorStream.update( );
        pimpl_->menu_->getView()->update();

		size_t selected_index = pimpl_->menu_->getSelectedIndex();
		if (selected_index == pimpl_->last_selected_index_) return;
		pimpl_->last_selected_index_ = selected_index;

		// check selected index of menu 
		auto item = pimpl_->menu_->getSelectedView();
		Vector2D item_position = item->getPositionStream().value();
		Vector2D item_size = item->getSizeStream().value();
		Vector2D position = getPositionStream().value();
		Vector2D size = getSizeStream().value();

		if (position.y + size.y < item_position.y + item_size.y) {
			// setup scroll down
			Vector2D offset(0.0f, position.y + size.y - item_position.y - item_size.y);
			pimpl_->update_offset(offset);
		}
		else if (item_position.y < position.y) {
			// setup scroll up
			Vector2D offset(0.0f, position.y - item_position.y);
			pimpl_->update_offset(offset);
		}



    }
//}}}
//{{{ draw
    void scroll_view::draw( ) const
    {
		int width = mof::GraphicsDevice::getViewportWidth();
		int height = mof::GraphicsDevice::getViewportHeight();
		mof::Vector2D position = m_positionStream.value();
		mof::Vector2D scale = m_sizeStream.value();
		if (0 == scale.x || 0 == scale.y) return;// prevent the area of viewport from vanishing. 
		mof::GraphicsDevice::setViewport(mof::Rectangle<mof::real>(position.x, position.y, position.x + scale.x, position.y + scale.y));
        pimpl_->menu_->getView()->draw( );
		mof::GraphicsDevice::setViewport(mof::Rectangle<mof::real>(0, 0, width, height));// restore the viewport.
    }
//}}}
//{{{ setVisible
    void scroll_view::setVisible(bool visible)
    {
		pimpl_->menu_->getView()->setVisible(visible);
    }
//}}}
//{{{ show
    FrameNumber scroll_view::show(bool immediately) 
    {
        return pimpl_->menu_->getView()->show(immediately);
    }
//}}}
//{{{ hide
    FrameNumber scroll_view::hide(bool immediately)
    {
        return pimpl_->menu_->getView()->hide(immediately);
    }
//}}}
//{{{ focus
    FrameNumber scroll_view::focus(bool immediately) 
    {
        return pimpl_->menu_->getView()->focus(immediately);
    }
//}}}
//{{{ blur
    FrameNumber scroll_view::blur(bool immediately)
    {
        return pimpl_->menu_->getView()->blur(immediately);
    }
//}}}
//{{{ click
    FrameNumber scroll_view::click(bool immediately)
    {
        return pimpl_->menu_->getView()->click(immediately);
    }
//}}}
//{{{ getPreferredSize
	mof::Vector2D scroll_view::getPreferredSize() const
    {
        return pimpl_->preferred_size_;
    }
//}}}
}
} // namespace mof
