#pragma once

#include <oneapi/tbb/scalable_allocator.h>
#include <oneapi/tbb/spin_mutex.h>
#include "../PrintConfig.hpp"
#include "../Slicing.hpp"
#include "../Fill/FillBase.hpp"
#include "../ClipperUtils.hpp"
#include "../Polygon.hpp"

namespace Slic3r {

	class PrintObject;
	class PrintConfig;
	class PrintObjectConfig;

	// Support layer type to be used by MyLayer. This type carries a much more detailed information
	// about the support layer type than the final support layers stored in a PrintObject.
	enum SupporLayerType {
		sltUnknown = 0,
		// Ratft base layer, to be printed with the support material.
		sltRaftBase,
		// Raft interface layer, to be printed with the support interface material. 
		sltRaftInterface,
		// Bottom contact layer placed over a top surface of an object. To be printed with a support interface material.
		sltBottomContact,
		// Dense interface layer, to be printed with the support interface material.
		// This layer is separated from an object by an sltBottomContact layer.
		sltBottomInterface,
		// Sparse base support layer, to be printed with a support material.
		sltBase,
		// Dense interface layer, to be printed with the support interface material.
		// This layer is separated from an object with sltTopContact layer.
		sltTopInterface,
		// Top contact layer directly supporting an overhang. To be printed with a support interface material.
		sltTopContact,
		// Some undecided type yet. It will turn into sltBase first, then it may turn into sltBottomInterface or sltTopInterface.
		sltIntermediate,
	};

	// A support layer type used internally by the SupportMaterial class. This class carries a much more detailed
	// information about the support layer than the layers stored in the PrintObject, mainly
	// the SupportGeneratorLayer is aware of the bridging flow and the interface gaps between the object and the support.
	// This is from the old "MyLayer".
	class SupportGeneratorLayer
	{
	public:
		void reset() {
			*this = SupportGeneratorLayer();
		}

		bool operator==(const SupportGeneratorLayer& layer2) const {
			return print_z == layer2.print_z && height == layer2.height && bridging == layer2.bridging;
		}

		// Order the layers by lexicographically by an increasing print_z and a decreasing layer height.
		bool operator<(const SupportGeneratorLayer& layer2) const {
			if (print_z < layer2.print_z) {
				return true;
			}
			else if (print_z == layer2.print_z) {
				if (height > layer2.height)
					return true;
				else if (height == layer2.height) {
					// Bridging layers first.
					return bridging && !layer2.bridging;
				}
				else
					return false;
			}
			else
				return false;
		}

		void merge(SupportGeneratorLayer&& rhs) {
			// The union_() does not support move semantic yet, but maybe one day it will.
			this->polygons = union_(this->polygons, std::move(rhs.polygons));
			auto merge = [](std::unique_ptr<Polygons>& dst, std::unique_ptr<Polygons>& src) {
				if (!dst || dst->empty())
					dst = std::move(src);
				else if (src && !src->empty())
					*dst = union_(*dst, std::move(*src));
				};
			merge(this->contact_polygons, rhs.contact_polygons);
			merge(this->overhang_polygons, rhs.overhang_polygons);
			merge(this->enforcer_polygons, rhs.enforcer_polygons);
			rhs.reset();
		}

		// For the bridging flow, bottom_print_z will be above bottom_z to account for the vertical separation.
		// For the non-bridging flow, bottom_print_z will be equal to bottom_z.
		coordf_t bottom_print_z() const { return print_z - height; }

		// To sort the extremes of top / bottom interface layers.
		coordf_t extreme_z() const { return (this->layer_type == SupporLayerType::sltTopContact) ? this->bottom_z : this->print_z; }

		SupporLayerType layer_type{ SupporLayerType::sltUnknown };
		// Z used for printing, in unscaled coordinates.
		coordf_t print_z{ 0 };
		// Bottom Z of this layer. For soluble layers, bottom_z + height = print_z,
		// otherwise bottom_z + gap + height = print_z.
		coordf_t bottom_z{ 0 };
		// Layer height in unscaled coordinates.
		coordf_t height{ 0 };
		// Index of a PrintObject layer_id supported by this layer. This will be set for top contact layers.
		// If this is not a contact layer, it will be set to size_t(-1).
		size_t 	 idx_object_layer_above{ size_t(-1) };
		// Index of a PrintObject layer_id, which supports this layer. This will be set for bottom contact layers.
		// If this is not a contact layer, it will be set to size_t(-1).
		size_t 	 idx_object_layer_below{ size_t(-1) };
		// Use a bridging flow when printing this support layer.
		bool 	 bridging{ false };

		// Polygons to be filled by the support pattern.
		Polygons polygons;
		// Currently for the contact layers only.
		std::unique_ptr<Polygons> contact_polygons;
		std::unique_ptr<Polygons> overhang_polygons;
		// Enforcers need to be propagated independently in case the "support on build plate only" option is enabled.
		std::unique_ptr<Polygons> enforcer_polygons;
	};

// Layers are allocated and owned by a deque. Once a layer is allocated, it is maintained
// up to the end of a generate() method. The layer storage may be replaced by an allocator class in the future, 
// which would allocate layers by multiple chunks.
class SupportGeneratorLayerStorage {
public:
	SupportGeneratorLayer& allocate_unguarded(SupporLayerType layer_type) { 
		m_storage.emplace_back();
		m_storage.back().layer_type = layer_type;
	    return m_storage.back();
	}

	SupportGeneratorLayer& allocate(SupporLayerType layer_type)
	{ 
		m_mutex.lock();
		m_storage.emplace_back();
	    SupportGeneratorLayer *layer_new = &m_storage.back();
		m_mutex.unlock();
	    layer_new->layer_type = layer_type;
	    return *layer_new;
	}

private:
	template<typename BaseType>
	using Allocator = tbb::scalable_allocator<BaseType>;
	Slic3r::deque<SupportGeneratorLayer, Allocator<SupportGeneratorLayer>> 		m_storage;
	tbb::spin_mutex                         									m_mutex;
};
	using SupportGeneratorLayersPtr = std::vector<SupportGeneratorLayer*>;
} // namespace Slic3r