/*-
 * 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: triangle.cpp,v 1.51 2000/06/13 16:54:23 n_hibma Exp $
 */

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

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

bool doClip = false;
bool doWindow = false;
bool fullScreen = false;

static Texture* makeTexture(Renderer* r, Color4b fg, Color4b bg)
{
    Color4b t = interpolate(0.5f, fg, bg);
#define B	bg.red(),bg.green(),bg.blue()
#define W	fg.red(),fg.green(),fg.blue()
#define G	t.red(),t.green(),t.blue()

    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
#undef G
}

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, 3.0f));
    buffer->setMatrix(Buffer::CameraMatrix, m);
}

// Define a vertex format containing just a coordinate a color and
// a texture coordinate.
struct Vertex {
    Vec3f		coord;
    Vec3f		normal;
    unsigned int	color;
    Vec2f		tex;
    Vertex(float x, float y, float z,
	   float nx, float ny, float nz,
	   float u, float v, unsigned int c)
	: coord(x, y, z),
	  normal(nx, ny, nz),
	  color(c), tex(u, v) {}
};

static void drawTriangle(Factory* f, Renderer* r,
			 Buffer* buffer, Shader* shader,
			 LightManager* lightman)
{
    static Vertex vertexData[] = {
	//	x	y	z	nx	ny	nz	u	v	color
	Vertex(	-1.0f,  -1.0f,	0.0f,	0,	0,	-1,	0.0f,	0.0f,	0xffff0000),
	Vertex(	1.0f,   -1.0f,	0.0f,	0,	0,	-1,	4.0f,	0.0f,	0xff00ff00),
	Vertex(	0.0f,	1.0f,	0.0f,	0,	0,	-1,	2.0f,	4.0f,	0xff0000ff),

	Vertex(	-1.0f,  -1.0f,	0.0f,	0,	0,	1,	0.0f,	0.0f,	0xff00ffff),
	Vertex(	1.0f,   -1.0f,	0.0f,	0,	0,	1,	4.0f,	0.0f,	0xffff00ff),
	Vertex(	0.0f,	1.0f,	0.0f,	0,	0,	1,	2.0f,	4.0f,	0xffffff00),
    };

    static VertexArray* vertices = 0;

    if (!vertices) {
	vertices = f->createVertexArray(FVF::Coord
					|FVF::Normal
					|FVF::MaterialColor
					|FVF::TexCoord(1), 6);
	for (unsigned int i = 0; i < 6; 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;

    buffer->setMaterial(white);
    buffer->setVertices(vertices);
    buffer->setShader(shader);
    buffer->triangle(0, 2, 1);
    buffer->triangle(3, 4, 5);
}

#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;
#endif
#ifdef q_unix
extern int QDriver_OS_X11;
int* osref = &QDriver_OS_X11;

extern int QDriver_Display_OpenGL;
int* displayref = &QDriver_Display_OpenGL;

extern int QDriver_Input_Raw;
int* inputref = &QDriver_Input_Raw;
#endif
#ifdef q_ps2
extern int QDriver_Display_Ps2;
int* displayref = &QDriver_Display_Ps2;

extern int QDriver_Input_Null;
int* inputref = &QDriver_Input_Null;
#endif

#endif // STATIC

static void performTest()
{
    try {
	/*
	 * Only use a Ref for the factory since deleting the
	 * factory will clear up everything else.
	 */
	Ref<Factory> f = Factory::create();
	Renderer* r = f->createRenderer(NULL, "Triangle", NULL, NULL,
					640, 480, fullScreen);
	Buffer* buffer = r->backBuffer();
	Texture* diff;
	Texture* spec;
	Shader* shader;
	LightManager* lightman;

	r->setBackgroundColor(Vec3f(0.3f, 0.0f, 0.0f));
	buffer->setCoordinateSystem(Buffer::RightHanded);

	diff = makeTexture(r, Color4b(255,255,255), Color4b(128,128,128));
	spec = makeTexture(r, Color4b(255,255,255), Color4b(0, 0, 0));

	shader = r->createShader();
	shader->setStage(0, ShaderStage(ShaderStage::PrimaryColor,
					diff,
					Blend::One,
					Blend::Zero));
	shader->setStage(1, ShaderStage(ShaderStage::SecondaryColor,
					spec,
					Blend::One,
					Blend::One));

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

	setViewport(buffer);
	setCamera(buffer);

	Matrix4f rotation;
	rotation.setIdentity();
	rotation.setRotation(Vec3f(0.0f,1.0f,0.0f), 0.01f);

	buffer->setLights(lightman);
	for (;;) {
	    buffer->multiplyMatrix(Buffer::ModelMatrix, rotation);
	    if (doWindow) {
		buffer->pushWindow(Box2f(0, 0, 320, 240), true);
		drawTriangle(f, r, buffer, shader, lightman);
		buffer->popWindow();
		buffer->pushWindow(Box2f(320, 0, 640, 240), true);
		drawTriangle(f, r, buffer, shader, lightman);
		buffer->popWindow();
		buffer->pushWindow(Box2f(0, 240, 320, 480), true);
		drawTriangle(f, r, buffer, shader, lightman);
		buffer->popWindow();
		buffer->pushWindow(Box2f(320, 240, 640, 480), true);
		drawTriangle(f, r, buffer, shader, lightman);
		buffer->popWindow();
	    } else if (doClip) {
		buffer->pushClip(Box2f(10, 10, 310, 230));
		drawTriangle(f, r, buffer, shader, lightman);
		buffer->popWindow();
		buffer->pushClip(Box2f(330, 10, 630, 230));
		drawTriangle(f, r, buffer, shader, lightman);
		buffer->popWindow();
		buffer->pushClip(Box2f(10, 250, 310, 470));
		drawTriangle(f, r, buffer, shader, lightman);
		buffer->popWindow();
		buffer->pushClip(Box2f(330, 250, 630, 470));
		drawTriangle(f, r, buffer, shader, lightman);
		buffer->popWindow();
	    } else {
		drawTriangle(f, r, buffer, shader, lightman);
	    }
	    r->update();

	    Event e;
	    bool running;
	    running = r->nextEvent(e, false);
	    while (running && e.code != Draw::Event::None) {
		switch (e.code) {
		case Event::WindowClose:
		    r->shutdown();
		    break;

		case Event::KeyPress:
		    switch (e.key) {
		    case Key::q:
			r->shutdown();
			break;
		    case Key::u:
			r->resize(320, 240);
			r->backBuffer()->setWindow(Box2f(0, 0, 320.0f, 240.0f),
						   true);
			r->setBackgroundColor(Vec3f(1, 0, 0));
			break;
		    case Key::i:
			r->resize(640, 480);
			r->backBuffer()->setWindow(Box2f(0, 0, 640.0f, 480.0f),
						   true);
			r->setBackgroundColor(Vec3f(1, 0, 0));
			break;
		    case Key::o:
			r->resize(800, 600);
			r->backBuffer()->setWindow(Box2f(0, 0, 800.0f, 600.0f),
						   true);
			r->setBackgroundColor(Vec3f(0, 1, 0));
			break;
		    case Key::p:
			r->resize(1024, 768);
			r->backBuffer()->setWindow(Box2f(0, 0,
							 1024.0f, 768.0f),
						   true);
			r->setBackgroundColor(Vec3f(0, 0, 1));
			break;
		    case Key::s:
			r->fullscreen(true);
			break;
		    case Key::d:
			r->fullscreen(false);
			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;
		default:
		    break;
		}
		running = r->nextEvent(e, false);
	    }
	    if (!running)
		break;
	}
    }
    catch (const Problem& p) {
        cout << "Error: " << p << endl;
    }
}

int
main(int argc, char** argv)
{
    if (argc == 2 && !strcmp(argv[1], "-clip"))
	doClip = true;

    if (argc == 2 && !strcmp(argv[1], "-window"))
	doWindow = true;

    if (argc == 2 && !strcmp(argv[1], "-fs"))
	fullScreen = true;
    
    performTest();
    return 0;
}
