#include "Commands_PathGrid.h"
#include "ParamInfos.h"
#include "ScriptUtils.h"

#if OBLIVION
#include "GameAPI.h"
#include "GameForms.h"
#include "GameObjects.h"
#include "ArrayVar.h"

/* Path grid nodes don't have formIDs. We need a way for scripts to identify nodes, but scripts
	may be interested in nodes outside of a single pathgrid when dealing with exterior cells.
	So NodeID provides a uniform way for scripts to refer to nodes by integer ID, relative to the pathgrid
	of the player's current cell. ID includes index into the local pathgrid's array of nodes, plus
	offset from the current pathgrid.
	For obvious reasons NodeID's become invalid when the player changes cells.
*/

TESObjectCELL* GetCurrentCell()
{
	return (*g_thePlayer)->parentCell;
}

TESObjectCELL* GetAbsoluteCell(float x, float y)
{
	TESWorldSpace* world = GetCurrentCell()->worldSpace;
	if (world) {
		SInt32 sx = x;
		SInt32 sy = y;
		return world->LookupCell(sx >> 12, sy >> 12);
	}
	else {
		return NULL;
	}
}

TESObjectCELL* GetRelativeCell(SInt32 dx, SInt32 dy)
{
	TESObjectCELL* cur = GetCurrentCell();
	if (cur->IsInterior() || !cur->coords) {
		return (dx == 0 && dy == 0) ? cur : NULL;
	}
	else if (dx == 0 && dy == 0) {
		return cur;
	}
	else if (cur->worldSpace) {
		dx += cur->coords->x;
		dy += cur->coords->y;
		return cur->worldSpace->LookupCell(dx, dy);
	}

	return NULL;
}

class NodeID
{
	struct  {
		// cell offset is sxxxsyyy where xxx is x offset, yyy is y offset, s is sign (1=negative)
		UInt16		localIndex;
		UInt8		cellOffset;
		UInt8		pad;		// don't want to use upper 8 bits to prevent bits getting chopped off on conversion to double

		UInt32	ToInteger() const { return *((UInt32*)this); }
	}	m_id;

public:
	NodeID(UInt16 idx, SInt8 cellDX, SInt8 cellDY) {
		m_id.localIndex = idx;
		m_id.cellOffset = 0;
		if (cellDX < 0) {
			m_id.cellOffset |= 0x80;
			cellDX *= -1;
		}
		if (cellDY < 0) {
			m_id.cellOffset |= 0x08;
			cellDY *= -1;
		}

		m_id.cellOffset |= cellDY | (cellDX << 4);
		m_id.pad = 0;
	}

	NodeID() {
		// init to node ID -1
		m_id.localIndex = -1;
		m_id.cellOffset = -1;
		m_id.pad = -1; 
	}

	UInt32 GetIntegerID() const { return  m_id.ToInteger(); }

	UInt16 GetIndex() const { return m_id.localIndex; }

	TESPathGridPoint* GetPoint() const {
		TESPathGrid* grid = GetParentGrid();
		UInt16 idx = GetIndex();
		if (grid && idx < grid->nodeCount) {
			return grid->nodes->data[idx];
		}

		return NULL;
	}

	SInt8 GetYOffset() const {
		SInt8 y = m_id.cellOffset & 0x07;
		if (m_id.cellOffset & 0x08) {
			y *= -1;
		}
		return y;
	}

	SInt8 GetXOffset() const {
		SInt8 x = (m_id.cellOffset >> 4) & 0x07;
		if (m_id.cellOffset & 0x80) {
			x *= -1;
		}
		return x;
	}

	TESObjectCELL* GetParentCell() const {
		return GetRelativeCell(GetXOffset(), GetYOffset());
	}

	TESPathGrid* GetParentGrid() const {
		TESObjectCELL* cell = GetParentCell();
		return cell ? cell->pathGrid : NULL;
	}
};

STATIC_ASSERT(sizeof(NodeID) == sizeof(UInt32));


	

/* Cmds */

static bool Cmd_IsPathNodeDisabled_Execute(COMMAND_ARGS)
{
	NodeID node;
	*result = -1.0;

	if (ExtractArgs(PASS_EXTRACT_ARGS, &node)) {
		TESPathGridPoint* pt = node.GetPoint();
		if (pt) {
			*result = pt->IsDisabled() ? 1.0 : 0.0;
		}
	}

	return true;
}

static bool Cmd_GetPathNodePos_Execute(COMMAND_ARGS)
{
	NodeID node;
	UInt32 axis = 0;
	*result = 0.0;

	if (ExtractArgs(PASS_EXTRACT_ARGS, &node, &axis)) {
		TESPathGridPoint* pt = node.GetPoint();
		if (pt) {
			switch (axis) {
				case 'X':
				case 'x':
					*result = pt->x;
					break;
				case 'Y':
				case 'y':
					*result = pt->y;
					break;
				case 'Z':
				case 'z':
					*result = pt->z;
					break;
			}
		}
	}

	return true;
}

static bool Cmd_SetPathNodeDisabled_Execute(COMMAND_ARGS)
{
	NodeID node;
	UInt32 bDisable = 1;
	*result = 0.0;

	if (ExtractArgs(PASS_EXTRACT_ARGS, &node, &bDisable)) {
		TESPathGrid* grid = node.GetParentGrid();
		if (grid) {
			*result = grid->SetPointDisabled(node.GetIndex(), bDisable ? true : false) ? 1.0 : 0.0;
		}
	}

	return true;
}

static bool Cmd_SetPathNodePreferred_Execute(COMMAND_ARGS)
{
	NodeID node;
	UInt32 bPreferred = 1;
	*result = 0.0;

	if (ExtractArgs(PASS_EXTRACT_ARGS, &node, &bPreferred)) {
		TESPathGrid* grid = node.GetParentGrid();
		if (grid) {
			*result = grid->SetPointPreferred(node.GetIndex(), bPreferred ? true : false) ? 1.0 : 0.0;
		}
	}

	return true;
}

static bool Cmd_GetAllPathNodes_Execute(COMMAND_ARGS)
{
	// a test command
	SInt32 cellDepth = 0;
	UInt32 bIncludeDisabled = 0;
	ArrayID arr = g_ArrayMap.Create(kDataType_Numeric, true, scriptObj->GetModIndex());
	*result = arr;

	if (ExtractArgs(PASS_EXTRACT_ARGS, &cellDepth, &bIncludeDisabled) && cellDepth >= 0) {
		TESObjectCELL* cur = GetCurrentCell();
		SInt32 cellX = 0;
		SInt32 cellY = 0;
		if (cur->IsInterior()) {
			cellDepth = 0;
			cellX = 0;
			cellY = 0;
		}
		else {
			cellX = cur->coords->x;
			cellY = cur->coords->y;
		}

		double curIdx = 0.0;
		for (SInt32 dx = -cellDepth; dx <= cellDepth; dx++) {
			for (SInt32 dy = -cellDepth; dy <= cellDepth; dy++) {
				cur = GetRelativeCell(dx, dy);
				if (cur && cur->pathGrid && cur->pathGrid->nodeCount) {
					for (UInt32 i = 0; i < cur->pathGrid->nodeCount; i++) {
						TESPathGridPoint* pt = cur->pathGrid->nodes->data[i];
						if (pt && (bIncludeDisabled || !pt->IsDisabled())) {
							g_ArrayMap.SetElementNumber(arr, curIdx, NodeID(i, dx, dy).GetIntegerID());
							curIdx += 1.0;
						}
						if (!pt) {
							DEBUG_PRINT("	* Found a null path grid point");
						}
					}
				}
			}
		}
	}

	return true;
}

static bool Cmd_PathEdgeExists_Execute(COMMAND_ARGS)
{
	NodeID a;
	NodeID b;
	*result = 0.0;
	if (ExtractArgs(PASS_EXTRACT_ARGS, &a, &b)) {
		TESPathGridPoint* pa = a.GetPoint();
		TESPathGridPoint* pb = b.GetPoint();

		if (a.GetParentGrid() == b.GetParentGrid()) {
			tList<TESPathGridPoint>::Iterator iter = pa->edges.Begin();
			while (!iter.End()) {
				if (iter.Get() == pb) {
					break;
				}
				++iter;
			}
			
			if (!iter.End()) {
				*result = 1.0;
			}
		}
		else {
			TESPathGrid* grid = a.GetParentGrid();
			UInt16 aID = a.GetIndex();
			if (grid && grid->externalEdges) {
				tList<TESPathGrid::ExternalEdge>::Iterator iter = grid->externalEdges->Begin();
				while (!iter.End()) {
					TESPathGrid::ExternalEdge* edge = iter.Get();
					if (edge->localNodeID == aID) {
						if (FloatEqual(edge->x, pb->x) && FloatEqual(edge->y, pb->y) && FloatEqual(edge->z, pb->z)) {
							break;
						}
					}
					++iter;
				}

				if (!iter.End()) {
					*result = 1.0;
				}
			}
		}
	}

	return true;
}

static bool Cmd_GetPathNodeLinkedRef_Execute(COMMAND_ARGS)
{
	NodeID node;
	UInt32* refResult = (UInt32*)result;
	*refResult = 0;

	if (ExtractArgs(PASS_EXTRACT_ARGS, &node)) {
		TESPathGrid* grid = node.GetParentGrid();
		if (grid) {
			TESObjectREFR* refr = grid->GetLinkedRef(node.GetPoint());
			if (refr) {
				*refResult = refr->refID;
			}
		}
	}

	return true;
}

static bool Cmd_AddPathNode_Execute(COMMAND_ARGS)
{
	float x, y, z;
	UInt32 bPreferred = 0;
	NodeID newNode;

	if (ExtractArgs(PASS_EXTRACT_ARGS, &x, &y, &z, &bPreferred)) {
		TESObjectCELL* cell = GetAbsoluteCell(x, y);
		if (cell && cell->pathGrid) {
			UInt16 idx = cell->pathGrid->AddNode(x, y, z, bPreferred ? true : false);
			if (idx != -1) {
				//newNode = NodeID(//###TODO: construct NodeID from TESPathGridPoint
			}
		}
	}
	return true;
}


#endif

static ParamInfo kParams_GetAllPathNodes[2] =
{
	{	"cell depth",				kParamType_Integer,	1	},
	{	"include disabled nodes",	kParamType_Integer, 1	},
};

DEFINE_COMMAND(GetAllPathNodes, returns an array of all path nodes within a cell, 0, 2, kParams_GetAllPathNodes);
DEFINE_COMMAND(IsPathNodeDisabled, returns true if the node is disabled, 0, 1, kParams_OneInt);
DEFINE_COMMAND(SetPathNodeDisabled, sets the disabled flag for the node, 0, 2, kParams_TwoInts);

static ParamInfo kParams_GetPathNodePos[2] =
{
	{	"nodeID",	kParamType_Integer,	0	},
	{	"axis",		kParamType_Axis,	0	},
};

DEFINE_COMMAND(GetPathNodePos, gets the position of the node, 0, 2, kParams_GetPathNodePos);
DEFINE_COMMAND(SetPathNodePreferred, sets the preferred flag for the node, 0, 2, kParams_TwoInts);