/*
Minetest-c55
Copyright (C) 2010 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#ifndef VOXEL_HEADER
#define VOXEL_HEADER

#include "common_irrlicht.h"
#include "mapblock.h"
#include <iostream>

/*
	TODO: A fast voxel manipulator class

	Not thread-safe.
*/

/*
	This class resembles aabbox3d<s16> a lot, but has inclusive
	edges for saner handling of integer sizes
*/
class VoxelArea
{
public:
	// Starts as zero sized
	VoxelArea():
		MinEdge(1,1,1),
		MaxEdge(0,0,0)
	{
	}
	VoxelArea(v3s16 min_edge, v3s16 max_edge):
		MinEdge(min_edge),
		MaxEdge(max_edge)
	{
	}
	VoxelArea(v3s16 p):
		MinEdge(p),
		MaxEdge(p)
	{
	}
	void addArea(VoxelArea &a)
	{
		if(a.MinEdge.X < MinEdge.X) MinEdge.X = a.MinEdge.X;
		if(a.MinEdge.Y < MinEdge.Y) MinEdge.Y = a.MinEdge.Y;
		if(a.MinEdge.Z < MinEdge.Z) MinEdge.Z = a.MinEdge.Z;
		if(a.MaxEdge.X > MaxEdge.X) MaxEdge.X = a.MaxEdge.X;
		if(a.MaxEdge.Y > MaxEdge.Y) MaxEdge.Y = a.MaxEdge.Y;
		if(a.MaxEdge.Z > MaxEdge.Z) MaxEdge.Z = a.MaxEdge.Z;
	}
	void addPoint(v3s16 p)
	{
		if(p.X < MinEdge.X) MinEdge.X = p.X;
		if(p.Y < MinEdge.Y) MinEdge.Y = p.Y;
		if(p.Z < MinEdge.Z) MinEdge.Z = p.Z;
		if(p.X > MaxEdge.X) MaxEdge.X = p.X;
		if(p.Y > MaxEdge.Y) MaxEdge.Y = p.Y;
		if(p.Z > MaxEdge.Z) MaxEdge.Z = p.Z;
	}
	v3s16 getExtent()
	{
		return MaxEdge - MinEdge + v3s16(1,1,1);
	}
	s32 getVolume()
	{
		v3s16 e = getExtent();
		return (s32)e.X * (s32)e.Y * (s32)e.Z;
	}
	bool isInside(v3s16 p)
	{
		return(
			p.X >= MinEdge.X && p.X <= MaxEdge.X &&
			p.Y >= MinEdge.Y && p.Y <= MaxEdge.Y &&
			p.Z >= MinEdge.Z && p.Z <= MaxEdge.Z
		);
	}
	bool operator==(const VoxelArea &other)
	{
		return (MinEdge == other.MinEdge
				&& MaxEdge == other.MaxEdge);
	}
	
	/*
		Translates position from virtual coordinates to array index
	*/
	s32 index(s16 x, s16 y, s16 z)
	{
		v3s16 em = getExtent();
		v3s16 off = MinEdge;
		s32 i = (s32)(z-off.Z)*em.Y*em.X + (y-off.Y)*em.X + (x-off.X);
		//dstream<<" i("<<x<<","<<y<<","<<z<<")="<<i<<" ";
		return i;
	}
	s32 index(v3s16 p)
	{
		return index(p.X, p.Y, p.Z);
	}

	void print(std::ostream &o)
	{
		o<<"("<<MinEdge.X
		 <<","<<MinEdge.Y
		 <<","<<MinEdge.Z
		 <<")("<<MaxEdge.X
		 <<","<<MaxEdge.Y
		 <<","<<MaxEdge.Z
		 <<")";
	}

	// Edges are inclusive
	v3s16 MinEdge;
	v3s16 MaxEdge;
};

class VoxelManipulator : public NodeContainer
{
public:
	VoxelManipulator();
	~VoxelManipulator();
	
	/*
		Virtuals from NodeContainer
	*/
	virtual u16 nodeContainerId() const
	{
		return NODECONTAINER_ID_VOXELMANIPULATOR;
	}
	bool isValidPosition(v3s16 p)
	{
		return m_area.isInside(p);
	}
	// These are a bit slow and shouldn't be used internally
	MapNode getNode(v3s16 p)
	{
		if(isValidPosition(p) == false)
			emerge(VoxelArea(p));

		MapNode &n = m_data[m_area.index(p)];

		//TODO: Is this the right behaviour?
		if(n.d == MATERIAL_IGNORE)
			throw InvalidPositionException
			("Not returning MATERIAL_IGNORE in VoxelManipulator");

		return n;
	}
	void setNode(v3s16 p, MapNode & n)
	{
		if(isValidPosition(p) == false)
			emerge(VoxelArea(p));
		m_data[m_area.index(p)] = n;
	}
	
	MapNode & operator[](v3s16 p)
	{
		//dstream<<"operator[] p=("<<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;
		if(isValidPosition(p) == false)
			emerge(VoxelArea(p));
		return m_data[m_area.index(p)];
	}

	/*
		Manipulation of bigger chunks
	*/
	
	void print(std::ostream &o);
	
	void addArea(VoxelArea area);

	void interpolate(VoxelArea area);

	/*void blitFromNodeContainer
			(v3s16 p_from, v3s16 p_to, v3s16 size, NodeContainer *c);
	
	void blitToNodeContainer
			(v3s16 p_from, v3s16 p_to, v3s16 size, NodeContainer *c);*/

	/*
		Virtual functions
	*/
	
	/*
		Get the contents of the requested area from somewhere.

		If not found from source, add as MATERIAL_IGNORE.
	*/
	virtual void emerge(VoxelArea a)
	{
		//dstream<<"emerge p=("<<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;
		addArea(a);
	}

	/*
		Member variables
	*/

	/*
		The area that is stored in m_data.
		addInternalBox should not be used if getExtent() == v3s16(0,0,0)
		MaxEdge is 1 higher than maximum allowed position
	*/
	VoxelArea m_area;
	/*
		NULL if data size is 0
		Data is stored as [z*h*w + y*h + x]
		Special data values:
			MATERIAL_IGNORE: Unspecified node
	*/
	MapNode *m_data;
private:
};

class Map;

class MapVoxelManipulator : public VoxelManipulator
{
public:
	MapVoxelManipulator(Map *map);

	virtual void emerge(VoxelArea a);

	void blitBack(core::map<v3s16, MapBlock*> & modified_blocks);

private:
	Map *m_map;
};

#endif