/*-
 * 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: windowed.cpp,v 1.15 2000/06/18 17:25:31 n_hibma Exp $
 */

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

#include "windowed.h"

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

/*
 * The interface to the other file, should be put in a header file
 */
unsigned long lock_ = 0;
bool quit_ = false;
bool resize_ = false;
int width_, height_;



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 setViewport(Buffer* buffer)
{
    buffer->setFrustum(Box2f(-0.5f, -0.5f, 0.5f, 0.5f), 1.0f, 1000.0f);
}

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

static void drawSide(Factory* f, Renderer* r,
		     Buffer* buffer, Shader* shader,
		     LightManager* lightman,
		     unsigned int i0,
		     unsigned int i1,
		     unsigned int i2)
{
    static Vec3f v[] = {
	Vec3f(-1, -0.5, -0.5),
	Vec3f(0, -0.5, sqrtf(3) - 0.5f),
	Vec3f(1, -0.5, -0.5),
	Vec3f(0, 0.5f * (sqrtf(11) - 1), 0)
    };

    Vec3f& v0 = v[i0];
    Vec3f& v1 = v[i1];
    Vec3f& v2 = v[i2];
    Vec3f n;

    static unsigned int colors[] = {
	0xffff0000,
	0xff00ff00,
	0xff0000ff,
	0xff707070
    };
    
    n = cross(v1 - v0, v2 - v0);
    n.normalize();

    struct Vertex {
	Vec3f		coord;
	Vec3f		normal;
	unsigned int	color;
	Vec2f		tex;
	Vertex(const Vec3f& c,
	       const Vec3f& n,
	       float u, float v, unsigned int col)
	    : coord(c), normal(n), color(col), tex(u, v) {}
    };

    Vertex vertexData[] = {
	//     coord	norm	u	v	color
	Vertex(v0,	n,	0.0f,	0.0f,	colors[i0]),
	Vertex(v1,	n,	2.0f,	4.0f,	colors[i1]),
	Vertex(v2,	n,	4.0f,	0.0f,	colors[i2]),
    };

    static VertexArray* vertices = 0;

    if (!vertices)
	vertices = f->createVertexArray(FVF::Coord
					|FVF::Normal
					|FVF::MaterialColor
					|FVF::TexCoord(1), 3, true);

    for (unsigned int i = 0; i < 3; i++)
	vertices->set(i, &vertexData[i]);

    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;

    lightman->setOptions(LightManager::ModulateAmbient
			 |LightManager::ModulateDiffuse
			 |LightManager::LocalViewer);

    buffer->setLights(lightman);
    buffer->setMaterial(white);
    buffer->setVertices(vertices);

    buffer->setShader(shader);
    buffer->triangle(0, 2, 1);
    buffer->triangle(0, 1, 2);
}

static void drawTetra(Factory* f, Renderer* r, Buffer* buffer,
		      Shader* shader, LightManager* lightman)
{
    drawSide(f, r, buffer, shader, lightman, 0, 3, 2);
    drawSide(f, r, buffer, shader, lightman, 0, 1, 3);
    drawSide(f, r, buffer, shader, lightman, 1, 2, 3);
    drawSide(f, r, buffer, shader, lightman, 0, 2, 1);
}

void draw3D(Draw::WindowInfo *windowInfo)
{
    try {
	Factory* f = Factory::create();
	Renderer* r = f->createRenderer(windowInfo,
				        NULL, NULL, NULL,
					RENDERER_WIDTH, RENDERER_HEIGHT);
	Buffer* buffer = r->backBuffer();
	Texture* checker;
	Shader* shader;
	LightManager* lightman;

	checker = makeTexture(r);

	buffer->setCoordinateSystem(Buffer::RightHanded);
	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));

	setViewport(buffer);
	setCamera(buffer);

	Matrix4f m;
	m.setIdentity();

	Matrix4f rotation;
	rotation.setIdentity();

	bool running = true;
	while (running) {
	    pollWindowEvents();

	    static unsigned int width = width_, height = height_;

	    QIF_EnterMutex(lock_);
	    if (quit_)
		r->shutdown();

	    if (resize_) {
		r->resize(width_, height_);
		r->backBuffer()->setWindow(Box2f(0, 0, width_, height_), true);
		width = width_;
		height = height_;
		resize_ = false;
	    }
	    QIF_ExitMutex(lock_);
	    
	    m = m * rotation;
	    buffer->pushMatrix(Buffer::ModelMatrix, m);

	    drawTetra(f, r, buffer, shader, lightman);
	    r->update();

	    buffer->popMatrix(Buffer::ModelMatrix);

	    Event e;
	    while ((running = r->nextEvent(e, false)) && e.code != Event::None) {
		static bool drag = false;
		static Vec2f lastPos;
		
		switch (e.code) {
		case Event::WindowClose:
		    r->shutdown(); // start the process of shutting down
		    break;

		case Event::KeyPress:
		    switch (e.key) {
		    case Key::q:
			r->shutdown();
			break;
		    }
		    break;

		case Event::WindowFullscreen:
		    r->fullscreen(e.fullscreen);
		    break;
		case Event::WindowSize:
		    r->resize(e.windowSize.width, e.windowSize.height);
		    r->backBuffer()->setWindow(Box2f(0, 0,
						     float(e.windowSize.width),
						     float(e.windowSize.height)),
					       true);
		    break;

		case Event::MousePress:
		    drag = true;
		    break;

		case Event::MouseRelease:
		    rotation.setIdentity();
		    drag = false;
		    break;
		    
		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;
		}
	    }
	}

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

