/*
-----------------------------------------------------------------------------
This source file is part of OGRE
(Object-oriented Graphics Rendering Engine)
For the latest info, see http://www.ogre3d.org/

Copyright (c) 2000-2013 Torus Knot Software Ltd

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-----------------------------------------------------------------------------
*/
#include "OgrePage.h"
#include "OgreRoot.h"
#include "OgrePagedWorldSection.h"
#include "OgrePagedWorld.h"
#include "OgrePageStrategy.h"
#include "OgrePageManager.h"
#include "OgreSceneNode.h"
#include "OgreSceneManager.h"
#include "OgreStreamSerialiser.h"
#include "OgrePageContentCollectionFactory.h"
#include "OgrePageContentCollection.h"

#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE_IOS
    #include "macUtils.h"
#endif

namespace Ogre
{
	//---------------------------------------------------------------------
	const uint32 Page::CHUNK_ID = StreamSerialiser::makeIdentifier("PAGE");
	const uint16 Page::CHUNK_VERSION = 1;
	const uint32 Page::CHUNK_CONTENTCOLLECTION_DECLARATION_ID = StreamSerialiser::makeIdentifier("PCNT");
	const uint16 Page::WORKQUEUE_PREPARE_REQUEST = 1;
	const uint16 Page::WORKQUEUE_CHANGECOLLECTION_REQUEST = 3;

	//---------------------------------------------------------------------
	Page::Page(PageID pageID, PagedWorldSection* parent)
		: mID(pageID)
		, mParent(parent)
		, mDeferredProcessInProgress(false)
		, mModified(false)
		, mDebugNode(0)
	{
		WorkQueue* wq = Root::getSingleton().getWorkQueue();
		mWorkQueueChannel = wq->getChannel("Ogre/Page");
		wq->addRequestHandler(mWorkQueueChannel, this);
		wq->addResponseHandler(mWorkQueueChannel, this);
		touch();
	}
	//---------------------------------------------------------------------
	Page::~Page()
	{
		WorkQueue* wq = Root::getSingleton().getWorkQueue();
		wq->removeRequestHandler(mWorkQueueChannel, this);
		wq->removeResponseHandler(mWorkQueueChannel, this);

		destroyAllContentCollections();
		if (mDebugNode)
		{
			// destroy while we have the chance
			SceneNode::ObjectIterator it = mDebugNode->getAttachedObjectIterator();
			while(it.hasMoreElements())
				mParent->getSceneManager()->destroyMovableObject(it.getNext());
			mDebugNode->removeAndDestroyAllChildren();
			mParent->getSceneManager()->destroySceneNode(mDebugNode);

			mDebugNode = 0;
		}
	}
	//---------------------------------------------------------------------
	void Page::destroyAllContentCollections()
	{
		for (ContentCollectionList::iterator i = mContentCollections.begin(); 
			i != mContentCollections.end(); ++i)
		{
			delete *i;
		}
		mContentCollections.clear();
	}
	//---------------------------------------------------------------------
	PageManager* Page::getManager() const
	{
		return mParent->getManager();
	}
	//---------------------------------------------------------------------
	void Page::touch()
	{
		mFrameLastHeld = Root::getSingleton().getNextFrameNumber();
	}
	//---------------------------------------------------------------------
	bool Page::isHeld() const
	{
		unsigned long nextFrame = Root::getSingleton().getNextFrameNumber();
		unsigned long dist;
		if (nextFrame < mFrameLastHeld)
		{
			// we must have wrapped around
			dist = mFrameLastHeld + (std::numeric_limits<unsigned long>::max() - mFrameLastHeld);
		}
		else
			dist = nextFrame - mFrameLastHeld;

		// 5-frame tolerance
		return dist <= 5;
	}
	//---------------------------------------------------------------------
	bool Page::prepareImpl(StreamSerialiser& stream, PageData* dataToPopulate)
	{

		// Now do the real loading
		if (!stream.readChunkBegin(CHUNK_ID, CHUNK_VERSION, "Page"))
			return false;

		// pageID check (we should know the ID we're expecting)
		uint32 storedID;
		stream.read(&storedID);
		if (mID != storedID)
		{
			LogManager::getSingleton().stream() << "Error: Tried to populate Page ID " << mID
				<< " with data corresponding to page ID " << storedID;
			stream.undoReadChunk(CHUNK_ID);
			return false;
		}

		PageManager* mgr = getManager();
		
		while(stream.peekNextChunkID() == CHUNK_CONTENTCOLLECTION_DECLARATION_ID)
		{
			const StreamSerialiser::Chunk* collChunk = stream.readChunkBegin();
			String factoryName;
			stream.read(&factoryName);
			stream.readChunkEnd(CHUNK_CONTENTCOLLECTION_DECLARATION_ID);
			// Supported type?
			PageContentCollectionFactory* collFact = mgr->getContentCollectionFactory(factoryName);
			if (collFact)
			{
				PageContentCollection* collInst = collFact->createInstance();
				if (collInst->prepare(stream)) // read type-specific data
				{
					dataToPopulate->collectionsToAdd.push_back(collInst);
				}
				else
				{
					LogManager::getSingleton().stream() << "Error preparing PageContentCollection type: " 
						<< factoryName << " in " << *this;
					collFact->destroyInstance(collInst);
				}
			}
			else
			{
				LogManager::getSingleton().stream() << "Unsupported PageContentCollection type: " 
					<< factoryName << " in " << *this;
				// skip
				stream.readChunkEnd(collChunk->id);
			}

		}


		mModified = false;

		return true;
	}
	//---------------------------------------------------------------------
	void Page::load(bool synchronous)
	{
		if (!mDeferredProcessInProgress)
		{
			destroyAllContentCollections();
			PageRequest req(this);
			mDeferredProcessInProgress = true;
			Root::getSingleton().getWorkQueue()->addRequest(mWorkQueueChannel, WORKQUEUE_PREPARE_REQUEST, 
				Any(req), 0, synchronous);
		}

	}
	//---------------------------------------------------------------------
	void Page::unload()
	{
		destroyAllContentCollections();
	}
	//---------------------------------------------------------------------
	bool Page::canHandleRequest(const WorkQueue::Request* req, const WorkQueue* srcQ)
	{
		PageRequest preq = any_cast<PageRequest>(req->getData());
		// only deal with own requests
		// we do this because if we delete a page we want any pending tasks to be discarded
		if (preq.srcPage != this)
			return false;
		else
			return RequestHandler::canHandleRequest(req, srcQ);

	}
	//---------------------------------------------------------------------
	bool Page::canHandleResponse(const WorkQueue::Response* res, const WorkQueue* srcQ)
	{
		PageRequest preq = any_cast<PageRequest>(res->getRequest()->getData());
		// only deal with own requests
		// we do this because if we delete a page we want any pending tasks to be discarded
		if (preq.srcPage != this)
			return false;
		else
			return true;

	}
	//---------------------------------------------------------------------
	WorkQueue::Response* Page::handleRequest(const WorkQueue::Request* req, const WorkQueue* srcQ)
	{
		// Background thread (maybe)

		PageRequest preq = any_cast<PageRequest>(req->getData());
		// only deal with own requests; we shouldn't ever get here though
		if (preq.srcPage != this)
			return 0;

		PageResponse res;
		res.pageData = OGRE_NEW PageData();
		WorkQueue::Response* response = 0;
		try
		{
			prepareImpl(res.pageData);
			response = OGRE_NEW WorkQueue::Response(req, true, Any(res));
		}
		catch (Exception& e)
		{
			// oops
			response = OGRE_NEW WorkQueue::Response(req, false, Any(res), 
				e.getFullDescription());
		}

		return response;
	}
	//---------------------------------------------------------------------
	void Page::handleResponse(const WorkQueue::Response* res, const WorkQueue* srcQ)
	{
		// Main thread
		PageResponse pres = any_cast<PageResponse>(res->getData());
		PageRequest preq = any_cast<PageRequest>(res->getRequest()->getData());

		// only deal with own requests
		if (preq.srcPage!= this)
			return;

		// final loading behaviour
		if (res->succeeded())
		{
			std::swap(mContentCollections, pres.pageData->collectionsToAdd);
			loadImpl();
		}

		OGRE_DELETE pres.pageData;

		mDeferredProcessInProgress = false;

	}
	//---------------------------------------------------------------------
	bool Page::prepareImpl(PageData* dataToPopulate)
	{
		// Procedural preparation
		if (mParent->_prepareProceduralPage(this))
			return true;
		else
		{
			// Background loading
			String filename = generateFilename();

			DataStreamPtr stream = Root::getSingleton().openFileStream(filename, 
				getManager()->getPageResourceGroup());
			StreamSerialiser ser(stream);
			return prepareImpl(ser, dataToPopulate);
		}


	}
	//---------------------------------------------------------------------
	void Page::loadImpl()
	{
		mParent->_loadProceduralPage(this);

		for (ContentCollectionList::iterator i = mContentCollections.begin();
			i != mContentCollections.end(); ++i)
		{
			(*i)->load();
		}
	}
	//---------------------------------------------------------------------
	void Page::save()
	{
		String filename = generateFilename();
		save(filename);
	}
	//---------------------------------------------------------------------
	void Page::save(const String& filename)
	{
		DataStreamPtr stream = Root::getSingleton().createFileStream(filename, 
			getManager()->getPageResourceGroup(), true);
		StreamSerialiser ser(stream);
		save(ser);
	}
	//---------------------------------------------------------------------
	void Page::save(StreamSerialiser& stream)
	{
		stream.writeChunkBegin(CHUNK_ID, CHUNK_VERSION);

		// page id
		stream.write(&mID);

		// content collections
		for (ContentCollectionList::iterator i = mContentCollections.begin();
			i != mContentCollections.end(); ++i)
		{
			// declaration
			stream.writeChunkBegin(CHUNK_CONTENTCOLLECTION_DECLARATION_ID);
			stream.write(&(*i)->getType());
			stream.writeChunkEnd(CHUNK_CONTENTCOLLECTION_DECLARATION_ID);
			// data
			(*i)->save(stream);
		}

		stream.writeChunkEnd(CHUNK_ID);

		mModified = false;
	}
	//---------------------------------------------------------------------
	void Page::frameStart(Real timeSinceLastFrame)
	{
		updateDebugDisplay();

		// content collections
		for (ContentCollectionList::iterator i = mContentCollections.begin();
			i != mContentCollections.end(); ++i)
		{
			(*i)->frameStart(timeSinceLastFrame);
		}


	}
	//---------------------------------------------------------------------
	void Page::frameEnd(Real timeElapsed)
	{
		// content collections
		for (ContentCollectionList::iterator i = mContentCollections.begin();
			i != mContentCollections.end(); ++i)
		{
			(*i)->frameEnd(timeElapsed);
		}

	}
	//---------------------------------------------------------------------
	void Page::notifyCamera(Camera* cam)
	{
		// content collections
		for (ContentCollectionList::iterator i = mContentCollections.begin();
			i != mContentCollections.end(); ++i)
		{
			(*i)->notifyCamera(cam);
		}

	}
	//---------------------------------------------------------------------
	void Page::updateDebugDisplay()
	{
		uint8 dbglvl = getManager()->getDebugDisplayLevel();
		if (dbglvl > 0)
		{
			// update debug display
			if (!mDebugNode)
			{
				mDebugNode = mParent->getSceneManager()->getRootSceneNode()->createChildSceneNode();
			}
			mParent->getStrategy()->updateDebugDisplay(this, mDebugNode);

			mDebugNode->setVisible(true);
		}
		else if (mDebugNode)
		{
			mDebugNode->setVisible(false);
		}

	}
	//---------------------------------------------------------------------
	PageContentCollection* Page::createContentCollection(const String& typeName)
	{
		PageContentCollection* coll = getManager()->createContentCollection(typeName);
		coll->_notifyAttached(this);
		mContentCollections.push_back(coll);
		return coll;
	}
	//---------------------------------------------------------------------
	void Page::destroyContentCollection(PageContentCollection* coll)
	{
		ContentCollectionList::iterator i = std::find(
			mContentCollections.begin(), mContentCollections.end(), coll);
		if (i != mContentCollections.end())
		{
			mContentCollections.erase(i);
		}
		getManager()->destroyContentCollection(coll);
	}
	//---------------------------------------------------------------------
	size_t Page::getContentCollectionCount() const
	{
		return mContentCollections.size();
	}
	//---------------------------------------------------------------------
	PageContentCollection* Page::getContentCollection(size_t index)
	{
		assert(index < mContentCollections.size());

		return mContentCollections[index];
	}
	//---------------------------------------------------------------------
	const Page::ContentCollectionList& Page::getContentCollectionList() const
	{
		return mContentCollections;
	}
	//---------------------------------------------------------------------
	SceneManager* Page::getSceneManager() const
	{
		return mParent->getSceneManager();
	}
	//---------------------------------------------------------------------
	std::ostream& operator <<( std::ostream& o, const Page& p )
	{
		o << "Page(ID:" << p.getID() << ", section:" << p.getParentSection()->getName()
			<< ", world:" << p.getParentSection()->getWorld()->getName() << ")";
		return o;
	}
	//---------------------------------------------------------------------
	String Page::generateFilename() const
	{
		StringUtil::StrStreamType str;
		if (mParent)
			str << mParent->getWorld()->getName() << "_" <<	mParent->getName();

		str	<< std::setw(8) << std::setfill('0') << std::hex << mID << ".page";

#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE_IOS
        // For the iOS we need to prefix the file name with the path to the Caches folder
        String cacheStr(Ogre::macCachePath() + str.str());
        return cacheStr;
#else
		return str.str();
#endif
	}



}

