/*
	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 "platformsystem.hpp"
#include "platform.hpp"
#include "CloonelJumpConfig.h"
#include "texture.hpp"
#include "gameplayscene.hpp"
#include "mover.hpp"
#include "circularbuffer.hpp"
#include <ciso646>
#include <cstdlib>
#include <cassert>
#include <algorithm>
#include <boost/noncopyable.hpp>

namespace cloonel {
	namespace {
		const uint32_t g_platfWidth = 104;
		const uint32_t g_platfHeight = 25;
	} //unnamed namespace

	struct PlatformInfo {
		PlatformInfo ( void ) :
			platform(nullptr),
			ticket(0)
		{
		}
		PlatformInfo ( const PlatformInfo& ) = delete;
		PlatformInfo ( Platform&& parPlatf ) noexcept :
			platform(new Platform(std::move(parPlatf))),
			ticket(0)
		{
		}
		PlatformInfo ( PlatformInfo&& parOther ) noexcept :
			platform(std::move(parOther.platform)),
			ticket(parOther.ticket)
		{
		}
		~PlatformInfo ( void ) noexcept = default;
		PlatformInfo& operator= ( const PlatformInfo& ) = delete;
		PlatformInfo& operator= ( PlatformInfo&& parOther ) {
			platform.swap(parOther.platform);
			std::swap(ticket, parOther.ticket);
			return *this;
		}

		std::unique_ptr<Platform> platform;
		Mover::PlaceableTicketType ticket;
	};

	struct PlatformSystem::LocalData : public boost::noncopyable {
		LocalData ( const char* parTexturePath, SDLMain* parSDLMain, GameplayScene* parScene, float parMaxDistance );
		~LocalData ( void ) noexcept = default;

		std::vector<PlatformInfo> platformsBuff;
		CircularBuffer<std::vector<PlatformInfo>::iterator> platforms;
		Texture texture;
		SDLMain* const sdlmain;
		GameplayScene* const scene;
		const float maxDistance;
	};

	///--------------------------------------------------------------------------
	///--------------------------------------------------------------------------
	PlatformSystem::LocalData::LocalData (const char* parTexturePath, SDLMain* parSDLMain, GameplayScene* parScene, float parMaxDistance) :
		platformsBuff(MAX_PLATFORMS_ON_SCREEN),
		platforms(platformsBuff.begin(), platformsBuff.end()),
		texture(parTexturePath, parSDLMain, false),
		sdlmain(parSDLMain),
		scene(parScene),
		maxDistance(parMaxDistance)
	{
	}

	///--------------------------------------------------------------------------
	///--------------------------------------------------------------------------
	PlatformSystem::PlatformSystem (const char* parTexturePath, SDLMain* parSDLMain, GameplayScene* parScene, float parMaxDistance) :
		Placeable(float2(0.0f)),
		m_localdata(new LocalData(parTexturePath, parSDLMain, parScene, parMaxDistance))
	{
	}

	///--------------------------------------------------------------------------
	///--------------------------------------------------------------------------
	PlatformSystem::~PlatformSystem() noexcept {
		Destroy();
	}

	///--------------------------------------------------------------------------
	///--------------------------------------------------------------------------
	void PlatformSystem::SpawnPlatforms() {
		//const int2 refWH(REFERENCE_WIDTH, REFERENCE_HEIGHT);
		//const int2 platfWH(g_platfWidth, g_platfHeight);

	//	if (m_localdata.platforms.back().platform->TopLeft().y() <= 0.0f) {
	//		const float2 newPos(static_cast<float>(std::rand() % (refWH.x() - platfWH.x())), static_cast<float>(std::rand() % (refWH.y() - platfWH.y())));

		//if (m_localdata->platforms.empty()) {
		//	const float2 newPos(static_cast<float>(std::rand() % (refWH.x() - platfWH.x())), static_cast<float>(std::rand() % (refWH.y() - platfWH.y())));
		//	m_localdata->platforms.push_back(Platform(m_localdata->sdlmain, newPos, &m_localdata->texture, static_cast<float2>(platfWH)));

		//	PlatformInfo& newPlatf = m_localdata->platforms.back();
		//	m_localdata->scene->AddDrawable(&newPlatf.platform);
		//	const Mover::PlaceableTicketType ticket = m_localdata->mover.RegisterPlaceable(&newPlatf.platform);
		//	newPlatf.ticket = ticket;
		//}
	}

	///--------------------------------------------------------------------------
	///--------------------------------------------------------------------------
	void PlatformSystem::Prepare() {
		m_localdata->texture.Reload();

		//Spawn the initial platforms
		m_localdata->platforms.reset();
		const size_t totalPlatf = m_localdata->platforms.capacity();
		const auto totalPlatfFloatInv = 1.0f / static_cast<float>(totalPlatf);
		const int2 platfWH(g_platfWidth, g_platfHeight);
		for (size_t z = 0; z < totalPlatf; ++z) {
			const float y = std::min(
				static_cast<float>(REFERENCE_HEIGHT) - 25.0f,
				static_cast<float>(z) * (static_cast<float>(REFERENCE_HEIGHT) * totalPlatfFloatInv)
			);
			const auto rndNum = std::rand() % (REFERENCE_WIDTH - platfWH.x());
			const float2 newPos(static_cast<float>(rndNum), 100.0f + std::min(m_localdata->maxDistance, y));
			m_localdata->platforms.push(Platform(m_localdata->sdlmain, newPos, &m_localdata->texture, static_cast<float2>(platfWH)));
		}
	}

	///--------------------------------------------------------------------------
	///--------------------------------------------------------------------------
	void PlatformSystem::Destroy() noexcept {
		m_localdata->texture.Destroy();

		for (size_t z = 0; z < m_localdata->platforms.size(); ++z) {
			m_localdata->mover.UnregisterPlaceable(m_localdata->platforms[z].ticket);
		}
		m_localdata->platforms.reset();
		m_localdata->platformsBuff.clear();
	}

	///--------------------------------------------------------------------------
	///--------------------------------------------------------------------------
	void PlatformSystem::AddDrawables() {
		for (size_t z = 0; z < m_localdata->platforms.size(); ++z) {
			PlatformInfo& newPlatf = m_localdata->platforms[z];
			m_localdata->scene->AddDrawable(newPlatf.platform.get());
			newPlatf.ticket = Mover::NullTicket;
		}
	}

	///--------------------------------------------------------------------------
	///--------------------------------------------------------------------------
	void PlatformSystem::OnRegister (Mover& parMover, Mover::PlaceableTicketType parTicket) {
		for (size_t z = 0; z < m_localdata->platforms.size(); ++z) {
			m_localdata->platforms[z].ticket = parMover.RegisterPlaceable(m_localdata->platforms[z].platform.get(), parTicket);
		}
	}
} //namespace cloonel
