/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2013 CERN
 * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
 *
 * This program 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 2
 * of the License, or (at your option) any later version.
 *
 * This program 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, you may find one here:
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * or you may search the http://www.gnu.org website for the version 2 license,
 * or you may write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */

#ifndef __TOOL_EVENT_H
#define __TOOL_EVENT_H

#include <cstdio>
#include <deque>

#include <math/vector2d.h>

#include <boost/optional.hpp>

class TOOL_MANAGER;

/**
 * Internal (GUI-independent) event definitions.
 * Enums are mostly self-explanatory.
 */
enum TOOL_EventCategory {
	TC_None = 0x0,
	TC_Mouse = 0x1,
	TC_Command = 0x2,
	TC_Message = 0x4,
	TC_View = 0x8,
	TC_Any = 0xffffffff
};

enum TOOL_Actions {
	TA_None = 0x0,
	TA_MouseClick = 0x1,
	TA_MouseUp = 0x2,
	TA_MouseDown = 0x4,
	TA_MouseDrag = 0x8,
	TA_MouseMotion = 0x10,
	TA_MouseWheel = 0x20,
	TA_Mouse = 0x3f,
	TA_ViewRefresh = 0x40,
	TA_ViewZoom = 0x80,
	TA_ViewPan = 0x100,
	TA_ViewDirty = 0x200,
	TA_ChangeLayer = 0x1000,

	// Tool cancel event. Issued automagically when the user hits escape or selects End Tool from the context menu.
	TA_CancelTool = 0x2000,

	// Tool activation event. Issued by the GUI upon pressing a button/menu selection.
	TA_ActivateTool = 0x4000,

	// Context menu update. Issued whenever context menu is open and the user hovers the mouse over one of choices.
	// Used in dynamic highligting in disambiguation menu 
	TA_ContextMenuUpdate = 0x8000,

	// Context menu choice. Sent if the user picked something from the context menu or closed it without selecting anything.
	TA_ContextMenuChoice = 0x10000,
	TA_Any = 0xffffffff
};

enum TOOL_MouseButtons {
	MB_None = 0x0,
	MB_Left = 0x1,
	MB_Right = 0x2,
	MB_Middle = 0x4,
	MB_ButtonMask = MB_Left | MB_Right | MB_Middle,
	MB_ModShift = 0x8,
	MB_ModCtrl = 0x10,
	MB_ModAlt = 0x20,
	MB_ModifierMask = MB_ModShift | MB_ModCtrl | MB_ModAlt,
	MB_Any = 0xffffffff
};


// Defines when a context menu is opened.
enum TOOL_ContextMenuTrigger {
	CMENU_BUTTON = 0, // On the right button
	CMENU_NOW, // Right now (after TOOL_INTERACTIVE::SetContxtMenu)
	CMENU_OFF  // Never
};

/** 
 * Class TOOL_EVENT
 * 
 * Generic, UI-independent tool event.
 */
class TOOL_EVENT
{
	public:

		const std::string Format ( ) const;

		TOOL_EVENT(TOOL_EventCategory aCategory = TC_None, TOOL_Actions aAction = TA_None ):
			m_category (aCategory),
			m_actions (aAction),
			m_mouseButtons(0) {};
		
		TOOL_EVENT(TOOL_EventCategory aCategory, TOOL_Actions aAction, int aExtraParam ):
			m_category (aCategory),
			m_actions (aAction)
			{
				if(aCategory == TC_Mouse)
					m_mouseButtons = aExtraParam;
				else if (aCategory == TC_Command)
					m_commandId = aExtraParam;
			};

		TOOL_EVENT(TOOL_EventCategory aCategory, TOOL_Actions aAction, const std::string& aExtraParam ):
			m_category (aCategory),
			m_actions (aAction),
			m_mouseButtons(0)
			{
				if(aCategory == TC_Command)
					m_commandStr = aExtraParam;
			}
		

		TOOL_EventCategory Category ( ) const
		{
			return m_category;
		}

		TOOL_Actions Action ( ) const
		{
			return m_actions;
		}

		const VECTOR2D Delta() const 
		{
			return m_mouseDelta;
		}

		const VECTOR2D& Position() const
		{
			return m_mousePos;
		}

		const VECTOR2D& DragOrigin() const
		{
			return m_mouseDragOrigin;
		}

		int Buttons() const
		{
			return m_mouseButtons;
		}

		bool IsClick ( int aButtonMask = MB_Any ) const
		{
			return (m_actions == TA_MouseClick) && ((m_mouseButtons & aButtonMask) == aButtonMask);
		} 

		bool IsDrag ( int aButtonMask = MB_Any ) const
		{
			return (m_actions == TA_MouseDrag) && ((m_mouseButtons & aButtonMask) == aButtonMask);
		} 

		bool IsMouseUp ( int aButtonMask = MB_Any ) const
		{
			return (m_actions == TA_MouseUp) && ((m_mouseButtons & aButtonMask) == aButtonMask);
		} 

		bool IsMotion ( ) const
		{
			return (m_actions == TA_MouseMotion);
		} 

		bool IsCancel ( ) const
		{
			return m_actions == TA_CancelTool;
		} 		

		bool Modifier ( int aMask = MB_ModifierMask )  const 
		{
			return (m_mouseButtons & aMask);
		}

		
		void Ignore();
	
		void SetMouseDragOrigin( const VECTOR2D &aP ) 
		{
			m_mouseDragOrigin = aP;
		}

		void SetMousePosition( const VECTOR2D& aP ) 
		{
			m_mousePos = aP;
		}

		void SetMouseDelta( const VECTOR2D& aP ) 
		{
			m_mouseDelta = aP;
		}

		bool Matches ( const TOOL_EVENT& aEvent ) const
		{
			if (! (m_category & aEvent.m_category))
				return false;

			if (! (m_actions & aEvent.m_actions))
				return false;

			if( m_category == TC_Command)
			{
				if(m_commandStr && aEvent.m_commandStr)
					return (*m_commandStr == *aEvent.m_commandStr);
				if(m_commandId && aEvent.m_commandId)
					return (*m_commandId == *aEvent.m_commandId);
			}

			return true;
		}
		
		boost::optional<int> GetCommandId()
		{
			return m_commandId;
		}


	private:

		friend class TOOL_MANAGER;

		TOOL_EventCategory m_category;
		TOOL_Actions m_actions;
		
		VECTOR2D m_mouseDelta;
		VECTOR2D m_mousePos;
		VECTOR2D m_mouseDragOrigin;
	
		int m_mouseButtons;
		boost::optional<int> m_commandId;
		boost::optional<std::string> m_commandStr;
		


};

typedef boost::optional<TOOL_EVENT> OPT_TOOL_EVENT;

/** 
 * Class TOOL_EVENT_LIST
 * 
 * A list of TOOL_EVENTs, with overloaded || operators allowing for
 * concatenating TOOL_EVENTs with little code.
 */
class TOOL_EVENT_LIST {

	public:
		typedef TOOL_EVENT value_type;
		typedef std::deque<TOOL_EVENT>::iterator iterator;
		typedef std::deque<TOOL_EVENT>::const_iterator const_iterator;

		TOOL_EVENT_LIST() {};
		TOOL_EVENT_LIST( const TOOL_EVENT& aSingleEvent )
		{
			m_events.push_back(aSingleEvent);
		}

		const std::string Format ( ) const;

		boost::optional<const TOOL_EVENT&> Matches( const TOOL_EVENT &b ) const
		{
			for(const_iterator i = m_events.begin(); i != m_events.end(); ++i)
				if (i->Matches(b))
					return *i;
			return boost::optional<const TOOL_EVENT&> ();
		}
		
		void Add ( const TOOL_EVENT& aEvent )
		{
			m_events.push_back(aEvent);
		}

		iterator begin() 
		{
			return m_events.begin();
		}
		
		iterator end() 
		{
			return m_events.end();
		}

		const_iterator cbegin()  const
		{
			return m_events.begin();
		}
		
		const_iterator cend()  const
		{
			return m_events.end();
		}

		int size() const
		{
			return m_events.size();
		}

		void clear()
		{
			m_events.clear();
		}

		TOOL_EVENT_LIST& operator=(const TOOL_EVENT_LIST& b)
		{
			m_events.clear();
			for(std::deque<TOOL_EVENT>::const_iterator i = b.m_events.begin(); i != b.m_events.end(); ++i)
				m_events.push_back(*i);
			return *this;
		}	

		TOOL_EVENT_LIST& operator=(const TOOL_EVENT& b)
		{
			m_events.clear();
			m_events.push_back(b);
			return *this;
		}
		
		TOOL_EVENT_LIST& operator||(const TOOL_EVENT& b)
		{
			Add(b);		
			return *this;
		}

		TOOL_EVENT_LIST& operator||(const TOOL_EVENT_LIST& b)
		{
			
			return *this;		
		}

	private:
		std::deque<TOOL_EVENT> m_events;
};

inline const TOOL_EVENT_LIST operator || (const TOOL_EVENT& a, const TOOL_EVENT &b )
{
	TOOL_EVENT_LIST l;

	l.Add(a);
	l.Add(b);

	return l;
}

inline const TOOL_EVENT_LIST operator || (const TOOL_EVENT & a, const TOOL_EVENT_LIST &b )
{
	TOOL_EVENT_LIST l(b);
	
	l.Add(a);
	return l;
}

#endif
