/* -----------------------------------------------------------------------------
	GSFramework
	Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
----------------------------------------------------------------------------- */


		#include <cmath>
		#include "scene3d/scene_debug.h"
		#include "scene3d/scene.h"
		#include "scene3d/mobject.h"
		#include "scene3d/mlight.h"
		#include "scene3d/mcamera.h"
		#include "scene3d/mtrigger.h"
		#include "scene3d/mconstraint.h"
		#include "scene3d/instance.h"
		#include "scene3d/group.h"
		#include "physic/physic_constraint.h"
		#include "core/renderer_toolbox.h"
		#include "metafile/nml_object.h"
		#include "core/path_kdtree.h"

		using namespace GS;
		using namespace GS::Core;
		using namespace GS::S3D;
		using namespace GS::Units;


//-----------------------------------------------------------------------------
void				SceneDebugger::DrawItemBoundingVolume(Renderer &render, MItem *i, uint display_flags)
{
	if	(i && (i->GetItemType() == Type_Object))
	{
		if	(display_flags & SceneDebugDisplayBoundingVolumes)
		{
			MObject *mobj = (MObject *)i;

			MinMax pinmax;
			mobj->ComputeLocalMinMax(pinmax);
			OBB obb = OBB::FromMinMax(pinmax);
			obb.Transform(i->GetBaseItem()->GetMatrix());

			Color color(1, 1, 0, 0.35f);
			RendererToolbox::DrawOBB(render, obb, &color, Material::Blend_Alpha);
		}

		if	(display_flags & SceneDebugDisplayOctreeVolumes)
		{
			/// TODO
		}
	}
}
void				SceneDebugger::DrawItem(Renderer &render, MItem *pitem, uint display_flags, Color *draw_color)
{
	if	(!pitem)
		return;

	Color item_color = draw_color ? *draw_color : Color(0.75f, 0.75f, 0.75f);
	Color bone_color = item_color;
	bone_color.w = 0.5f;

	render.SetWorldMatrix(Matrix4::IdentityMatrix(), &Matrix4::IdentityMatrix());

	if	(pitem->GetBaseItem()->item_flags.IsSet(ItemFlagBoneHint) && pitem->GetBaseItem()->GetParent())
	{
		if	(display_flags & SceneDebugDisplayBones)
		{
			Item *link = pitem->GetBaseItem()->GetParent();
			Vector4 w = pitem->GetBaseItem()->GetMatrix().GetRow(3) - link->GetMatrix().GetRow(3);
			float k = w.Len();
			Vector4 row = link->GetMatrix().GetRow(1);
			Matrix4 m = Matrix4::FromMatrix3(Matrix3::FromOrthonormalBasis(w, &row));
			m.SetRow(3, link->GetMatrix().GetRow(3));

			Vector4 p[6] =
			{
				Vector4(0, 0, 0),
				Vector4(-k * 0.1f,  k * 0.1f, k * 0.2f),
				Vector4( k * 0.1f,  k * 0.1f, k * 0.2f),
				Vector4( k * 0.1f, -k * 0.1f, k * 0.2f),
				Vector4(-k * 0.1f, -k * 0.1f, k * 0.2f),
				Vector4( 0, 0, k)
			}, wp[6];

			m.Apply(wp, p, 6);

			RendererToolbox::Line3D(render, wp[0], wp[1], &bone_color, Material::Blend_Alpha, Material::RenderWord(Material::Render_NoZTest | Material::Render_NoZWrite));
			RendererToolbox::Line3D(render, wp[0], wp[2], &bone_color, Material::Blend_Alpha, Material::RenderWord(Material::Render_NoZTest | Material::Render_NoZWrite));
			RendererToolbox::Line3D(render, wp[0], wp[3], &bone_color, Material::Blend_Alpha, Material::RenderWord(Material::Render_NoZTest | Material::Render_NoZWrite));
			RendererToolbox::Line3D(render, wp[0], wp[4], &bone_color, Material::Blend_Alpha, Material::RenderWord(Material::Render_NoZTest | Material::Render_NoZWrite));

			RendererToolbox::Line3D(render, wp[1], wp[5], &bone_color, Material::Blend_Alpha, Material::RenderWord(Material::Render_NoZTest | Material::Render_NoZWrite));
			RendererToolbox::Line3D(render, wp[2], wp[5], &bone_color, Material::Blend_Alpha, Material::RenderWord(Material::Render_NoZTest | Material::Render_NoZWrite));
			RendererToolbox::Line3D(render, wp[3], wp[5], &bone_color, Material::Blend_Alpha, Material::RenderWord(Material::Render_NoZTest | Material::Render_NoZWrite));
			RendererToolbox::Line3D(render, wp[4], wp[5], &bone_color, Material::Blend_Alpha, Material::RenderWord(Material::Render_NoZTest | Material::Render_NoZWrite));

			RendererToolbox::Line3D(render, wp[1], wp[2], &bone_color, Material::Blend_Alpha, Material::RenderWord(Material::Render_NoZTest | Material::Render_NoZWrite));
			RendererToolbox::Line3D(render, wp[2], wp[3], &bone_color, Material::Blend_Alpha, Material::RenderWord(Material::Render_NoZTest | Material::Render_NoZWrite));
			RendererToolbox::Line3D(render, wp[3], wp[4], &bone_color, Material::Blend_Alpha, Material::RenderWord(Material::Render_NoZTest | Material::Render_NoZWrite));
			RendererToolbox::Line3D(render, wp[4], wp[1], &bone_color, Material::Blend_Alpha, Material::RenderWord(Material::Render_NoZTest | Material::Render_NoZWrite));
		}
	}
	
	// Display item motions.
	if	(display_flags & SceneDebugDisplayMotions)
		if	(pitem->automation_player && pitem->automation_player->GetMotionList().GetCount() && pitem->GetBaseItem())
		{
			bool found = true;
			Matrix4 m = pitem->GetBaseItem()->GetMatrix();

			for	(int index=0; index< pitem->automation_player->GetMotionList().GetCount(); ++index)
			{
				Motion* motion = pitem->automation_player->GetMotionFromIndex(index);

			//	if(found)
				{
					found = false;
					if(render.GetCamera())
					{
						Vector4 view_position = render.GetCamera()->GetPosition();
						Vector4 view_position_closest_point(0, 0, 0);		
						float closest_t;
						motion->GetClosestPoint(view_position * pitem->GetBaseItem()->GetInverseMatrix(), view_position_closest_point, &closest_t);
											
						Vector4	sample(0, 0, 0);
						motion->EvaluatePosition(Time::fromSec(closest_t+1), sample, Curve::Constant);

						Color color(1,1,0);
						if(sample == view_position_closest_point)
							RendererToolbox::DrawCross(render, view_position_closest_point * m, Units::Mtr(1), &color);
						else
						{
							Vector4 OrthoVec = ((view_position_closest_point - sample).Normalized()).Cross(Vector4(0.0,1.0,0.0));
							
							RendererToolbox::Line3D(render, sample * m, (view_position_closest_point-OrthoVec) * m, &color);
							RendererToolbox::Line3D(render, sample * m, (view_position_closest_point+OrthoVec) * m, &color);
						}

			//			if(motion->quadtree.IsValid())	
				//			motion->quadtree->draw_scene_debug(render, m);
					}
				}

				MotionChannel *c[3];
				motion->GetTransformationChannels(c, NULL, NULL);

				// check all 3 channel has the same number of point, because it's a vector
				// if not just continue to the next
				if( c[0]->GetPoints().GetCount() != c[1]->GetPoints().GetCount()||
					c[0]->GetPoints().GetCount() != c[2]->GetPoints().GetCount()||
					c[1]->GetPoints().GetCount() != c[2]->GetPoints().GetCount()||
					c[1]->GetPoints().GetCount() <= 1)
					continue;

				ArrayList <CurvePoint *> ::Iterator iteratorX(c[0]->GetPoints()); 
				ArrayList <CurvePoint *> ::Iterator iteratorY(c[1]->GetPoints()); 
				ArrayList <CurvePoint *> ::Iterator iteratorZ(c[2]->GetPoints()); 
			
				Vector4 vec;

				Array<Vector4> v;
				v.Allocate(c[0]->GetPointCount()*2);
				Vector4 prev_vec;
				int count = 0;

				for ( ; iteratorX.ObjectPtr(); )	// need to test iterator validity prior to access in case the motion has no point
				{
					vec.x = iteratorX.ObjectPtr()->v; 
					vec.y = iteratorY.ObjectPtr()->v; 
					vec.z = iteratorZ.ObjectPtr()->v; 

					if(count != 0)
					{
						v[(count++)-1] = prev_vec;
						v[(count++)-1] = vec*m;
					}
					else
						++count;

					prev_vec = vec*m;

					++iteratorX;
					++iteratorY;
					++iteratorZ;				
				}

				render.DrawLine(c[0]->GetPointCount()-1, v);

				// draw cross for the first and the last point
				if	((c[0]->GetPoints().GetCount() > 1) && (c[1]->GetPoints().GetCount() > 1) && (c[2]->GetPoints().GetCount() > 1))
				{
					RendererToolbox::DrawCross(render, Vector4(c[0]->GetPoints()[0]->v, c[1]->GetPoints()[0]->v, c[2]->GetPoints()[0]->v)*m, Mtr(1), &Color::Green);
					RendererToolbox::DrawCross(render, Vector4(c[0]->GetPoints()[c[0]->GetPointCount()-1]->v, c[1]->GetPoints()[c[1]->GetPointCount()-1]->v, c[2]->GetPoints()[c[2]->GetPointCount()-1]->v)*m, Mtr(1.1), &Color::Red);
				}
			}
		}
}
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
void				Triangle2d(Renderer &renderer, Vector4 sv[3], Color *c)
{
	Vector4 wv[3];
	for (int n = 0; n < 3; ++n)
		wv[n] = renderer.GetCamera()->ScreenToWorld(renderer.GetViewport(), sv[n].x, sv[n].y);
	Color cl[3];
	for	(int n = 0; n < 3; ++n)
		cl[n] = *c;
	RendererToolbox::Triangle3D(renderer, wv, cl, 0, 0, Material::Blend_Alpha, (Material::RenderWord)(Material::Render_NoZTest | Material::Render_NoZWrite));
}
void				Cartouche(Renderer &render, Vector4 sp, float width, float height, Color *color)
{
	float offset_y = 0.6f;

	Vector4 v[3];
	v[0].Set(sp.x - width * 0.5f, sp.y + (offset_y - 1) * height * 0.5f);
	v[1].Set(sp.x + width * 0.5f, sp.y + (offset_y - 1) * height * 0.5f);
	v[2].Set(sp.x + width * 0.5f, sp.y + (offset_y + 1) * height * 0.5f);
	Triangle2d(render, v, color);
	v[0].Set(sp.x + width * 0.5f, sp.y + (offset_y + 1) * height * 0.5f);
	v[1].Set(sp.x - width * 0.5f, sp.y + (offset_y + 1) * height * 0.5f);
	v[2].Set(sp.x - width * 0.5f, sp.y + (offset_y - 1) * height * 0.5f);
	Triangle2d(render, v, color);

	// Draw border.
	for	(int n = 0; n < 16; ++n)
	{
		v[2].Set(sp.x - width * 0.5f, sp.y);
		float a = n * Deg(180.f / 16.f);
		v[1].Set(sp.x - width * 0.5f - sin(a) * height * 0.25f, sp.y + (offset_y - cos(a)) * height * 0.5f);
		a = (n + 1) * Deg(180.f / 16.f);
		v[0].Set(sp.x - width * 0.5f - sin(a) * height * 0.25f, sp.y + (offset_y - cos(a)) * height * 0.5f);
		Triangle2d(render, v, color);
	}
	for	(int n = 0; n < 16; ++n)
	{
		v[0].Set(sp.x + width * 0.5f, sp.y);
		float a = n * Deg(180.f / 16.f);
		v[1].Set(sp.x + width * 0.5f + sin(a) * height * 0.25f, sp.y + (offset_y - cos(a)) * height * 0.5f);
		a = (n + 1) * Deg(180.f / 16.f);
		v[2].Set(sp.x + width * 0.5f + sin(a) * height * 0.25f, sp.y + (offset_y - cos(a)) * height * 0.5f);
		Triangle2d(render, v, color);
	}
}
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
void				SceneDebugger::DrawItemName(Renderer &renderer, MItem *pitem)
{
	String label;
	switch (pitem->GetItemType())
	{
		case Type_Object:	label = "Object: ";		break;
		case Type_Camera:	label = "Camera: ";		break;
		case Type_Light:	label = "Light: ";		break;
		case Type_Trigger:	label = "Trigger: ";	break;

		default: break;
	}
	label += pitem->name;

	Vector4 sp;
	if	(!renderer.GetCamera()->WorldToScreen(renderer.GetViewport(), pitem->GetBaseItem()->GetMatrix().GetRow(3), sp))
		return;
	sp.y += 0.05f;

	// Draw a background.
//	fRect viewport = renderer.GetViewport();
//	float width = profiler_font.ComputeLineWidth(label.c_str());
	Color bg_color(0.5f, 0.5f, 0.5f, 0.7f);
//	Cartouche(render, sp, k * width * 0.5f, k * 0.075f, &bg_color);
//	render.Write(profiler_font, label, sp.x, sp.y, k, true, Renderer::AlignMiddle);
}
void				SceneDebugger::DrawFigure(Renderer &render, Geometry *figure, const Matrix4 &matrix, Color &color)
{
	if	(!figure)
		return;

	uint line_count = 0;
	for	(uint n = 0; n < figure->pol.GetCount(); ++n)
		line_count += figure->pol[n].vtx_count;

	Array <Vector4> p_line(line_count * 2);
	Array <Color> c_line(line_count * 2);
	Vector4 *p_p = p_line;
	Color *p_c = c_line;

	if	(p_p && p_c)
	{
		for	(uint n = 0; n < figure->pol.GetCount(); ++n)
			for	(int v = 0; v < figure->pol[n].vtx_count; ++v)
			{
				int w = v + 1;
				if	(w == figure->pol[n].vtx_count)
					w = 0;

				*p_p++ = figure->vtx[figure->pol[n].binding[v]];
				*p_p++ = figure->vtx[figure->pol[n].binding[w]];
				*p_c++ = color;
				*p_c++ = color;
			}

		render.SetWorldMatrix(matrix.AsOrthonormalBase() * Matrix4::ScaleMatrix(Vector4(scale, scale, scale)));
		render.DrawLine(line_count, p_line, c_line, Material::Blend_Alpha, (Material::RenderWord)(Material::Render_NoZTest | Material::Render_NoZWrite));
	}
}
//-----------------------------------------------------------------------------

//------------------------------------------------------------------------------
static	void		DrawLineHatched(const Vector4 &a, const Vector4 &b, Vector4 *p_p, Color *p_c)
{
	(*p_c++).Set(0.8f, 0.8f, 0.8f, 0.75f);
	(*p_c++).Set(0.8f, 0.8f, 0.8f, 0.75f);
	(*p_c++).Set(0.8f, 0.8f, 0.8f, 0.75f);
	(*p_c++).Set(0.8f, 0.8f, 0.8f, 0.75f);

	Vector4 dt = b - a;
	*p_p++ = a;	*p_p++ = a + dt * 0.2f;
	*p_p++ = b;	*p_p++ = b - dt * 0.2f;
}
static	void		DrawOBBCorners(Renderer &render, const OBB &obb, const Matrix4 &m)
{
	Vector4 vtx[8];
	vtx[0].Set(-0.5,  0.5,  0.5);
	vtx[1].Set( 0.5,  0.5,  0.5);
	vtx[2].Set( 0.5, -0.5,  0.5);
	vtx[3].Set(-0.5, -0.5,  0.5);
	vtx[4].Set(-0.5,  0.5, -0.5);
	vtx[5].Set( 0.5,  0.5, -0.5);
	vtx[6].Set( 0.5, -0.5, -0.5);
	vtx[7].Set(-0.5, -0.5, -0.5);

	render.SetWorldMatrix(m * Matrix4::TransformationMatrix(obb.bb_position, obb.bb_rotation, obb.bb_scale));

	Array <Vector4> p_line(12 * 2 * 2);
	Array <Color> c_line(12 * 2 * 2);
	Vector4 *p_p = p_line;
	Color *p_c = c_line;

	if	(p_p && p_c)
	{
		DrawLineHatched(vtx[0], vtx[1], p_p + 0, p_c + 0);
		DrawLineHatched(vtx[1], vtx[2], p_p + 4, p_c + 4);
		DrawLineHatched(vtx[2], vtx[3], p_p + 8, p_c + 8);
		DrawLineHatched(vtx[3], vtx[0], p_p + 12, p_c + 12);

		DrawLineHatched(vtx[4], vtx[5], p_p + 16, p_c + 16);
		DrawLineHatched(vtx[5], vtx[6], p_p + 20, p_c + 20);
		DrawLineHatched(vtx[6], vtx[7], p_p + 24, p_c + 24);
		DrawLineHatched(vtx[7], vtx[4], p_p + 28, p_c + 28);

		DrawLineHatched(vtx[0], vtx[4], p_p + 32, p_c + 32);
		DrawLineHatched(vtx[1], vtx[5], p_p + 36, p_c + 36);
		DrawLineHatched(vtx[2], vtx[6], p_p + 40, p_c + 40);
		DrawLineHatched(vtx[3], vtx[7], p_p + 44, p_c + 44);

		render.DrawLine(12 * 2, p_line, c_line, Material::Blend_Alpha, Material::Render_NoZTest);
	}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				SceneDebugger::DrawScene(Renderer &render, const List <MItem *> *selection, const List <MItem *> *blacklist, uint display_flags)
{
	Color	middle_grey(0.72f, 0.72f, 0.72f),
			selected_orange(1, 0.65f, 0.16f),
			draw_color;

	// Display light schematics.
	ListForeachPtr(MItem *, i, scene->GetItemList())
	{
		if	(blacklist && blacklist->Find(i))
			continue;

		switch (i->GetItemType())
		{
			case Type_Light:
				if	(MLight *l = (MLight *)i)
				{
					DrawItem(render, l, display_flags);

					if	(selection && selection->Find(l))
							draw_color = selected_orange;
					else	draw_color = middle_grey;

					switch (l->model)
					{
						case Light::Model_Point:
							DrawFigure(render, pointlight.c_ptr(), l->GetMatrix(), draw_color);
							break;
						case Light::Model_Linear:
							DrawFigure(render, linearlight.c_ptr(), l->GetMatrix(), draw_color);
							break;
						case Light::Model_Spot:
							DrawFigure(render, spotlight.c_ptr(), l->GetMatrix(), draw_color);
							break;

						default: break;
					}
				}
				break;

			case Type_Object:
				if	(MObject *o = (MObject *)i)
				{
					DrawItemBoundingVolume(render, o, display_flags);

					bool display_item = false;
					if	(!selection || selection->Find(o))
					{
						display_item = true;
						draw_color = selected_orange;
					}
					else
						draw_color = middle_grey;

					if	(display_item || o->geometry.IsEmpty())
						DrawItem(render, o, display_flags, &draw_color);

					// DrawFigure(render, dbg_object, obj->GetMatrix(), draw_color);
				}
				break;

			case Type_Camera:
				if	(MCamera *c = (MCamera *)i)
				{
					DrawItem(render, c, display_flags);

					if	(!selection || selection->Find(c))
							draw_color = selected_orange;
					else	draw_color = middle_grey;

					DrawFigure(render, camera.c_ptr(), c->GetMatrix(), draw_color);
				}
				break;

			case Type_Trigger:
				if	(MTrigger *t = (MTrigger *)i)
				{
					OBB obb(t->GetMatrix().GetRow(3), t->GetScale(), &t->GetRotationMatrix());
					Color color(1, 0, 0);
					if	(t->isActive())
						color.Set(0, 1, 0);
					RendererToolbox::DrawOBB(render, obb, &color);

					if	(!selection || selection->Find(t))
							draw_color = selected_orange;
					else	draw_color = middle_grey;

					DrawFigure(render, trigger.c_ptr(), t->GetMatrix(), draw_color);
				}
				break;

			case Type_Constraint:
				if	(MConstraint *c = (MConstraint *)i)
				{
					if	(c->desc.item_a.IsNull() || c->desc.item_b.IsNull())
						continue;

					Vector4	wp_a = c->desc.pivot_a.GetRow(3) * c->desc.item_a->GetBaseItem()->GetMatrix(),
							wp_b = c->desc.pivot_b.GetRow(3) * c->desc.item_b->GetBaseItem()->GetMatrix();

					RendererToolbox::DrawCross(render, wp_a, Cm(20.f));
					RendererToolbox::DrawCross(render, wp_b, Cm(20.f));
					RendererToolbox::Line3D(render, wp_a, wp_b);
				}
				break;

			case Type_Instance:
			{
				//  the items draw debug inside this instance, only if this instance is selected
				if	(selection && selection->Find(i))
				{						
					if	(Instance *instance = (Instance *)i)
					{
						if	(instance->instance_scene)
						{
							// call the draw debug for the object inside this scene instance
							ListForeachPtr(MItem *, i_instance, instance->instance_scene->GetItemList())
							{								
								if	(i_instance->GetItemType() == Type_Object)
								{									
									if	(MObject *o = (MObject *)i_instance)
									{
										DrawItemBoundingVolume(render, o, display_flags);

										bool display_item = false;
										if	(selection->Find(o))
										{
											display_item = true;
											draw_color = selected_orange;
										}
										else
											draw_color = middle_grey;

										if	(display_item || o->geometry.IsEmpty())
											DrawItem(render, o, display_flags, &draw_color);
									}
								}
							}
						}
					}
				}
			}
			break;

			default: break;
		}
	}

	// Display bounding.
	MinMax minmax;
	ListForeachPtr(MItem *, i, *selection)
		switch (i->GetItemType())
		{
			case Type_Object:
			{
				((MObject *)i)->ComputeLocalMinMax(minmax);
				OBB obb(minmax);
				DrawOBBCorners(render, obb, i->GetBaseItem()->GetMatrix());
			}
			break;

			case Type_Instance:
			{
				Instance *instance = (Instance *)i;
				if	(instance->instance_scene)
				{
					instance->instance_scene->GetMinMax(minmax);
					OBB obb(minmax);
					DrawOBBCorners(render, obb, i->GetBaseItem()->GetMatrix());
				}
			}
			break;

			default: break;
		}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				SceneDebugger::LoadSchematicFigures(const char *dbg_pointlight, const char *dbg_spotlight, const char *dbg_linearlight, const char *dbg_camera, const char *dbg_object, const char *dbg_trigger)
{
	pointlight = new Geometry;
	pointlight->name = dbg_pointlight;
	NML::LoadFromFile(*pointlight, dbg_pointlight);
	spotlight = new Geometry;
	spotlight->name = dbg_spotlight;
	NML::LoadFromFile(*spotlight, dbg_spotlight);
	linearlight = new Geometry;
	linearlight->name = dbg_linearlight;
	NML::LoadFromFile(*linearlight, dbg_linearlight);

	camera = new Geometry;
	camera->name = dbg_camera;
	NML::LoadFromFile(*camera, dbg_camera);
	object = new Geometry;
	object->name = dbg_object;
	NML::LoadFromFile(*object, dbg_object);
	trigger = new Geometry;
	trigger->name = dbg_trigger;
	NML::LoadFromFile(*trigger, dbg_trigger);
	return true;
}
void				SceneDebugger::SetScene(Scene *s)
{	scene = s;	}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
SceneDebugger::SceneDebugger()
{
	scene = NULL;
	scale = 1.f;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Scene::DumpContentToLog() const
{
	__LOG_V__ << "Dumping scene content\n\n";

	__LOG_V__ << "Item list (" << GetActiveList().GetCount() << " entries):\n";
	ArrayListForeachPtr(MItem *, i, GetActiveList())
	{
		MItem *pi = NULL;
		if	(i->GetBaseItem())
			LocateItem(i->GetBaseItem()->GetParent(), &pi);
		__LOG_V__ << "  - '" << i->name << "' (" << i->GetUid() << ")";

		if	(pi)
				__LOG_V__ << "(Linked to '" << pi->name << "' (" << pi->GetUid() << ")), (Active: " << (i->isActive() ? "yes" : "no") << ", Invisible: " << (i->GetBaseItem()->item_flags.IsSet(ItemFlagInvisible) ? "yes" : "no") << ")\n";
		else	__LOG_V__ << "\n";
	}
	__LOG_V__ << "\n";
}
//------------------------------------------------------------------------------
