#ifndef __SPLINES_H
#define __SPLINES_H

extern "C" {
#ifdef Q3RADIANT
#include "../qgl.h"
#else
#include "../renderer/qgl.h"
#endif
}
#include "util_list.h"
#include "util_str.h"
#include "math_vector.h"

typedef int fileHandle_t;

extern void glBox(idVec3_t &color, idVec3_t &point, float size);
extern void glLabeledPoint(idVec3_t &color, idVec3_t &point, float size, const char *label);

static vec4_t blue(0, 0, 1, 1);
static vec4_t red(1, 0, 0, 1);

class idPointListInterface {
public:
	idPointListInterface() {
		selectedPoints.Clear();
	}
	virtual ~idPointListInterface() {}
	
	virtual int numPoints() {
		return 0;
	}
	
	virtual void addPoint(const float x, const float y, const float z) {}
	virtual void addPoint(const idVec3_t &v) {}
	virtual void removePoint(int index) {}
	virtual idVec3_t *getPoint(int index) { return NULL; }
	
	int	selectPointByRay(float ox, float oy, float oz, float dx, float dy, float dz, bool single) {
		idVec3_t origin(ox, oy, oz);
		idVec3_t dir(dx, dy, dz);
		return selectPointByRay(origin, dir, single);
	}

	int	selectPointByRay(const idVec3_t origin, const idVec3_t direction, bool single) {
		int		i, besti, count;
		float	d, bestd;
		idVec3_t	temp, temp2;

		// find the point closest to the ray
		besti = -1;
		bestd = 8;
		count = numPoints();

		for (i=0; i < count; i++) {
			temp = *getPoint(i);
			temp2 = temp;
			temp -= origin;
			d = DotProduct(temp, direction);
			__VectorMA (origin, d, direction, temp);
			temp2 -= temp;
			d = temp2.Length();
			if (d <= bestd) {
				bestd = d;
				besti = i;
			}
		}

		if (besti >= 0) {
			selectPoint(besti, single);
		}

		return besti;
	}

	int isPointSelected(int index) {
		int count = selectedPoints.Num();
		for (int i = 0; i < count; i++) {
			if (selectedPoints[i] == index) {
				return i;
			}
		}
		return -1;
	}
	
	int selectPoint(int index, bool single) {
		if (index >= 0 && index < numPoints()) {
			if (single) {
				deselectAll();
			} else {
				if (isPointSelected(index) >= 0) {
					selectedPoints.Remove(index);
				}
			}
			return selectedPoints.Append(index);
		}
		return -1;
	}
	
	void selectAll() {
		selectedPoints.Clear();
		for (int i = 0; i < numPoints(); i++) {
			selectedPoints.Append(i);
		}
	}

	void deselectAll() {
		selectedPoints.Clear();
	}

	int numSelectedPoints();
    
	idVec3_t *getSelectedPoint(int index) {
		assert(index >= 0 && index < numSelectedPoints());
		return getPoint(selectedPoints[index]);
	}
	
	virtual void updateSelection(float x, float y, float z) {
		idVec3_t move(x, y, z);
		updateSelection(move);
	}

	virtual void updateSelection(const idVec3_t &move) {
		int count = selectedPoints.Num();
		for (int i = 0; i < count; i++) {
			*getPoint(selectedPoints[i]) += move;
		}
	}

	void drawSelection() {
		int count = selectedPoints.Num();
		for (int i = 0; i < count; i++) {
			glBox(red, *getPoint(selectedPoints[i]), 4);
		}
	}

protected:
	idList<int> selectedPoints;

};


class idSplineList {

public:

	idSplineList() {
		clear();
	}

	idSplineList(const char *p) {
		clear();
		name = p;
	};

	~idSplineList() {
		clear();
	};

	void clearControl() {
		for (int i = 0; i < controlPoints.Num(); i++) {
			delete controlPoints[i];
		}
		controlPoints.Clear();
	}

	void clearSpline() {
		for (int i = 0; i < splinePoints.Num(); i++) {
			delete splinePoints[i];
		}
		splinePoints.Clear();
	}

	void parse(const char *(*text));
	void write(fileHandle_t file, const char *name);

	void clear() {
		clearControl();
		clearSpline();
		splineTime.Clear();
		selected = NULL;
		dirty = true;
		activeSegment = 0;
		granularity = 0.025;
		pathColor.set(1.0, 0.5, 0.0);
		controlColor.set(0.7, 0.0, 1.0);
		segmentColor.set(0.0, 0.0, 1.0);
		activeColor.set(1.0, 0.0, 0.0);
	}

	void initPosition(long startTime, long totalTime);
	const idVec3_t *getPosition(long time);


	void draw(bool editMode);
	void addToRenderer();

	void setSelectedPoint(idVec3_t *p);
	idVec3_t *getSelectedPoint() {
	  return selected;
	}

	void addPoint(const idVec3_t &v) {
		controlPoints.Append(new idVec3_t(v));
		dirty = true;
	}

	void addPoint(float x, float y, float z) {
		controlPoints.Append(new idVec3_t(x, y, z));
		dirty = true;
	}

	void updateSelection(const idVec3_t &move);

	void startEdit() {
		editMode = true;
	}
		
	void stopEdit() {
		editMode = false;
	}

	void buildSpline();

	void setGranularity(float f) {
		granularity = f;
	}

	float getGranularity() {
		return granularity;
	}

	int numPoints() {
		return controlPoints.Num();
	}

	idVec3_t *getPoint(int index) {
		assert(index >= 0 && index < controlPoints.Num());
		return controlPoints[index];
	}

	idVec3_t *getSegmentPoint(int index) {
		assert(index >= 0 && index < splinePoints.Num());
		return splinePoints[index];
	}


	void setSegmentTime(int index, int time) {
		assert(index >= 0 && index < splinePoints.Num());
		splineTime[index] = time;
	}

	double getSegmentTime(int index) {
		assert(index >= 0 && index < splinePoints.Num());
		return splineTime[index];
	}
	void addSegmentTime(int index, int time) {
		assert(index >= 0 && index < splinePoints.Num());
		splineTime[index] += time;
	}

	float totalDistance();

	static idVec3_t zero;

	int getActiveSegment() {
		return activeSegment;
	}

	void setActiveSegment(int i) {
		//assert(i >= 0 && (splinePoints.Num() > 0 && i < splinePoints.Num()));
		activeSegment = i;
	}

	int numSegments() {
		return splinePoints.Num();
	}

	void setColors(idVec3_t &path, idVec3_t &segment, idVec3_t &control, idVec3_t &active) {
		pathColor = path;
		segmentColor = segment;
		controlColor = control;
		activeColor = active;
	}

	const char *getName() {
		return name.c_str();
	}

	void setName(const char *p) {
		name = p;
	}

	bool validTime() {
		if (dirty) {
			buildSpline();
		}
		// gcc doesn't allow static casting away from bools
		// why?  I've no idea...
		return (bool)(splineTime.Num() > 0 && splineTime.Num() == splinePoints.Num());
	}

	void setTime(long t) {
		time = t;
	}

	void setBaseTime(long t) {
		baseTime = t;
	}

protected:
	idStr name;
	float calcSpline(int step, float tension);
	idList<idVec3_t*> controlPoints;
	idList<idVec3_t*> splinePoints;
	idList<double> splineTime;
	idVec3_t *selected;
	idVec3_t pathColor, segmentColor, controlColor, activeColor;
	float granularity;
	bool editMode;
	bool dirty;
	int activeSegment;
	long baseTime;
	long time;
	friend class idCamera;
};

// time in milliseconds 
// velocity where 1.0 equal rough walking speed
struct idVelocity {
	idVelocity(long start, long duration, float s) {
		startTime = start;
		time = duration;
		speed = s;
	}
	long	startTime;
	long	time;
	float	speed;
};

// can either be a look at or origin position for a camera
// 
class idCameraPosition : public idPointListInterface {
public:
	
	virtual void clear() {
		editMode = false;
		for (int i = 0; i < velocities.Num(); i++) {
			delete velocities[i];
			velocities[i] = NULL;
		}
		velocities.Clear();
	}

	idCameraPosition(const char *p) {
		name = p;
	}

	idCameraPosition() {
		time = 0;
		name = "position";
	}

	idCameraPosition(long t) {
		time = t;
	}

	virtual ~idCameraPosition() {
		clear();
	}

	
	// this can be done with RTTI syntax but i like the derived classes setting a type
	// makes serialization a bit easier to see
	//
	enum positionType {
		FIXED = 0x00,
		INTERPOLATED,
		SPLINE,
		POSITION_COUNT
	};


	virtual void start(long t) {
		startTime = t;
	}

	long getTime() {
		return time;
	}

	virtual void setTime(long t) {
		time = t;
	}

	float getVelocity(long t) {
		long check = t - startTime;
		for (int i = 0; i < velocities.Num(); i++) {
			if (check >= velocities[i]->startTime && check <= velocities[i]->startTime + velocities[i]->time) {
				return velocities[i]->speed;
			}
		}
		return baseVelocity;
	}

	void addVelocity(long start, long duration, float speed) {
		velocities.Append(new idVelocity(start, duration, speed));
	}

	virtual const idVec3_t *getPosition(long t) { 
		assert(true);
		return NULL;
	}

	virtual void draw(bool editMode) {};

	virtual void parse(const char *(*text)) {};
	virtual void write(fileHandle_t file, const char *name);
	virtual bool parseToken(const char *key, const char *(*text));

	const char *getName() {
		return name.c_str();
	}

	void setName(const char *p) {
		name = p;
	}

	virtual void startEdit() {
		editMode = true;
	}

	virtual void stopEdit() {
		editMode = false;
	}

	virtual void draw() {};

	const char *typeStr() {
		return positionStr[static_cast<int>(type)];
	}

	void calcVelocity(float distance) {
		float secs = (float)time / 1000;
		baseVelocity = distance / secs;
	}

protected:
	static const char* positionStr[POSITION_COUNT];
	long		startTime;
	long		time;
	idCameraPosition::positionType type;
	idStr		name;
	bool	editMode;
	idList<idVelocity*> velocities;
	float		baseVelocity;
};

class idFixedPosition : public idCameraPosition {
public:

	void init() {
		pos.Zero();
		type = idCameraPosition::FIXED;
	}
	
	idFixedPosition() : idCameraPosition() {
		init();
	}
	
	idFixedPosition(idVec3_t p) : idCameraPosition() {
		init();
		pos = p;
	}

	virtual void addPoint(const idVec3_t &v) {
		pos = v;
	}
	
	virtual void addPoint(const float x, const float y, const float z) {
		pos.set(x, y, z);
	}


	~idFixedPosition() {
	}

	virtual const idVec3_t *getPosition(long t) { 
		return &pos;
	}

	void parse(const char *(*text));
	void write(fileHandle_t file, const char *name);

	virtual int numPoints() {
		return 1;
	}

	virtual idVec3_t *getPoint(int index) {
		if (index != 0) {
			assert(true);
		};
		return &pos;
	}

	virtual void draw(bool editMode) {
		glLabeledPoint(blue, pos, (editMode) ? 5 : 3, "Fixed point");
	}

protected:
	idVec3_t pos;
};

class idInterpolatedPosition : public idCameraPosition {
public:

	void init() {
		type = idCameraPosition::INTERPOLATED;
		first = true;
		startPos.Zero();
		endPos.Zero();
	}
	
	idInterpolatedPosition() : idCameraPosition() {
		init();
	}
	
	idInterpolatedPosition(idVec3_t start, idVec3_t end, long time) : idCameraPosition(time) {
		init();
		startPos = start;
		endPos = end;
	}

	~idInterpolatedPosition() {
	}

	virtual const idVec3_t *getPosition(long t);

	void parse(const char *(*text));
	void write(fileHandle_t file, const char *name);

	virtual int numPoints() {
		return 2;
	}

	virtual idVec3_t *getPoint(int index) {
		assert(index >= 0 && index < 2);
		if (index == 0) {
			return &startPos;
		}
		return &endPos;
	}

	virtual void addPoint(const float x, const float y, const float z) {
		if (first) {
			startPos.set(x, y, z);
			first = false;
		} else {
			endPos.set(x, y, z);
			first = true;
		}
	}

	virtual void addPoint(const idVec3_t &v) {
		if (first) {
			startPos = v;
			first = false;
		} else {
			endPos = v;
			first = true;
		}
	}

	virtual void draw(bool editMode) {
		glLabeledPoint(blue, startPos, (editMode) ? 5 : 3, "Start interpolated");
		glLabeledPoint(blue, endPos, (editMode) ? 5 : 3, "End interpolated");
		qglBegin(GL_LINES);
		qglVertex3fv(startPos);
		qglVertex3fv(endPos);
		qglEnd();
	}

	virtual void start(long t) {
		idCameraPosition::start(t);
		lastTime = startTime;
		distSoFar = 0.0;
		idVec3_t temp = startPos;
		temp -= endPos;
		calcVelocity(temp.Length());
	}

protected:
	bool first;
	idVec3_t startPos;
	idVec3_t endPos;
	long lastTime;
	float distSoFar;
};

class idSplinePosition : public idCameraPosition {
public:

	void init() {
		type = idCameraPosition::SPLINE;
	}
	
	idSplinePosition() : idCameraPosition() {
		init();
	}
	
	idSplinePosition(long time) : idCameraPosition(time) {
		init();
	}

	~idSplinePosition() {
	}

	virtual void start(long t) {
		idCameraPosition::start(t);
		target.initPosition(t, time);
		calcVelocity(target.totalDistance());
	}

	virtual const idVec3_t *getPosition(long t) { 
		return target.getPosition(t);
	}

	//virtual const idVec3_t *getPosition(long t) const { 

	void addControlPoint(idVec3_t &v) {
		target.addPoint(v);
	}

	void parse(const char *(*text));
	void write(fileHandle_t file, const char *name);

	virtual int numPoints() {
		return target.numPoints();
	}

	virtual idVec3_t *getPoint(int index) {
		return target.getPoint(index);
	}

	virtual void addPoint(const idVec3_t &v) {
		target.addPoint(v);
	}

	virtual void addPoint(const float x, const float y, const float z) {
		target.addPoint(x, y, z);
	}

	virtual void draw(bool editMode) {
		target.draw(editMode);
	}

	virtual void updateSelection(const idVec3_t &move) {
		idCameraPosition::updateSelection(move);
		target.buildSpline();
	}

protected:
	idSplineList target;
};

class idCameraFOV {
public:
	
	idCameraFOV() {
		time = 0;
		fov = 90;
	}

	idCameraFOV(int v) {
		time = 0;
		fov = v;
	}

	idCameraFOV(int s, int e, long t) {
		startFOV = s;
		endFOV = e;
		time = t;
	}


	~idCameraFOV(){}

	void setFOV(float f) {
		fov = f;
	}

	float getFOV(long t) {
		if (time) {
			assert(startTime);
			float percent = t / startTime;
			float temp = startFOV - endFOV;
			temp *= percent;
			fov = startFOV + temp;
		}
		return fov;
	}

	void start(long t) {
		startTime = t;
	}

	void parse(const char *(*text));
	void write(fileHandle_t file, const char *name);

protected:
	float fov;
	float startFOV;
	float endFOV;
	int startTime;
	int time;
};




class idCameraEvent {
public:
	enum eventType {
		EVENT_NA = 0x00,
		EVENT_WAIT,
		EVENT_TARGETWAIT,
		EVENT_SPEED,
		EVENT_TARGET,
		EVENT_SNAPTARGET,
		EVENT_FOV,
		EVENT_SCRIPT,
		EVENT_TRIGGER,
		EVENT_STOP,
		EVENT_COUNT
	};

	static const char* eventStr[EVENT_COUNT];

	idCameraEvent() {
		paramStr = "";
		type = EVENT_NA;
		time = 0;
	}

	idCameraEvent(eventType t, const char *param, long n) {
		type = t;
		paramStr = param;
		time = n;
	}

	~idCameraEvent() {};

	eventType getType() {
		return type;
	}

	const char *typeStr() {
		return eventStr[static_cast<int>(type)];
	}

	const char *getParam() {
		return paramStr.c_str();
	}

	long getTime() {
		return time;
	}

	void setTime(long n) {
		time = n;
	}

	void parse(const char *(*text));
	void write(fileHandle_t file, const char *name);

	void setTriggered(bool b) {
		triggered = b;
	}

	bool getTriggered() {
		return triggered;
	}

protected:
	eventType type;
	idStr paramStr;
	long time;
	bool triggered;

};

class idCameraDef {
public:

	void clear() {
		currentCameraPosition = 0;
		cameraRunning = false;
		lastDirection.Zero();
		baseTime = 30;
		activeTarget = 0;
		name = "camera01";
		fov.setFOV(90);
		int i;
		for (i = 0; i < targetPositions.Num(); i++) {
			delete targetPositions[i];
		}
		for (i = 0; i < events.Num(); i++) {
			delete events[i];
		}
		delete cameraPosition;
		cameraPosition = NULL;
		events.Clear();
		targetPositions.Clear();
	}

	idCameraPosition *startNewCamera(idCameraPosition::positionType type) {
		clear();
		if (type == idCameraPosition::SPLINE) {
			cameraPosition = new idSplinePosition();
		} else if (type == idCameraPosition::INTERPOLATED) {
			cameraPosition = new idInterpolatedPosition();
		} else {
			cameraPosition = new idFixedPosition();
		}
		return cameraPosition;
	}

	idCameraDef() {
		clear();
	}

	~idCameraDef() {
		clear();
	}

	void addEvent(idCameraEvent::eventType t, const char *param, long time);

	void addEvent(idCameraEvent *event);

	static int sortEvents(const void *p1, const void *p2);

	int numEvents() {
		return events.Num();
	}

	idCameraEvent *getEvent(int index) {
		assert(index >= 0 && index < events.Num());
		return events[index];
	}

	void parse(const char *(*text));
	qboolean load(const char *filename);
	void save(const char *filename);

	void buildCamera();

	//idSplineList *getcameraPosition() {
	//	return &cameraPosition;
	//}

	static idCameraPosition *newFromType(idCameraPosition::positionType t) {
		switch (t) {
			case idCameraPosition::FIXED : return new idFixedPosition();
			case idCameraPosition::INTERPOLATED : return new idInterpolatedPosition();
    case idCameraPosition::SPLINE : return new idSplinePosition();
    default:
        break;
		};
		return NULL;
	}

	void addTarget(const char *name, idCameraPosition::positionType type);

	idCameraPosition *getActiveTarget() {
		if (targetPositions.Num() == 0) {
			addTarget(NULL, idCameraPosition::FIXED);
		}
		return targetPositions[activeTarget];
	}

	idCameraPosition *getActiveTarget(int index) {
		if (targetPositions.Num() == 0) {
			addTarget(NULL, idCameraPosition::FIXED);
			return targetPositions[0];
		}
		return targetPositions[index];
	}

	int numTargets() {
		return targetPositions.Num();
	}


	void setActiveTargetByName(const char *name) {
		for (int i = 0; i < targetPositions.Num(); i++) {
			if (Q_stricmp(name, targetPositions[i]->getName()) == 0) {
				setActiveTarget(i);
				return;
			}
		}
	}

	void setActiveTarget(int index) {
		assert(index >= 0 && index < targetPositions.Num());
		activeTarget = index;
	}

	void setRunning(bool b) {
		cameraRunning = b;
	}

	void setBaseTime(float f) {
		baseTime = f;
	}

	float getBaseTime() {
		return baseTime;
	}

	float getTotalTime() {
		return totalTime;
	}
	
	void startCamera(long t);
	void stopCamera() {
		cameraRunning = true;
	}
	void getActiveSegmentInfo(int segment, idVec3_t &origin, idVec3_t &direction, float *fv);

	bool getCameraInfo(long time, idVec3_t &origin, idVec3_t &direction, float *fv);
	bool getCameraInfo(long time, float *origin, float *direction, float *fv) {
		idVec3_t org, dir;
		org[0] = origin[0];
		org[1] = origin[1];
		org[2] = origin[2];
		dir[0] = direction[0];
		dir[1] = direction[1];
		dir[2] = direction[2];
		bool b = getCameraInfo(time, org, dir, fv);
		origin[0] = org[0];
		origin[1] = org[1];
		origin[2] = org[2];
		direction[0] = dir[0];
		direction[1] = dir[1];
		direction[2] = dir[2];
		return b;
	}

	void draw(bool editMode) {
                // gcc doesn't allow casting away from bools
                // why?  I've no idea...
		if (cameraPosition) {
			cameraPosition->draw((bool)((editMode || cameraRunning) && cameraEdit));
			int count = targetPositions.Num();
			for (int i = 0; i < count; i++) {
				targetPositions[i]->draw((bool)((editMode || cameraRunning) && i == activeTarget && !cameraEdit));
			}
		}
	}

/*
	int numSegments() {
		if (cameraEdit) {
			return cameraPosition.numSegments();
		}
		return getTargetSpline()->numSegments();
	}

	int getActiveSegment() {
		if (cameraEdit) {
			return cameraPosition.getActiveSegment();
		}
		return getTargetSpline()->getActiveSegment();
	}

	void setActiveSegment(int i) {
		if (cameraEdit) {
			cameraPosition.setActiveSegment(i);
		} else {
			getTargetSpline()->setActiveSegment(i);
		}
	}
*/
	int numPoints() {
		if (cameraEdit) {
			return cameraPosition->numPoints();
		}
		return getActiveTarget()->numPoints();
	}

	const idVec3_t *getPoint(int index) {
		if (cameraEdit) {
			return cameraPosition->getPoint(index);
		}
		return getActiveTarget()->getPoint(index);
	}

	void stopEdit() {
		editMode = false;
		if (cameraEdit) {
			cameraPosition->stopEdit();
		} else {
			getActiveTarget()->stopEdit();
		}
	}

	void startEdit(bool camera) {
		cameraEdit = camera;
		if (camera) {
			cameraPosition->startEdit();
			for (int i = 0; i < targetPositions.Num(); i++) {
				targetPositions[i]->stopEdit();
			}
		} else {
			getActiveTarget()->startEdit();
			cameraPosition->stopEdit();
		}
		editMode = true;
	}

	bool waitEvent(int index);

	const char *getName() {
		return name.c_str();
	}

	void setName(const char *p) {
		name = p;
	}

	idCameraPosition *getPositionObj() {
		if (cameraPosition == NULL) {
			cameraPosition = new idFixedPosition();
		}
		return cameraPosition;
	}

protected:
	idStr name;
	int currentCameraPosition;
	idVec3_t lastDirection;
	bool cameraRunning;
	idCameraPosition *cameraPosition;
	idList<idCameraPosition*> targetPositions;
	idList<idCameraEvent*> events;
	idCameraFOV fov;
	int activeTarget;
	float totalTime;
	float baseTime;
	long startTime;

	bool cameraEdit;
	bool editMode;
};

extern bool g_splineMode;

extern idCameraDef *g_splineList;


#endif
