/*-
 * 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: skin.cpp,v 1.11 2000/05/26 11:05:28 dfr Exp $
 */

#include <iostream>
#include <string.h>
#include <vector>
#include "maths.h"
#include "draw.h"
#include "utilsout.h"
#include "event.h"
#include "primgen.h"
#include "primbuilder.h"
#include "image.h"

using namespace std;
using namespace Draw;
using namespace Image;

PrimBuilder PrimBuilder::primitive;

static Draw::Texture*
loadTexture(Renderer* renderer, const char* name)
{
    bool imageOk = true;
    Draw::Texture* texture;

    unsigned int width;
    unsigned int height;
    unsigned int maxdim;
    
    Pixmap* image = Pixmap::loadBMP(name, Format::RGB888, 8);
    if (!image)
	return 0;

    width = image->extent().x;
    height = image->extent().y;

    maxdim = width;
    if (height > width)
	maxdim = height;

    if (width != height)
	imageOk = false;

    unsigned int levels = 0;
    for (levels = 0; (1U << levels) < maxdim; levels++);

    if (maxdim != (1U << levels))
	imageOk = false;

    if (!imageOk) {
	/*
	 * Reformat the image to a square power of two image.
	 */
	if (levels > 8) levels = 8;
	maxdim = 1U << levels;
	Pixmap* scale = new Pixmap(maxdim, maxdim, image->extent().z,
				   Format::RGB888, 8);
	
	copy(*image, Box2ui(0, 0, width, height),
	     *scale, Box2ui(0, 0, maxdim, maxdim));
	width = height = maxdim;
	delete image;
	image = scale;
    }

    texture = renderer->createTexture(levels + 1, image->extent().z);
	
    for (unsigned int i = levels; i != 0; i--) {
	texture->loadLevel(i, (unsigned char*) image->address(0, 0));
	    
	if (i > 0) {
	    Pixmap* scale = new Pixmap(width / 2,
				       height / 2,
				       image->extent().z,
				       Format::RGB888,
				       8);
	    copy(*image, Box2ui(0, 0, width, height),
		 *scale, Box2ui(0, 0, width/2, height/2));
	    width /= 2;
	    height /= 2;
	    delete image;
	    image = scale;
	}
    }
    texture->loadLevel(0, (unsigned char*) image->address(0, 0));
    delete image;
    
    return texture;
}

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 performTest()
{
    try {
	Factory* f = Factory::create();
	Renderer* r = f->createRenderer(NULL, "Skin", NULL, NULL,
					640, 480, false);
	PrimBuilder::primitive.factory = f;
	
	Buffer* buffer = r->backBuffer();
	Draw::Texture* diffuse;
	Draw::Texture* specular;
	Draw::Shader* simple;
	Draw::Shader* spectex;
	Draw::Shader* shader;
	LightManager* lightman;
	unsigned int lc = 2;

	diffuse = loadTexture(r, "diff.bmp");
	specular = loadTexture(r, "spec.bmp");
	
	simple = r->createShader(diffuse);
	spectex = r->createShader();
	spectex->setStage(0, ShaderStage(ShaderStage::PrimaryColor,
					 diffuse,
					 Blend::One,
					 Blend::Zero));
	spectex->setStage(1, ShaderStage(ShaderStage::SecondaryColor,
					 specular,
					 Blend::One,
					 Blend::One));
	shader = spectex;

	lightman = r->createLightManager();
	lightman->reserve(2);

	Light lights[2];
	lights[0].point(Vec3f(0.5, 0.5, -2));
	lights[0].color = Vec3f(1.0f, 1.0f, 1.0f);
	lights[1].point(Vec3f(-0.5, -0.5, -2));
	lights[1].color = Vec3f(1.0f, 1.0f, 1.0f);
	lightman->set(lights, 2);
	
	lightman->setAmbient(Vec3f(0.0f, 0.0f, 0.0f));
	lightman->setOptions(LightManager::ModulateAmbient |
			     LightManager::ModulateDiffuse |
			     LightManager::LocalViewer);

	setViewport(buffer);
	setCamera(buffer);

	Matrix4f m;
	m.setIdentity();
#if 0
	m[0][0] = 0.001;
	m[1][1] = 1.0;
	m.flags = m.Scale;
#endif

	Matrix4f rotation;
	rotation.setIdentity();
	rotation.setRotation(Vec3f(0, 1, 0), 0.05f);
	
	PrimBuilder::primitive.buildPrim(1);

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

	    PrimBuilder::primitive.render(buffer, shader, lightman);
	    
	    r->update();

	    buffer->popMatrix(Buffer::ModelMatrix);

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

		case Draw::Event::KeyPress:
		    switch (e.key) {
		    case Key::q:
			r->shutdown();
			break;
		    case Key::a:
			if (shader == simple)
			    shader = spectex;
			else
			    shader = simple;
			break;
		    case Key::l:
			lc = 3 - lc;
			lightman->set(lights, lc);
			break;
		    case Key::n:
			lightman->setOptions
			    (lightman->options() ^ LightManager::Renormalize);
			break;
		    case Key::s:
			PrimBuilder::primitive.buildPrim(0);
			break;
		    case Key::c:
			PrimBuilder::primitive.buildPrim(1);
			break;
		    case Key::f:
			PrimBuilder::primitive.buildPrim(2);
			break;
		    case Key::w:
		    {
			SphereTextureMapper& m = PrimBuilder::primitive.mapper;
			m.orientation(m.orientation() + 0.1f);
			PrimBuilder::primitive.reBuild();
			break;
		    }
		    case Key::e:
		    {
			SphereTextureMapper& m = PrimBuilder::primitive.mapper;
			m.orientation(m.orientation() - 0.1f);
			PrimBuilder::primitive.reBuild();
			break;
		    }
		    case Key::r:
		    {
			SphereTextureMapper& m = PrimBuilder::primitive.mapper;
			m.uScale(m.uScale() + 1);
			PrimBuilder::primitive.reBuild();
			break;
		    }
		    case Key::t:
		    {
			SphereTextureMapper& m = PrimBuilder::primitive.mapper;
			m.uScale(m.uScale() - 1);
			PrimBuilder::primitive.reBuild();
			break;
		    }
		    }
		    break;
		case Draw::Event::MousePress:
		    drag = true;
		    break;

		case Draw::Event::MouseRelease:
		    drag = false;
		    break;
		    
		case Draw::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;
	    }
	}

	delete f;
    }
    catch (const Utils::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
