////////////////////////////////////////////////////////////////////////////////////////
//
// Nestopia - NES/Famicom emulator written in C++
//
// Copyright (C) 2003-2008 Martin Freij
//
// This file is part of Nestopia.
//
// Nestopia 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.
//
// Nestopia 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 Nestopia; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
////////////////////////////////////////////////////////////////////////////////////////

#ifndef NST_MANAGER_INPUT_H
#define NST_MANAGER_INPUT_H

#pragma once

#include "NstDirectInput.hpp"
#include "NstResourceCursor.hpp"

namespace Nestopia
{
	namespace Window
	{
		class Input;
	}

	namespace Managers
	{
		class Input : Manager
		{
		public:

			typedef Object::Delegate<void,Window::Rect&> Screening;

			Input
			(
				Window::Custom&,
				Window::Menu&,
				Emulator&,
				const Configuration&,
				const Screening&,
				const Screening&
			);

			~Input();

			void Save(Configuration&) const;
			void StartEmulation();
			void StopEmulation();

		private:

			void ForcePoll();

			bool SetAdapter(uint) const;
			void SyncControllers(uint,Nes::Input::Type,uint) const;

			void UpdateDevices();
			void UpdateSettings();
			void UpdateAdapterPort(Nes::Input::Adapter) const;

			void OnMenuPort1(const Window::Menu::PopupHandler::Param&);
			void OnMenuPort2(const Window::Menu::PopupHandler::Param&);
			void OnMenuPort3(const Window::Menu::PopupHandler::Param&);
			void OnMenuPort4(const Window::Menu::PopupHandler::Param&);
			void OnMenuPort5(const Window::Menu::PopupHandler::Param&);
			void OnEmuEvent(Emulator::Event,Emulator::Data);
			void OnCmdMachineAutoSelectController(uint);
			void OnCmdMachinePort(uint);
			void OnCmdMachineAdapter(uint);
			void OnCmdOptionsInput(uint);

			class Callbacks;

			class Cursor
			{
			public:

				Cursor(Window::Custom&,const Screening&,const Screening&);
				~Cursor();

				void Acquire(Emulator&);
				void Unacquire();
				void AutoHide();
				bool Poll(uint&,uint&,uint&,uint* = NULL) const;

			private:

				enum
				{
					TIME_OUT    = 3000,
					NO_DEADLINE = DWORD(~0UL),
					WHEEL_MIN   = -WHEEL_DELTA*30,
					WHEEL_MAX   = +WHEEL_DELTA*30,
					WHEEL_SCALE = 56
				};

				ibool OnNop            (Window::Param&);
				ibool OnSetCursor      (Window::Param&);
				ibool OnMouseMove      (Window::Param&);
				ibool OnLButtonDown    (Window::Param&);
				ibool OnRButtonDown    (Window::Param&);
				ibool OnRButtonDownNop (Window::Param&);
				ibool OnButtonUp       (Window::Param&);
				ibool OnWheel          (Window::Param&);

				HCURSOR hCursor;
				HCURSOR hCurrent;
				DWORD deadline;
				Window::Custom& window;
				int wheel;
				const Screening getInputRect;
				const Screening getOutputRect;
				const Resource::Cursor gun;
				const uint primaryButtonId;
				const uint secondaryButtonId;

			public:

				int GetWheel() const
				{
					return wheel / WHEEL_SCALE;
				}

				bool MustAutoHide() const
				{
					return deadline != NO_DEADLINE && deadline <= ::GetTickCount();
				}
			};

			class Commands
			{
				typedef DirectX::DirectInput::Key Key;

			public:

				Commands(Window::Custom&,DirectX::DirectInput&);
				~Commands();

				void BeginAdd();
				void Add(const Key&,uint);
				void EndAdd();
				void Acquire();
				void Unacquire();

			private:

				bool ForcePoll();
				void Update();
				uint OnTimer();
				void OnFocus(Window::Param&);

				enum
				{
					POLL_RAPID    = 50,
					POLL_REST     = 1000,
					CLOCK_DEFAULT = 1,
					CLOCK_STOP    = 0
				};

				struct CmdKey : Key
				{
					CmdKey(const Key&,uint);

					uint cmd;
					bool prev;
				};

				typedef Collection::Vector<CmdKey> Keys;

				bool acquired;
				Keys keys;
				uint clock;
				Window::Custom& window;
				DirectX::DirectInput& directInput;

				bool CanPoll() const
				{
					return !keys.Empty() && window.Focused();
				}

			public:

				void Poll()
				{
					if (CanPoll())
						ForcePoll();
				}
			};

			class Clipboard : Manager
			{
			public:

				Clipboard(Emulator&,Window::Menu&);

				enum Type
				{
					FAMILY,
					SUBOR
				};

				uint Query(const uchar* NST_RESTRICT,Type);
				void Clear();
				void operator ++ ();

			private:

				bool CanPaste() const;
				void OnCmdMachineKeyboardPaste(uint);
				void OnMenuKeyboard(const Window::Menu::PopupHandler::Param&);

				uint pos;
				uchar releasing;
				uchar hold;
				bool shifted;
				bool paste;
				String::Heap<wchar_t> buffer;

			public:

				bool Shifted() const
				{
					return shifted;
				}

				void Shift()
				{
					shifted = true;
				}

				uint operator * ()
				{
					return releasing ? (--releasing, UINT_MAX) : buffer[pos];
				}
			};

			class AutoFire : public ImplicitBool<AutoFire>
			{
			public:

				inline AutoFire();
				void operator = (uint);

			private:

				uint step;
				uint signal;

			public:

				bool operator ! () const
				{
					return step < signal;
				}

				void operator ++ ()
				{
					step = (step < signal) ? (step + 1) : 0;
				}
			};

			AutoFire autoFire;
			Cursor cursor;
			Nes::Input::Controllers nesControllers;
			DirectX::DirectInput directInput;
			Object::Heap<Window::Input> dialog;
			Commands commands;
			Clipboard clipboard;

		public:

			void Calibrate(bool full)
			{
				directInput.Calibrate( full );
			}

			Nes::Input::Controllers* GetOutput()
			{
				++autoFire;
				return &nesControllers;
			}

			void Poll()
			{
				ForcePoll();

				if (cursor.MustAutoHide())
					cursor.AutoHide();
			}
		};
	}
}

#endif
