/*
	Copyright 2014 Michele "King_DuckZ" Santullo

	This file is part of CloonelJump.

	CloonelJump 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 3 of the License, or
	(at your option) any later version.

	CloonelJump 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 CloonelJump.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "inputbag.hpp"
#include "key.hpp"
#include <vector>
#include <map>
#include <cassert>
#include <algorithm>
#include <ciso646>

namespace cloonel {
	struct Action {
		Key key;
		const std::string name;
		InputBag::ActionStateType state;
		bool newStatePressed;

		bool operator== ( const Key& parKey ) const { return key == parKey; }
	};

	struct InputBag::LocalData {
		std::vector<Action> actions;
		std::map<int, Action*> mappings;
	};

	namespace {
		//      | rl  | pr
		// -----------------
		// rel  | rel | jpr
		// prs  | jrl | prs
		// jrl  | rel | jpr
		// jpr  | jrl | prs
		const InputBag::ActionStateType g_stateTruthTable[] = {
			InputBag::ActionState_Released,
			InputBag::ActionState_JustReleased,
			InputBag::ActionState_Released,
			InputBag::ActionState_JustReleased,
			InputBag::ActionState_JustPressed,
			InputBag::ActionState_Pressed,
			InputBag::ActionState_JustPressed,
			InputBag::ActionState_Pressed
		};

		///----------------------------------------------------------------------
		///When actions vector is reallocated, update pointers in mappings.
		///----------------------------------------------------------------------
		void UpdatePointers (Action* parOldAddrStart, Action* parNewAddress, std::map<int, Action*>& parUpdate, int parOffset) {
			for (auto& itMap : parUpdate) {
				if (itMap.second >= parOldAddrStart) {
					itMap.second = parNewAddress + (itMap.second - parOldAddrStart) + parOffset;
				}
			}
		}
	} //unnamed namespace

	///--------------------------------------------------------------------------
	///--------------------------------------------------------------------------
	InputBag::InputBag() :
		m_localdata(new LocalData)
	{
	}

	///--------------------------------------------------------------------------
	///--------------------------------------------------------------------------
	InputBag::~InputBag() noexcept {
	}

	///--------------------------------------------------------------------------
	///--------------------------------------------------------------------------
	void InputBag::AddAction (int parAction, const Key& parKey, std::string&& parName) {
		Action* const oldBuff = m_localdata->actions.data();
		m_localdata->actions.push_back(Action({parKey, parName, ActionState_Released, false}));
		Action* const newBuff = m_localdata->actions.data();
		if (oldBuff != newBuff) {
			UpdatePointers(oldBuff, newBuff, m_localdata->mappings, 0);
		}
		m_localdata->mappings[parAction] = &m_localdata->actions.back();
	}

	///--------------------------------------------------------------------------
	///--------------------------------------------------------------------------
	InputBag::ActionStateType InputBag::ActionState (int parAction) const {
		assert(m_localdata->mappings.find(parAction) != m_localdata->mappings.end());
		return m_localdata->mappings[parAction]->state;
	}

	///--------------------------------------------------------------------------
	///--------------------------------------------------------------------------
	void InputBag::Clear() {
		m_localdata->actions.clear();
		m_localdata->mappings.clear();
	}

	///--------------------------------------------------------------------------
	///--------------------------------------------------------------------------
	void InputBag::NotifyKeyAction (InputDeviceType parDev, int parScancode, bool parPressed) {
		const Key searchKey(parDev, parScancode);
		auto itFound = std::find(m_localdata->actions.begin(), m_localdata->actions.end(), searchKey);
		//This method gets called every time a keypress or keyrelease is
		//produced, but if the key is of any use is decided here. So it possible
		//that a keystroke is not bound to any action and therefore no
		//corresponding element is present in the actions list.
		if (m_localdata->actions.end() != itFound)
			itFound->newStatePressed = parPressed;
	}

	///--------------------------------------------------------------------------
	///--------------------------------------------------------------------------
	void InputBag::KeyStateUpdateFinished() noexcept {
		for (auto& currAction : m_localdata->actions) {
			const int numericState = static_cast<int>(currAction.state);
			assert(numericState < 4);
			const int index = (currAction.newStatePressed ? 4 : 0) + numericState;
			currAction.state = g_stateTruthTable[index];
		}
	}

	///--------------------------------------------------------------------------
	///--------------------------------------------------------------------------
	void InputBag::ClearState() {
		for (auto& currAction : m_localdata->actions) {
			currAction.newStatePressed = false;
			currAction.state = ActionState_Released;
		}
	}

	///--------------------------------------------------------------------------
	///--------------------------------------------------------------------------
	bool IsPressed (const InputBag& parInput, int parAction) {
		const InputBag::ActionStateType state = parInput.ActionState(parAction);
		return InputBag::ActionState_Pressed == state or InputBag::ActionState_JustPressed == state;
	}
} //namespace cloonel
