/*-
 * Copyright (c) 1999,2000 Qube Software, Ltd.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 * 1. Redistributions of the source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Any redistribution solely in binary form must conspicuously
 *    reproduce the following disclaimer in documentation provided with the
 *    binary redistribution.
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'', WITHOUT ANY WARRANTIES, EXPRESS
 * OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  LICENSOR SHALL
 * NOT BE LIABLE FOR ANY LOSS OR DAMAGES RESULTING FROM THE USE OF THIS
 * SOFTWARE, EITHER ALONE OR IN COMBINATION WITH ANY OTHER SOFTWARE.
 * 
 * 	$Id: parray.cpp,v 1.31 2000/05/26 11:05:27 dfr Exp $
 */

#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include "maths.h"
#include "draw.h"
#include "event.h"
#include "utilsout.h"

using namespace std;
using namespace Draw;
using namespace Utils;
using namespace Maths;

struct Vertex {
    Vec3f		coord;
    Vec3f		normal;
    unsigned int	color;
    Vertex(float x, float y, float z,
	   float nx, float ny, float nz,
	   unsigned int c)
	: coord(x, y, z),
	  normal(nx, ny, nz),
	  color(c) {}
};
#define vertexType (FVF::Coord|FVF::Normal|FVF::MaterialColor)

static PrimitiveArray*
makePA(Factory* f, PrimitiveArray::Type type)
{
    PrimitiveArray* pa = 0;
    unsigned int i;

    switch (type) {
    case PrimitiveArray::Points:
	pa = f->createPrimitiveArray(type, 200);
	pa->lock();
	for (i = 0; i < 200; i++) {
	    PrimitiveArray::Element elt = i;
	    pa->set(i, &elt);
	}
	pa->unlock();
	break;

    case PrimitiveArray::Lines:
	pa = f->createPrimitiveArray(type, 101);
	pa->lock();
	for (i = 0; i < 101; i++) {
	    PrimitiveArray::Element line[2];
	    line[0] = 2*i;
	    line[1] = 2*i + 1;
	    pa->set(i, line);
	}
	pa->unlock();
	break;

    case PrimitiveArray::Triangles:
	pa = f->createPrimitiveArray(type, 200);
	pa->lock();
	for (i = 0; i < 100; i++) {
	    PrimitiveArray::Element tri[3];
	    tri[0] = 2*i;
	    tri[1] = 2*i + 1;
	    tri[2] = 2*i + 3;
	    pa->set(2*i, tri);
	    tri[0] = 2*i;
	    tri[1] = 2*i + 3;
	    tri[2] = 2*i + 2;
	    pa->set(2*i+1, tri);
	}
	pa->unlock();
	break;

    case PrimitiveArray::Quads:
	pa = f->createPrimitiveArray(type, 100);
	pa->lock();
	for (i = 0; i < 100; i++) {
	    PrimitiveArray::Element quad[4];
	    quad[0] = 2*i;
	    quad[1] = 2*i + 1;
	    quad[2] = 2*i + 3;
	    quad[3] = 2*i + 2;
	    pa->set(i, quad);
	}
	pa->unlock();
	break;

    case PrimitiveArray::LineStrip:
	pa = f->createPrimitiveArray(type, 202);
	pa->lock();
	pa->at(0)[-1] = 0;
	for (i = 1; i < 202; i++) {
	    PrimitiveArray::Element elt = i;
	    pa->set(i, &elt);
	}
	pa->unlock();
	break;

    case PrimitiveArray::TriStrip:
	pa = f->createPrimitiveArray(type, 200);
	pa->lock();
	pa->at(0)[-2] = 0;
	pa->at(0)[-1] = 1;
	for (i = 2; i < 202; i++) {
	    PrimitiveArray::Element elt = i;
	    pa->set(i-2, &elt);
	}
	pa->unlock();
	break;

    case PrimitiveArray::TriFan:
	pa = f->createPrimitiveArray(type, 100);
	pa->lock();
	pa->at(0)[-2] = 0;
	pa->at(0)[-1] = 2;
	for (i = 2; i < 101; i++) {
	    PrimitiveArray::Element elt = 2*i;
	    pa->set(i-2, &elt);
	}
	pa->unlock();
	break;

    case PrimitiveArray::QuadStrip:
	pa = f->createPrimitiveArray(type, 100);
	pa->lock();
	pa->at(0)[-2] = 0;
	pa->at(0)[-1] = 1;
	for (i = 1; i < 101; i++) {
	    PrimitiveArray::Element elt[2];
	    elt[0] = 2*i;
	    elt[1] = 2*i+1;
	    pa->set(i-1, elt);
	}
	pa->unlock();
	break;
    }

    return pa;
}

Color4b
colorFromAngle(float angle)
{
    int h, s, v;

    h = (int) (angle * 180 / Maths::PI);
    s = 255;
    v = 255;

    int r=255, g=255, b=255;

    unsigned int f = h%60;
    h /= 60;
    unsigned int p = 0;
    unsigned int q, t;
    if ( h&1 ) {
	q = (2*255*(15300-255*f)+15300)/30600;
	switch( h ) {
	case 1: r=q; g=255, b=p; break;
	case 3: r=p; g=q, b=255; break;
	case 5: r=255; g=p, b=q; break;
	}
    } else {
	t = (2*255*(15300-(255*(60-f)))+15300)/30600;
	switch( h ) {
	case 0: r=255; g=t, b=p; break;
	case 2: r=p; g=255, b=t; break;
	case 4: r=t; g=p, b=255; break;
	}
    }
    return Color4b(r, g, b);
}

static VertexArray*
makeVA(Factory* f)
{
    VertexArray* va = f->createVertexArray(vertexType, 202);
    va->lock();
    for (unsigned int i = 0; i < 200; i++) {
	float angle = (i / 2) * (2 * Maths::PI / 100);
	float x, y, z;
	x = cosf(angle);
	z = sinf(angle);
	y = (i & 1) ? 0.05f : -0.05f;
	Vertex v(x, y, z, x, 0, z, colorFromAngle(angle));
	va->set(i, &v);
    }
    va->set(200, va->at(0));
    va->set(201, va->at(1));
    va->unlock();

    return va;
}

static bool
pickPrimitive(Buffer* buffer,
              Box2f& box,
              float front,
              float back,
              Vec2f& pos,
              PrimitiveArray* pa,
              VertexArray* va)
{
    Vec2f extent = buffer->extent();
    Vec2f centre;

    centre.x = extent.x / 2;
    centre.y = extent.y / 2;

    Matrix4f m = buffer->worldToEyeMatrix();
    Matrix4f win = buffer->projectionToWindowMatrix();

    /*
     * Calculate a vector in the projection plane from the
     * window coordinate passed in.
     * First transform from window to normalised (-1..1) box, then
     * adjust for dimensions of viewing plane (inverting projection).
     */
    Vec3f p(pos.x, pos.y, front);
    p = p.inverseTransform(win);
    p.x = (p.x * (box.max.x - box.min.x) + (box.max.x + box.min.x)) / 2.0f;
    p.y = (p.y * (box.max.y - box.min.y) + (box.max.y + box.min.y)) / 2.0f;

    /*
     * Transform back to world coordinates by inverting the eye matrix.
     * Construct a ray in world coordinates from the eye through the position
     * on the viewing plane.
     */
    p = p.inverseTransform(m);
    Vec3f direction(p - buffer->worldEye());
    direction.normalize();
    Rayf ray(buffer->worldEye(), direction);

    /*
     * Finally, transform the ray back into model coordinates by inverting
     * the modelling matrix.
     */
    ray = ray.inverseTransform(buffer->matrix(Buffer::ModelMatrix));

    float hitDistance = back;
    unsigned int hitIndex;

    Factory* f = pa->factory();

    VertexArray* hitVertexArray = f->createVertexArray(vertexType, 1);

    hitVertexArray->lock();

    void* hitVertex = hitVertexArray->at(0);

    bool hit = pa->rayIntersect(va,
                                ray,
                                0,
                                pa->size(),
                                hitIndex,
                                hitDistance,
                                vertexType,
                                hitVertex);

    hitVertexArray->unlock();

    return hit;
}

static Texture* makeTexture(Renderer* r)
{
#define B	128,128,128
#define W	255,255,255
#define G	192,192,192

    unsigned char checker3[] = {
	B,B,B,B,W,W,W,W,
	B,B,B,B,W,W,W,W,
	B,B,B,B,W,W,W,W,
	B,B,B,B,W,W,W,W,
	W,W,W,W,B,B,B,B,
	W,W,W,W,B,B,B,B,
	W,W,W,W,B,B,B,B,
	W,W,W,W,B,B,B,B,
    };
    unsigned char checker2[] = {
	B,B,W,W,
	B,B,W,W,
	W,W,B,B,
	W,W,B,B,
    };
    unsigned char checker1[] = {
	B,W,
	W,B,
    };
    unsigned char checker0[] = {
	G
    };

    Texture* tex = r->createTexture(4,24);
    tex->loadLevel(3, checker3);
    tex->loadLevel(2, checker2);
    tex->loadLevel(1, checker1);
    tex->loadLevel(0, checker0);
    return tex;

#undef B
#undef W
}

static void setCamera(Buffer* buffer)
{
    Matrix4f m;
    m.setIdentity();
    m.position(Vec3f(0.0f, 0.0f, -2.0f));
    buffer->setMatrix(Buffer::CameraMatrix, m);
}

static void
performTest()
{
    try {
	Factory* f = Factory::create();
	Renderer* r = f->createRenderer(NULL, "PrimitiveArray", NULL, NULL, 640, 480);
	VertexArray* va = makeVA(f);
	int mode = PrimitiveArray::Points;
	PrimitiveArray* pa = makePA(f, (PrimitiveArray::Type) mode);
	Buffer* buffer = r->backBuffer();
	Texture* checker;
	Shader* shader;
	LightManager* lightman;
	Vec2f lastPos;

	r->setBackgroundColor(Vec3f(0.7f, 0.7f, 0.7f));

	checker = makeTexture(r);

	shader = r->createShader(checker);

	lightman = r->createLightManager();
	Light light;
	//light.directional(Vec3f(1, 1, -1));
	light.point(Vec3f(1, 0, -2));
	lightman->set(&light, 1);
	lightman->setAmbient(Vec3f(0.3f, 0.3f, 0.3f));
	lightman->setOptions(LightManager::ModulateAmbient
			     |LightManager::ModulateDiffuse
			     |LightManager::LocalViewer);
	buffer->setLights(lightman);

        Box2f bufferBox(-0.5f, -0.5f, 0.5f, 0.5f);
        float frontPlane = 1.0f;
        float backPlane = 1000.0f;
        buffer->setFrustum(bufferBox, frontPlane, backPlane);

	setCamera(buffer);

	Matrix4f m;
	m.setIdentity();

	Matrix4f rotation;
	rotation.setIdentity();

	Material white;
	white.emissive = Vec3f(0, 0, 0);
	white.ambient = Vec3f(1, 1, 1);
	white.diffuse = Vec3f(1, 1, 1);
	white.specular = Vec3f(1, 1, 1);
	white.shininess = 10;
	white.brilliance = 1;
	buffer->setMaterial(white);
			  
	buffer->setShader(shader);
	buffer->setVertices(va);

	for (;;) {
	    m = m * rotation;
	    buffer->pushMatrix(Buffer::ModelMatrix, m);

	    buffer->render(pa, 0, pa->size());
	    r->update();

	    Event e;
	    if (r->nextEvent(e, false)) { // don't wait for an event
		static bool drag = false;
		
		switch (e.code) {
		case Event::WindowClose:
		    r->shutdown(); // start the process of shutting down
		    break;

		case Event::KeyPress:
		    switch (e.key) {
		    case Key::m:
			mode++;
			if (mode > PrimitiveArray::QuadStrip)
			    mode = PrimitiveArray::Points;
			delete pa;
			pa = makePA(f, (PrimitiveArray::Type)mode);
			break;

		    case Key::q:
			r->shutdown();
			break;

		    case Key::Left:
		    case Key::Right:
			// Do stuff
			break;
		    }
		    break;

		case Event::MousePress:
                    if (mode == PrimitiveArray::Points ||
			mode == PrimitiveArray::Lines ||
                        mode == PrimitiveArray::LineStrip) {
                        drag = true;
                    } else {
                        drag = false;
                        if (pickPrimitive(buffer, bufferBox,
                                          frontPlane,
                                          backPlane,
                                          lastPos,
                                          pa, va))
                            drag = true;
                    }                    
		    break;

		case Event::MouseRelease:
		    rotation.setIdentity();
		    drag = false;

		case Event::MousePosition:
		    {
			Vec2f pos = Vec2f((float)e.mousePosition.x,
					  (float)e.mousePosition.y);
			if (drag) {
			    Vec2f delta = pos - lastPos;
			    
			    float deltaR = delta.modulus();
			    float radius = 300;
			    float denom;

			    denom = sqrtf(radius * radius + deltaR * deltaR);

			    if (deltaR == 0 || denom == 0) break;

			    float angle = deltaR / denom;
			    rotation.setRotation(Vec3f(delta.y / deltaR,
						       delta.x / deltaR, 0),
						 -angle);
			}
			lastPos = pos;
		    }
		    break;

		default:
		    break;
		}
	    } else {
		// The render has shutdown and is ready to quit
		break;
	    }
	    buffer->popMatrix(Buffer::ModelMatrix);
	}

	delete f;
    }
    catch (const Problem& p) {
        cout << "Error: " << p << endl;
    }
}

int
main(int argc, char** argv)
{

    performTest();
    return 0;
}

#ifdef STATIC

/* Make sure the drivers we want are linked in. */

#ifdef q_windows
extern int QDriver_OS_Win32;
int* osref = &QDriver_OS_Win32;

extern int QDriver_Input_Win32;
int* inpref = &QDriver_Input_Win32;

extern int QDriver_Display_DX7;
int* displayref = &QDriver_Display_DX7;
#else    
extern int QDriver_OS_X11;
int* osref = &QDriver_OS_X11;

extern int QDriver_Display_OpenGL;
int* displayref = &QDriver_Display_OpenGL;
#endif

#endif // STATIC
