// AreaPocket.cpp
// Copyright 2011, Dan Heeks
// This program is released under the BSD license. See the file COPYING for details.

// implements CArea::MakeOnePocketCurve

#include "Area.h"

#include <map>
#include <set>

static const CAreaPocketParams* pocket_params = NULL;

class IslandAndOffset
{
public:
	const CCurve* island;
	CArea offset;
	std::list<CCurve> island_inners;
	std::list<IslandAndOffset*> touching_offsets;

	IslandAndOffset(const CCurve* Island)
	{
		island = Island;

		offset.m_curves.push_back(*island);
		offset.m_curves.back().Reverse();

		offset.Offset(-pocket_params->stepover);


		if(offset.m_curves.size() > 1)
		{
			for(std::list<CCurve>::iterator It = offset.m_curves.begin(); It != offset.m_curves.end(); It++)
			{
				if(It == offset.m_curves.begin())continue;
				island_inners.push_back(*It);
				island_inners.back().Reverse();
			}
			offset.m_curves.resize(1);
		}
	}
};

class CurveTree
{
	static std::list<CurveTree*> to_do_list_for_MakeOffsets;
	void MakeOffsets2();
	static std::list<CurveTree*> islands_added;

public:
	Point point_on_parent;
	CCurve curve;
	std::list<CurveTree*> inners;
	std::list<const IslandAndOffset*> offset_islands;
	CurveTree(const CCurve &c)
	{
		curve = c;
	}
	~CurveTree(){}

	void MakeOffsets();
};
std::list<CurveTree*> CurveTree::islands_added;

class GetCurveItem
{
public:
	CurveTree* curve_tree;
	std::list<CVertex>::iterator EndIt;
	static std::list<GetCurveItem> to_do_list;

	GetCurveItem(CurveTree* ct, std::list<CVertex>::iterator EIt):curve_tree(ct), EndIt(EIt){}

	void GetCurve(CCurve& output);
	CVertex& back(){std::list<CVertex>::iterator It = EndIt; It--; return *It;}
};

std::list<GetCurveItem> GetCurveItem::to_do_list;
std::list<CurveTree*> CurveTree::to_do_list_for_MakeOffsets;

void GetCurveItem::GetCurve(CCurve& output)
{
	// walk around the curve adding spans to output until we get to an inner's point_on_parent
	// then add a line from the inner's point_on_parent to inner's start point, then GetCurve from inner

	// add start point
	if(CArea::m_please_abort)return;
	output.m_vertices.insert(this->EndIt, CVertex(curve_tree->curve.m_vertices.front()));

	std::list<CurveTree*> inners_to_visit;
	for(std::list<CurveTree*>::iterator It2 = curve_tree->inners.begin(); It2 != curve_tree->inners.end(); It2++)
	{
		inners_to_visit.push_back(*It2);
	}

	const CVertex* prev_vertex = NULL;

	for(std::list<CVertex>::iterator It = curve_tree->curve.m_vertices.begin(); It != curve_tree->curve.m_vertices.end(); It++)
	{
		const CVertex& vertex = *It;
		if(prev_vertex)
		{
			Span span(prev_vertex->m_p, vertex);

			// order inners on this span
			std::multimap<double, CurveTree*> ordered_inners;
			for(std::list<CurveTree*>::iterator It2 = inners_to_visit.begin(); It2 != inners_to_visit.end();)
			{
				CurveTree *inner = *It2;
				double t;
				if(span.On(inner->point_on_parent, &t))
				{
					ordered_inners.insert(std::make_pair(t, inner));
					It2 = inners_to_visit.erase(It2);
				}
				else
				{
					It2++;
				}
				if(CArea::m_please_abort)return;
			}

			if(CArea::m_please_abort)return;
			for(std::multimap<double, CurveTree*>::iterator It2 = ordered_inners.begin(); It2 != ordered_inners.end(); It2++)
			{
				CurveTree& inner = *(It2->second);
				if(inner.point_on_parent.dist(back().m_p) > 0.01/CArea::m_units)
				{
					output.m_vertices.insert(this->EndIt, CVertex(vertex.m_type, inner.point_on_parent, vertex.m_c));
				}
				if(CArea::m_please_abort)return;

				// vertex add after GetCurve
				std::list<CVertex>::iterator VIt = output.m_vertices.insert(this->EndIt, CVertex(inner.point_on_parent));

				//inner.GetCurve(output);
				GetCurveItem::to_do_list.push_back(GetCurveItem(&inner, VIt));
			}

			if(back().m_p != vertex.m_p)output.m_vertices.insert(this->EndIt, vertex);
		}
		prev_vertex = &vertex;
	}

	if(CArea::m_please_abort)return;
	for(std::list<CurveTree*>::iterator It2 = inners_to_visit.begin(); It2 != inners_to_visit.end(); It2++)
	{
		CurveTree &inner = *(*It2);
		if(inner.point_on_parent != back().m_p)
		{
			output.m_vertices.insert(this->EndIt, CVertex(inner.point_on_parent));
		}
		if(CArea::m_please_abort)return;

		// vertex add after GetCurve
		std::list<CVertex>::iterator VIt = output.m_vertices.insert(this->EndIt, CVertex(inner.point_on_parent));

		//inner.GetCurve(output);
		GetCurveItem::to_do_list.push_back(GetCurveItem(&inner, VIt));

	}
}

class IslandAndOffsetLink
{
public:
	const IslandAndOffset* island_and_offset;
	CurveTree* add_to;
	IslandAndOffsetLink(const IslandAndOffset* i, CurveTree* a){island_and_offset = i; add_to = a;}
};

static Point GetNearestPoint(CurveTree* curve_tree, std::list<CurveTree*> &islands_added, const CCurve &test_curve, CurveTree** best_curve_tree)
{
	// find nearest point to test_curve, from curve and all the islands in 
	double best_dist;
	Point best_point = curve_tree->curve.NearestPoint(test_curve, &best_dist);
	*best_curve_tree = curve_tree;
	for(std::list<CurveTree*>::iterator It = islands_added.begin(); It != islands_added.end(); It++)
	{
		CurveTree* island = *It;
		double dist;
		Point p = island->curve.NearestPoint(test_curve, &dist);
		if(dist < best_dist)
		{
			*best_curve_tree = island;
			best_point = p;
			best_dist = dist;
		}
	}

	return best_point;
}

void CurveTree::MakeOffsets2()
{
	// make offsets

	if(CArea::m_please_abort)return;
	CArea smaller;
	smaller.m_curves.push_back(curve);
	smaller.Offset(pocket_params->stepover);

	if(CArea::m_please_abort)return;

	// test islands
	for(std::list<const IslandAndOffset*>::iterator It = offset_islands.begin(); It != offset_islands.end();)
	{
		const IslandAndOffset* island_and_offset = *It;

		if(GetOverlapType(island_and_offset->offset, smaller) == eInside)
			It++; // island is still inside
		else
		{
			inners.push_back(new CurveTree(*island_and_offset->island));
			islands_added.push_back(inners.back());
			inners.back()->point_on_parent = curve.NearestPoint(*island_and_offset->island);
			if(CArea::m_please_abort)return;
			Point island_point = island_and_offset->island->NearestPoint(inners.back()->point_on_parent);
			if(CArea::m_please_abort)return;
			inners.back()->curve.ChangeStart(island_point);
			if(CArea::m_please_abort)return;

			// add the island offset's inner curves
			for(std::list<CCurve>::const_iterator It2 = island_and_offset->island_inners.begin(); It2 != island_and_offset->island_inners.end(); It2++)
			{
				const CCurve& island_inner = *It2;
				inners.back()->inners.push_back(new CurveTree(island_inner));
				inners.back()->inners.back()->point_on_parent = inners.back()->curve.NearestPoint(island_inner);
				if(CArea::m_please_abort)return;
				Point island_point = island_inner.NearestPoint(inners.back()->inners.back()->point_on_parent);
				if(CArea::m_please_abort)return;
				inners.back()->inners.back()->curve.ChangeStart(island_point);
				to_do_list_for_MakeOffsets.push_back(inners.back()->inners.back()); // do it later, in a while loop
				if(CArea::m_please_abort)return;
			}

			smaller.Subtract(island_and_offset->offset);

			std::set<const IslandAndOffset*> added;

			std::list<IslandAndOffsetLink> touching_list;
			for(std::list<IslandAndOffset*>::const_iterator It2 = island_and_offset->touching_offsets.begin(); It2 != island_and_offset->touching_offsets.end(); It2++)
			{
				const IslandAndOffset* touching = *It2;
				touching_list.push_back(IslandAndOffsetLink(touching, inners.back()));
				added.insert(touching);
			}

			while(touching_list.size() > 0)
			{
				IslandAndOffsetLink touching = touching_list.front();
				touching_list.pop_front();
				touching.add_to->inners.push_back(new CurveTree(*touching.island_and_offset->island));
				islands_added.push_back(touching.add_to->inners.back());
				touching.add_to->inners.back()->point_on_parent = touching.add_to->curve.NearestPoint(*touching.island_and_offset->island);
				Point island_point = touching.island_and_offset->island->NearestPoint(touching.add_to->inners.back()->point_on_parent);
				touching.add_to->inners.back()->curve.ChangeStart(island_point);
				smaller.Subtract(touching.island_and_offset->offset);

				// add the island offset's inner curves
				for(std::list<CCurve>::const_iterator It2 = touching.island_and_offset->island_inners.begin(); It2 != touching.island_and_offset->island_inners.end(); It2++)
				{
					const CCurve& island_inner = *It2;
					touching.add_to->inners.back()->inners.push_back(new CurveTree(island_inner));
					touching.add_to->inners.back()->inners.back()->point_on_parent = touching.add_to->inners.back()->curve.NearestPoint(island_inner);
					if(CArea::m_please_abort)return;
					Point island_point = island_inner.NearestPoint(touching.add_to->inners.back()->inners.back()->point_on_parent);
					if(CArea::m_please_abort)return;
					touching.add_to->inners.back()->inners.back()->curve.ChangeStart(island_point);
					to_do_list_for_MakeOffsets.push_back(touching.add_to->inners.back()->inners.back()); // do it later, in a while loop
					if(CArea::m_please_abort)return;
				}

				for(std::list<IslandAndOffset*>::const_iterator It2 = touching.island_and_offset->touching_offsets.begin(); It2 != touching.island_and_offset->touching_offsets.end(); It2++)
				{
					if(added.find(*It2)==added.end() && ((*It2) != island_and_offset))
					{
						touching_list.push_back(IslandAndOffsetLink(*It2, touching.add_to->inners.back()));
						added.insert(*It2);
					}
				}
			}

			if(CArea::m_please_abort)return;
			It = offset_islands.erase(It);

			for(std::set<const IslandAndOffset*>::iterator It2 = added.begin(); It2 != added.end(); It2++)
			{
				const IslandAndOffset* i = *It2;
				offset_islands.remove(i);
			}

			if(offset_islands.size() == 0)break;
			It = offset_islands.begin();
		}
	}

	CArea::m_processing_done += CArea::m_MakeOffsets_increment;
	if(CArea::m_processing_done > CArea::m_after_MakeOffsets_length)CArea::m_processing_done = CArea::m_after_MakeOffsets_length;

	std::list<CArea> separate_areas;
	smaller.Split(separate_areas);
	if(CArea::m_please_abort)return;
	for(std::list<CArea>::iterator It = separate_areas.begin(); It != separate_areas.end(); It++)
	{
		CArea& separate_area = *It;
		CCurve& first_curve = separate_area.m_curves.front();

		CurveTree* nearest_curve_tree = NULL;
		Point near_point = GetNearestPoint(this, islands_added, first_curve, &nearest_curve_tree);

		nearest_curve_tree->inners.push_back(new CurveTree(first_curve));

		for(std::list<const IslandAndOffset*>::iterator It = offset_islands.begin(); It != offset_islands.end();It++)
		{
			const IslandAndOffset* island_and_offset = *It;
			if(GetOverlapType(island_and_offset->offset, separate_area) == eInside)
				nearest_curve_tree->inners.back()->offset_islands.push_back(island_and_offset);
			if(CArea::m_please_abort)return;
		}

		nearest_curve_tree->inners.back()->point_on_parent = near_point;

		if(CArea::m_please_abort)return;
		Point first_curve_point = first_curve.NearestPoint(nearest_curve_tree->inners.back()->point_on_parent);
		if(CArea::m_please_abort)return;
		nearest_curve_tree->inners.back()->curve.ChangeStart(first_curve_point);
		if(CArea::m_please_abort)return;
		to_do_list_for_MakeOffsets.push_back(nearest_curve_tree->inners.back()); // do it later, in a while loop
		if(CArea::m_please_abort)return;
	}
}

void CurveTree::MakeOffsets()
{
	to_do_list_for_MakeOffsets.push_back(this);
	islands_added.clear();

	while(to_do_list_for_MakeOffsets.size() > 0)
	{
		CurveTree* curve_tree = to_do_list_for_MakeOffsets.front();
		to_do_list_for_MakeOffsets.pop_front();
		curve_tree->MakeOffsets2();
	}
}

void recur(std::list<CArea> &arealist, const CArea& a1, const CAreaPocketParams &params, int level)
{
	//if(level > 3)return;

    // this makes arealist by recursively offsetting a1 inwards
    
	if(a1.m_curves.size() == 0)
		return;
    
	if(params.from_center)
		arealist.push_front(a1);
	else
		arealist.push_back(a1);

    CArea a_offset = a1;
    a_offset.Offset(params.stepover);
    
    // split curves into new areas
	if(CArea::HolesLinked())
	{
		for(std::list<CCurve>::iterator It = a_offset.m_curves.begin(); It != a_offset.m_curves.end(); It++)
		{
            CArea a2;
			a2.m_curves.push_back(*It);
            recur(arealist, a2, params, level + 1);
		}
	}
    else
	{
        // split curves into new areas
		a_offset.Reorder();
        CArea* a2 = NULL;
       
		for(std::list<CCurve>::iterator It = a_offset.m_curves.begin(); It != a_offset.m_curves.end(); It++)
		{
			CCurve& curve = *It;
			if(curve.IsClockwise())
			{
				if(a2 != NULL)
					a2->m_curves.push_back(curve);
			}
			else
			{
				if(a2 != NULL)
					recur(arealist, *a2, params, level + 1);
				else
					a2 = new CArea();
                a2->m_curves.push_back(curve);
			}
		}

		if(a2 != NULL)
			recur(arealist, *a2, params, level + 1);
	}
}

void MarkOverlappingOffsetIslands(std::list<IslandAndOffset> &offset_islands)
{
	for(std::list<IslandAndOffset>::iterator It1 = offset_islands.begin(); It1 != offset_islands.end(); It1++)
	{
		std::list<IslandAndOffset>::iterator It2 = It1;
		It2++;
		for(;It2 != offset_islands.end(); It2++)
		{
			IslandAndOffset &o1 = *It1;
			IslandAndOffset &o2 = *It2;

			if(GetOverlapType(o1.offset, o2.offset) == eCrossing)
			{
				o1.touching_offsets.push_back(&o2);
				o2.touching_offsets.push_back(&o1);
			}
		}
	}
}

void CArea::MakeOnePocketCurve(std::list<CCurve> &curve_list, const CAreaPocketParams &params)const
{
	if(CArea::m_please_abort)return;
#if 0  // simple offsets with feed or rapid joins
	CArea area_for_feed_possible = *this;

	area_for_feed_possible.Offset(-params.tool_radius - 0.01);
	CArea a_offset = *this;

	std::list<CArea> arealist;
	recur(arealist, a_offset, params, 0);

	bool first = true;

	for(std::list<CArea>::iterator It = arealist.begin(); It != arealist.end(); It++)
	{
		CArea& area = *It;
		for(std::list<CCurve>::iterator It = area.m_curves.begin(); It != area.m_curves.end(); It++)
		{
			CCurve& curve = *It;
			if(!first)
			{
				// try to join these curves with a feed move, if possible and not too long
				CCurve &prev_curve = curve_list.back();
				const Point &prev_p = prev_curve.m_vertices.back().m_p;
				const Point &next_p = curve.m_vertices.front().m_p;

				if(feed_possible(area_for_feed_possible, prev_p, next_p, params.tool_radius))
				{
					// join curves
					prev_curve += curve;
				}
				else
				{
					curve_list.push_back(curve);
				}
			}
			else
			{
				curve_list.push_back(curve);
			}
			first = false;
		}
	}
#else
	pocket_params = &params;
	if(m_curves.size() == 0)
	{
		CArea::m_processing_done += CArea::m_single_area_processing_length;
		return;
	}
	CurveTree top_level(m_curves.front());

	std::list<IslandAndOffset> offset_islands;

	for(std::list<CCurve>::const_iterator It = m_curves.begin(); It != m_curves.end(); It++)
	{
		const CCurve& c = *It;
		if(It != m_curves.begin())
		{
			IslandAndOffset island_and_offset(&c);
			offset_islands.push_back(island_and_offset);
			top_level.offset_islands.push_back(&(offset_islands.back()));
			if(m_please_abort)return;
		}
	}

	MarkOverlappingOffsetIslands(offset_islands);

	CArea::m_processing_done += CArea::m_single_area_processing_length * 0.1;

	double MakeOffsets_processing_length = CArea::m_single_area_processing_length * 0.8;
	CArea::m_after_MakeOffsets_length = CArea::m_processing_done + MakeOffsets_processing_length;
	double guess_num_offsets = sqrt(GetArea(true)) * 0.5 / params.stepover;
	CArea::m_MakeOffsets_increment = MakeOffsets_processing_length / guess_num_offsets;

	top_level.MakeOffsets();
	if(CArea::m_please_abort)return;
	CArea::m_processing_done = CArea::m_after_MakeOffsets_length;

	curve_list.push_back(CCurve());
	CCurve& output = curve_list.back();

	GetCurveItem::to_do_list.push_back(GetCurveItem(&top_level, output.m_vertices.end()));

	while(GetCurveItem::to_do_list.size() > 0)
	{
		GetCurveItem item = GetCurveItem::to_do_list.front();
		item.GetCurve(output);
		GetCurveItem::to_do_list.pop_front();
	}

	// delete curve_trees non-recursively
	std::list<CurveTree*> CurveTreeDestructList;
	for(std::list<CurveTree*>::iterator It = top_level.inners.begin(); It != top_level.inners.end(); It++)
	{
		CurveTreeDestructList.push_back(*It);
	}
	while(CurveTreeDestructList.size() > 0)
	{
		CurveTree* curve_tree = CurveTreeDestructList.front();
		CurveTreeDestructList.pop_front();
		for(std::list<CurveTree*>::iterator It = curve_tree->inners.begin(); It != curve_tree->inners.end(); It++)
		{
			CurveTreeDestructList.push_back(*It);
		}
		delete curve_tree;
	}

	CArea::m_processing_done += CArea::m_single_area_processing_length * 0.1;
#endif
}

