#include "EmberPch.h"
#include "RendererBase.h"

namespace EmberNs
{
/// <summary>
/// Constructor that sets default values.
/// The thread count is set to the number of cores detected on the system.
/// </summary>
RendererBase::RendererBase()
{
	ThreadCount(Timing::ProcessorCount());
}

/// <summary>
/// Non-virtual processing functions.
/// </summary>

/// <summary>
/// Abort the render and call a function to do something, most likely change a value.
/// Then update the current process action to the one specified.
/// The current process action will only be set if it makes sense based
/// on the current process state. If the value specified doesn't make sense
/// the next best choice will be made. If nothing makes sense, a complete
/// re-render will be triggered on the next call to Run().
/// </summary>
/// <param name="func">The function to execute</param>
/// <param name="action">The desired process action</param>
void RendererBase::ChangeVal(std::function<void(void)> func, eProcessAction action)
{
	Abort();
	EnterRender();
	func();

	//If they want a full render, don't bother inspecting process state, just start over.
	if (action == eProcessAction::FULL_RENDER)
	{
		m_ProcessState = eProcessState::NONE;
		m_ProcessAction = eProcessAction::FULL_RENDER;
	}
	//Keep iterating is when rendering has completed and the user increases the quality.
	//Rendering can be started where it left off by adding just the difference between the
	//new and old quality values.
	else if (action == eProcessAction::KEEP_ITERATING)
	{
		if ((m_ProcessState == eProcessState::ACCUM_DONE || m_ProcessState == eProcessState::ITER_STARTED) && TemporalSamples() == 1)
		{
			m_ProcessState = eProcessState::ITER_STARTED;
			m_ProcessAction = eProcessAction::KEEP_ITERATING;
		}
		else//Invaid process state to handle KEEP_ITERATING, so just start over.
		{
			m_ProcessState = eProcessState::NONE;
			m_ProcessAction = eProcessAction::FULL_RENDER;
		}
	}
	else if (action == eProcessAction::FILTER_AND_ACCUM)
	{
		//If in the middle of a render, cannot skip to filtering or accum, so just start over.
		if (m_ProcessState == eProcessState::NONE || m_ProcessState == eProcessState::ITER_STARTED)
		{
			m_ProcessState = eProcessState::NONE;
			m_ProcessAction = eProcessAction::FULL_RENDER;
		}
		//Set the state to ITER_DONE and the next process action to FILTER_AND_ACCUM.
		else
		{
			m_ProcessState = eProcessState::ITER_DONE;
			m_ProcessAction = eProcessAction::FILTER_AND_ACCUM;
		}
	}
	//Run accum only.
	else if (action == eProcessAction::ACCUM_ONLY)
	{
		//Doesn't make sense if in the middle of iterating, so just start over.
		if (m_ProcessState == eProcessState::NONE || m_ProcessState == eProcessState::ITER_STARTED)
		{
			m_ProcessAction = eProcessAction::FULL_RENDER;
		}
		else if (m_ProcessState == eProcessState::ITER_DONE)//If iterating is done, can start at density filtering and proceed.
		{
			m_ProcessAction = eProcessAction::FILTER_AND_ACCUM;
		}
		else if (m_ProcessState == eProcessState::FILTER_DONE)//Density filtering is done, so the process action is assigned as desired.
		{
			m_ProcessAction = eProcessAction::ACCUM_ONLY;
		}
		else if (m_ProcessState == eProcessState::ACCUM_DONE)//Final accum is done, so back up and run final accum again.
		{
			m_ProcessState = eProcessState::FILTER_DONE;
			m_ProcessAction = eProcessAction::ACCUM_ONLY;
		}
	}

	LeaveRender();
}

/// <summary>
/// Return the amount of memory needed for the histogram.
/// </summary>
/// <returns>The memory required for the histogram to render the current ember</returns>
size_t RendererBase::HistMemoryRequired(size_t strips)
{
	bool newFilterAlloc = false;
	CreateSpatialFilter(newFilterAlloc);
	CreateTemporalFilter(newFilterAlloc);
	ComputeBounds();
	//Because ComputeBounds() was called, this includes gutter.
	return (SuperSize() * HistBucketSize()) / strips;
}

/// <summary>
/// Return a pair whose first member contains the amount of memory needed for the histogram,
/// and whose second member contains the total the amount of memory needed to render the current ember.
/// Optionally include the memory needed for the final output image in pair.second.
/// Note that the memory required for the final output image will be doubled if threaded writes
/// are used because a copy of the final output is passed to a thread.
/// </summary>
/// <param name="strips">The number of strips being used</param>
/// <param name="includeFinal">If true include the memory needed for the final output image, else don't.</param>
/// <param name="threadedWrite">Whether the caller will be writing the output in a thread, which doubles the memory required for the final output buffer.</param>
/// <returns>The histogram memory required in first, and the total memory required in second</returns>
pair<size_t, size_t> RendererBase::MemoryRequired(size_t strips, bool includeFinal, bool threadedWrite)
{
	pair<size_t, size_t> p;
	size_t outSize = includeFinal ? FinalBufferSize() : 0;
	outSize *= (threadedWrite ? 2 : 1);
	p.first = HistMemoryRequired(strips);
	p.second = (p.first * 2) + outSize;//Multiply hist by 2 to account for the density filtering buffer which is the same size as the histogram.
	return p;
}

/// <summary>
/// Get a copy of the vector of random contexts.
/// Useful for debugging because the returned vector can be used for future renders to
/// produce the exact same output.
/// </summary>
/// <returns>The vector of random contexts to assign</returns>
vector<QTIsaac<ISAAC_SIZE, ISAAC_INT>> RendererBase::RandVec() { return m_Rand; }

/// <summary>
/// Set the vector of random contexts.
/// Assignment will only take place if the size of the vector matches
/// the number of threads used for rendering.
/// Reset the rendering process.
/// </summary>
/// <param name="randVec">The vector of random contexts to assign</param>
/// <returns>True if the size of the vector matched the number of threads used for rendering, else false.</returns>
bool RendererBase::RandVec(vector<QTIsaac<ISAAC_SIZE, ISAAC_INT>>& randVec)
{
	bool b = false;

	if (randVec.size() == ThreadCount())
	{
		ChangeVal([&]
		{
			m_Rand = randVec;
			b = true;
		}, eProcessAction::FULL_RENDER);
	}

	return b;
}

/// <summary>
/// Resize the passed in vector to be large enough to handle the output image.
/// If m_ReclaimOnResize is true, and the vector is already larger than needed,
/// it will be shrunk to the needed size. However if m_ReclaimOnResize is false,
/// it will be left alone if already large enough.
/// ComputeBounds() must be called before calling this function.
/// </summary>
/// <param name="pixels">The vector to allocate</param>
/// <returns>True if the vector contains enough space to hold the output image</returns>
bool RendererBase::PrepFinalAccumVector(vector<v4F>& pixels)
{
	EnterResize();
	const auto size = FinalDimensions();

	if (m_ReclaimOnResize)
	{
		if (pixels.size() != size)
		{
			pixels.resize(size);
			pixels.shrink_to_fit();
		}
	}
	else
	{
		if (pixels.size() < size)
			pixels.resize(size);
	}

	LeaveResize();
	return pixels.size() >= size;//Ensure allocation went ok.
}

/// <summary>
/// Virtual processing functions.
/// </summary>

/// <summary>
/// Get a status indicating whether this renderer is ok.
/// Return true for this class, derived classes will inspect GPU hardware
/// to determine if they are ok.
/// </summary>
/// <returns>Always true for this class</returns>
bool RendererBase::Ok() const
{
	return true;
}

/// <summary>
/// The amount of RAM available to render with.
/// </summary>
/// <returns>An unsigned 64-bit integer specifying how much memory is available</returns>
size_t RendererBase::MemoryAvailable()
{
	size_t memAvailable = 0;
#ifdef _WIN32
	MEMORYSTATUSEX stat;
	stat.dwLength = sizeof(stat);
	GlobalMemoryStatusEx(&stat);
	memAvailable = stat.ullTotalPhys;
#elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGESIZE)
	memAvailable = sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGESIZE);
#elif defined __APPLE__
#ifdef __LP64__
	long physmem;
	size_t len = sizeof(physmem);
	static int mib[2] = { CTL_HW, HW_MEMSIZE };
#else
	size_t physmem;
	size_t len = sizeof(physmem);
	static int mib[2] = { CTL_HW, HW_PHYSMEM };
#endif

	if (sysctl(mib, 2, &physmem, &len, nullptr, 0) == 0 && len == sizeof(physmem))
	{
		memAvailable = physmem;
	}
	else
	{
		cout << "Warning: unable to determine physical memory.\n";
		memAvailable = 4e9;
	}

#else
	cout << "Warning: unable to determine physical memory.\n";
	memAvailable = 4e9;
#endif
	return memAvailable;
}

/// <summary>
/// Non-virtual renderer properties, getters only.
/// </summary>

size_t		   RendererBase::SuperRasW()				   const { return m_SuperRasW; }
size_t		   RendererBase::SuperRasH()				   const { return m_SuperRasH; }
size_t		   RendererBase::SuperSize()				   const { return m_SuperSize; }
size_t		   RendererBase::FinalRowSize()				   const { return FinalRasW() * PixelSize(); }
size_t		   RendererBase::FinalDimensions()			   const { return FinalRasW() * FinalRasH(); }
size_t		   RendererBase::FinalBufferSize()			   const { return FinalRowSize() * FinalRasH(); }
size_t		   RendererBase::PixelSize()				   const { return NumChannels() * BytesPerChannel(); }
size_t		   RendererBase::GutterWidth()				   const { return m_GutterWidth; }
size_t		   RendererBase::DensityFilterOffset()		   const { return m_DensityFilterOffset; }
size_t         RendererBase::TotalIterCount(size_t strips) const { return static_cast<size_t>(static_cast<size_t>(Round(ScaledQuality())) * FinalRasW() * FinalRasH() * strips); }//Use Round() because there can be some roundoff error when interpolating.
size_t         RendererBase::ItersPerTemporalSample()	   const { return static_cast<size_t>(std::ceil(static_cast<double>(TotalIterCount(1)) / static_cast<double>(TemporalSamples()))); }//Temporal samples is used with animation, which doesn't support strips, so pass 1.
eProcessState  RendererBase::ProcessState()				   const { return m_ProcessState; }
eProcessAction RendererBase::ProcessAction()			   const { return m_ProcessAction; }
EmberStats     RendererBase::Stats()					   const { return m_Stats; }

/// <summary>
/// Non-virtual render properties, getters and setters.
/// </summary>

/// <summary>
/// Get whether the histogram is locked during accumulation.
/// This is to prevent two threads from writing to the same histogram
/// bucket at once.
/// The current implementation matches flam3 and is very innefficient
/// to the point of negating any gains gotten from multi-threading.
/// Future workarounds may be tried in the future.
/// Default: false.
/// </summary>
/// <returns>True if the histogram is locked during accumulation, else false.</returns>
bool RendererBase::LockAccum() const { return m_LockAccum; }

/// <summary>
/// Set whether the histogram is locked during accumulation.
/// This is to prevent two threads from writing to the same histogram
/// bucket at once.
/// The current implementation matches flam3 and is very innefficient
/// to the point of negating any gains gotten from multi-threading.
/// Different workarounds may be tried in the future.
/// Reset the rendering process.
/// </summary>
/// <param name="lockAccum">True if the histogram should be locked when accumulating, else false</param>
void RendererBase::LockAccum(bool lockAccum)
{
	ChangeVal([&] { m_LockAccum = lockAccum; }, eProcessAction::FULL_RENDER);
}

/// <summary>
/// Get whether color clipping and gamma correction is done before
/// or after spatial filtering.
/// Default: false.
/// </summary>
/// <returns>True if early clip, else false.</returns>
bool RendererBase::EarlyClip() const { return m_EarlyClip; }

/// <summary>
/// Set whether color clipping and gamma correction is done before
/// or after spatial filtering.
/// Set the render state to FILTER_AND_ACCUM.
/// </summary>
/// <param name="earlyClip">True if early clip, else false.</param>
void RendererBase::EarlyClip(bool earlyClip)
{
	ChangeVal([&] { m_EarlyClip = earlyClip; }, eProcessAction::FILTER_AND_ACCUM);
}

/// <summary>
/// Get whether the positive Y coordinate of the final output image is up.
/// Default: false.
/// </summary>
/// <returns>True if up, else false.</returns>
bool RendererBase::YAxisUp() const { return m_YAxisUp; }

/// <summary>
/// Set whether the positive Y axis of the final output image is up.
/// </summary>
/// <param name="yup">True if the positive y axis is up, else false.</param>
void RendererBase::YAxisUp(bool yup)
{
	ChangeVal([&] { m_YAxisUp = yup; }, eProcessAction::ACCUM_ONLY);
}

/// <summary>
/// Get whether to insert the palette as a block of colors in the final output image.
/// This is useful for debugging palette issues.
/// Default: 1.
/// </summary>
/// <returns>True if inserting the palette, else false.</returns>
bool RendererBase::InsertPalette() const { return m_InsertPalette; }

/// <summary>
/// Set whether to insert the palette as a block of colors in the final output image.
/// This is useful for debugging palette issues.
/// Set the render state to ACCUM_ONLY.
/// </summary>
/// <param name="insertPalette">True if inserting the palette, else false.</param>
void RendererBase::InsertPalette(bool insertPalette)
{
	ChangeVal([&] { m_InsertPalette = insertPalette; }, eProcessAction::ACCUM_ONLY);
}

/// <summary>
/// Get whether to reclaim unused memory in the final output buffer
/// when a smaller size is requested than has been previously allocated.
/// Default: false.
/// </summary>
/// <returns>True if reclaim, else false.</returns>
bool RendererBase::ReclaimOnResize() const { return m_ReclaimOnResize; }

/// <summary>
/// Set whether to reclaim unused memory in the final output buffer
/// when a smaller size is requested than has been previously allocated.
/// Reset the rendering process.
/// </summary>
/// <param name="reclaimOnResize">True if reclaim, else false.</param>
void RendererBase::ReclaimOnResize(bool reclaimOnResize)
{
	ChangeVal([&] { m_ReclaimOnResize = reclaimOnResize; }, eProcessAction::FULL_RENDER);
}

/// <summary>
/// Set the callback object.
/// </summary>
/// <param name="callback">The callback object to set</param>
void RendererBase::Callback(RenderCallback* callback)
{
	m_Callback = callback;
}

/// <summary>
/// Set the number of threads to use when rendering.
/// This will also reset the vector of random contexts to be the same size
/// as the number of specified threads.
/// Since this is where they get set up, the caller can optionally pass in
/// a seed string, however it's only used if threads is 1.
/// This is useful for debugging since it will run the same point trajectory
/// every time.
/// Reset the rendering process.
/// </summary>
/// <param name="threads">The number of threads to use</param>
/// <param name="seedString">The seed string to use if threads is 1. Default: nullptr.</param>
void RendererBase::ThreadCount(size_t threads, const char* seedString)
{
	ChangeVal([&]
	{
		Timing t;
		size_t i, size;
		const size_t isaacSize = 1 << ISAAC_SIZE;
		ISAAC_INT seeds[isaacSize];
		m_ThreadsToUse = threads > 0 ? threads : 1;
		m_Rand.clear();
		m_SubBatch.clear();
		m_SubBatch.resize(m_ThreadsToUse);
		m_BadVals.resize(m_ThreadsToUse);

		if (seedString)
		{
			memset(seeds, 0, isaacSize * sizeof(ISAAC_INT));
			memcpy(reinterpret_cast<char*>(seeds), seedString, std::min(strlen(seedString), isaacSize * sizeof(ISAAC_INT)));
		}

		//This is critical for multithreading, otherwise the threads all happen
		//too close to each other in time, resulting in bad randomization.
		while (m_Rand.size() < m_ThreadsToUse)
		{
			size = m_Rand.size();

			if (seedString)
			{
				ISAAC_INT newSize = ISAAC_INT(size + 5 + (t.Toc() + t.EndTime()));
#ifdef ISAAC_FLAM3_DEBUG
				QTIsaac<ISAAC_SIZE, ISAAC_INT> isaac(0, 0, 0, seeds);
#else
				QTIsaac<ISAAC_SIZE, ISAAC_INT> isaac(newSize, newSize * 2, newSize * 3, seeds);
#endif
				m_Rand.push_back(isaac);

				for (i = 0; i < (isaacSize * sizeof(ISAAC_INT)); i++)
					reinterpret_cast<byte*>(seeds)[i]++;
			}
			else
			{
				for (i = 0; i < isaacSize; i++)
				{
					t.Toc();
					seeds[i] = ISAAC_INT((t.EndTime() * i) + (size + 1));
				}

				t.Toc();
				ISAAC_INT r = ISAAC_INT((size * i) + i + t.EndTime());
				QTIsaac<ISAAC_SIZE, ISAAC_INT> isaac(r, r * 2, r * 3, seeds);
				m_Rand.push_back(isaac);
			}
		}
	}, eProcessAction::FULL_RENDER);
}

/// <summary>
/// Get the bytes per channel of the output image.
/// This will always be 4 since each channel is a 32-bit float.
/// </summary>
/// <returns>The number of bytes per channel</returns>
size_t RendererBase::BytesPerChannel() const { return m_BytesPerChannel; }

/// <summary>
/// Get the number of channels per pixel in the output image.
/// This will always be 4 since each pixel is always RGBA.
/// </summary>
/// <returns>The number of channels per pixel in the output image</returns>
size_t RendererBase::NumChannels() const { return m_NumChannels; }

/// <summary>
/// Get/set the priority used for the CPU rendering threads.
/// This does not affect OpenCL rendering.
/// </summary>
/// <param name="priority">The priority to use for the CPU rendering threads</param>
eThreadPriority RendererBase::Priority() const { return m_Priority; }
void RendererBase::Priority(eThreadPriority priority) { m_Priority = priority; }

/// <summary>
/// Get the type of filter to use for preview renders during interactive rendering.
/// Using basic log scaling is quicker, but doesn't provide any bluring.
/// Full DE is much slower, but provides a more realistic preview of what the final image
/// will look like.
/// Default: FILTER_LOG.
/// </summary>
/// <returns>The type of filter to use</returns>
eInteractiveFilter RendererBase::InteractiveFilter() const { return m_InteractiveFilter; }

/// <summary>
/// Set the type of filter to use for preview renders during interactive rendering.
/// Using basic log scaling is quicker, but doesn't provide any bluring.
/// Full DE is much slower, but provides a more realistic preview of what the final image
/// will look like.
/// Reset the rendering process.
/// </summary>
/// <param name="filter">The filter.</param>
void RendererBase::InteractiveFilter(eInteractiveFilter filter)
{
	ChangeVal([&] { m_InteractiveFilter = filter; }, eProcessAction::FULL_RENDER);
}

/// <summary>
/// Virtual render properties, getters and setters.
/// </summary>

/// <summary>
/// Get the number of threads used when rendering.
/// Default: use all avaliable cores.
/// </summary>
/// <returns>The number of threads used when rendering</returns>
size_t RendererBase::ThreadCount() const { return m_ThreadsToUse; }

/// <summary>
/// Get the renderer type enum.
/// eRendererType::CPU_RENDERER for this class, other values for derived classes.
/// </summary>
/// <returns>eRendererType::CPU_RENDERER</returns>
eRendererType RendererBase::RendererType() const { return eRendererType::CPU_RENDERER; }

/// <summary>
/// Get whether the renderer uses a shared texture with OpenGL.
/// This only applies to the OpenCL renderer (which can be shared or unshared), so it's always false in the base.
/// </summary>
/// <returns>True if shared, else false. Always false in the base.</returns>
bool RendererBase::Shared() const { return false; }

/// <summary>
/// //Non-virtual threading control.
/// </summary>

/// <summary>
/// Stop rendering, ensure all locks are exited and reset the rendering state.
/// </summary>
void RendererBase::Reset()
{
	Abort();
	EnterRender();
	EnterFinalAccum();
	LeaveFinalAccum();
	LeaveRender();
	m_ProcessState = eProcessState::NONE;
	m_ProcessAction = eProcessAction::FULL_RENDER;
}

void RendererBase::EnterRender() { m_RenderingCs.lock(); }
void RendererBase::LeaveRender() { m_RenderingCs.unlock(); }

void RendererBase::EnterFinalAccum() { m_FinalAccumCs.lock(); m_InFinalAccum = true; }
void RendererBase::LeaveFinalAccum() { m_FinalAccumCs.unlock(); m_InFinalAccum = false; }

void RendererBase::EnterResize() { m_ResizeCs.lock(); }
void RendererBase::LeaveResize() { m_ResizeCs.unlock(); }

void RendererBase::Abort() { m_Abort = true; Pause(false); }
bool RendererBase::Aborted() { return m_Abort; }

void RendererBase::Pause(bool pause) { m_Pause = pause; }
bool RendererBase::Paused() { return m_Pause; }

bool RendererBase::InRender()	  { return m_InRender; }
bool RendererBase::InFinalAccum() { return m_InFinalAccum; }

}
