// K-3D
// Copyright (c) 1995-2004, Timothy M. Shead
//
// Contact: tshead@k-3d.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

/** \file
		\brief Implements the atom object
		\author Tim Shead (tshead@k-3d.com)
		\author Ed Millard (emillard@direcway.com)
*/

#include <k3dsdk/algebra.h>
#include <k3dsdk/bounded.h>
#include <k3dsdk/high_res_timer.h>
#include <k3dsdk/iapplication.h>
#include <k3dsdk/idag.h>
#include <k3dsdk/imaterial.h>
#include <k3dsdk/persistence.h>
#include <k3dsdk/material_collection.h>
#include <k3dsdk/measurement.h>
#include <k3dsdk/module.h>
#include <k3dsdk/object.h>
#include <k3dsdk/renderman.h>
#include <k3dsdk/transformable.h>
#include <k3dsdk/viewport.h>

#include <sdpgl/sdpgl.h>

#include <boost/scoped_ptr.hpp>

namespace
{

/////////////////////////////////////////////////////////////////////////////
// atom_implementation

class atom_implementation :
	public k3d::bounded<k3d::material_collection<k3d::viewport::drawable<k3d::ri::renderable<k3d::transformable<k3d::persistent<k3d::object> > > > > >
{
	typedef k3d::bounded<k3d::material_collection<k3d::viewport::drawable<k3d::ri::renderable<k3d::transformable<k3d::persistent<k3d::object> > > > > > base;

public:
	atom_implementation(k3d::idocument& Document) :
		base(Document),
		m_radius(k3d::init_name("radius") + k3d::init_description("Radius [number]") + k3d::init_value(5.0) + k3d::init_document(Document) + k3d::init_precision(2) + k3d::init_step_increment(0.1) + k3d::init_units(typeid(k3d::measurement::distance)))
	{
		enable_serialization(k3d::persistence::proxy(m_radius));

		register_property(m_radius);
		
		m_position.changed_signal().connect(SigC::slot(*this, &atom_implementation::async_redraw_all));
		m_orientation.changed_signal().connect(SigC::slot(*this, &atom_implementation::async_redraw_all));
		m_scale.changed_signal().connect(SigC::slot(*this, &atom_implementation::async_redraw_all));
	}

	const k3d::bounding_box extents()
	{
		const double radius = m_radius.property_value();
		return k3d::bounding_box(radius, -radius, radius, -radius, radius, -radius);
	}

	void on_viewport_draw(const k3d::viewport::render_state& State)
	{
		if(is_selected())
			{
				glPolygonOffset(1.0, 1.0);
				glEnable(GL_POLYGON_OFFSET_FILL);
			}
			
		glEnable(GL_LIGHTING);
		glDisable(GL_TEXTURE_1D);
		glDisable(GL_TEXTURE_2D);

		const double radius = m_radius.property_value();
		const double zmin = -1.0;
		const double zmax = 1.0;

		glPolygonMode(GL_FRONT, GL_FILL);
		glCullFace(GL_BACK);
		glEnable(GL_CULL_FACE);

		// Setup material ...
		k3d::viewport::setup_material(m_material.object());

		const unsigned long nsides = 16;
		const unsigned long nlevels = 8;

		const double zminangle = k3d::pi() / 2.0 - acos(-zmin);
		const double zmaxangle = k3d::pi() / 2.0 - acos(zmax);
		const double zstep = (zminangle + zmaxangle) / static_cast<double>(nlevels);
		const double start = 0.0;
		const double step  = 360.0 / static_cast<double>(nsides);

		const int kVertexElements = 3; 
		if(nsides != fsSides || start != fsStart || step != fsStep) {
			if(fsVertexArray != NULL) {
				free(fsVertexArray);
			}
			fsVertexArray = (float*)malloc((nsides+1) * nlevels * sizeof(float) * kVertexElements);
			fsSides = nsides;

			float* vertex = fsVertexArray;
			for(unsigned long j = 1; j < nlevels; j++) {
				const float radius1 = sin(zstep * static_cast<double>(j));
				const float height1 = cos(zstep * static_cast<double>(j));
				float current = start;
				for(unsigned long i = 0; i < nsides+1; i++) {
					k3d::vector3 v(radius1 * cos(k3d::radians(current)),
									height1,
									radius1 * sin(k3d::radians(current)));
					vertex[0] = static_cast<float>(v[0]);
					vertex[1] = static_cast<float>(v[1]);
					vertex[2] = static_cast<float>(v[2]);
					vertex += kVertexElements;

					current += step;
				}
			}
			fsStart = start;
			fsStep  = step;
		}

		glPushMatrix();
		glScaled(radius, radius, radius);

		// Top cap
		float* vertex = fsVertexArray;
		vertex = &vertex[nsides * kVertexElements];
		glBegin(GL_TRIANGLE_FAN);
		glNormal3f(0.0f, 1.0f, 0.0f);
		glVertex3f(0.0f, 1.0f, 0.0f);
		for(unsigned long i = 0; i < nsides+1; i++) {
			glNormal3fv(vertex); 
			glVertex3fv(vertex);
			vertex -= kVertexElements;
		}
		glEnd();

		// Mid
		vertex = fsVertexArray;
		for(unsigned long j = 1; j < nlevels-1; j++) {
			float* vertex2 = &vertex[(nsides+1) * kVertexElements];
			glBegin(GL_TRIANGLE_STRIP);
			for(unsigned long i = 0; i < nsides+1; i++) {
				glNormal3fv(vertex2);
				glVertex3fv(vertex2);
				vertex2 += kVertexElements;

				glNormal3fv(vertex); 
				glVertex3fv(vertex);
				vertex += kVertexElements;
			}
			glEnd();
		}

		// Bottom cap
		vertex = &fsVertexArray[(((nsides+1) * kVertexElements) * (nlevels-2))];
		glBegin(GL_TRIANGLE_FAN);
		glNormal3f(0.0f, -1.0f, 0.0f);
		glVertex3f(0.0f, -1.0f, 0.0f);
		for(unsigned long i = 0; i < nsides+1; i++) {
			glNormal3fv(vertex);
			glVertex3fv(vertex);
			vertex += kVertexElements;
		}
		glEnd();

		if(selected)
			{
				const unsigned long nsides = 16;
				const unsigned long nlevels = 8;

				// Lattitudinal lines
				const int kVertexElements = 3; 
				float* vertex = fsVertexArray;
				for(unsigned long j = 0; j < nlevels; j++) {
					glBegin(GL_LINE_STRIP);
					for(unsigned long i = 0; i <= nsides; i++) {
						glVertex3fv(vertex);
						vertex += kVertexElements;
					}
					glEnd();
				}

				// Longitudinal lines
				int offset = (nsides+1) * kVertexElements;
				for(unsigned long i = 0; i < nsides; i++) {
					float* vertex = &fsVertexArray[i * kVertexElements];
					glBegin(GL_LINE_STRIP);
					glVertex3f(0.0f, 1.0f, 0.0f);
					for(unsigned long j = 1; j <= nlevels-1; j++) {
						glVertex3fv(vertex);
						vertex += offset;
					}
					glVertex3f(0.0f, -1.0f, 0.0f);
					glEnd();
				}
				
				glDisable(GL_POLYGON_OFFSET_FILL);
			}
		glPopMatrix();
	}
	
	void on_viewport_select(const k3d::viewport::render_state& State)
	{
		std::cerr << debug << __PRETTY_FUNCTION__ << " - implement this" << std::endl;
	}

	void on_renderman_render(const k3d::ri::render_state& State)
	{
		const double radius = m_radius.property_value();
		const double zmin = -1.0;
		const double zmax = 1.0;
		const double sweepangle = 360.0;

		State.engine.RiTransformBegin();
		State.engine.RiConcatTransform(k3d::ri::convert(k3d::rotation3D(k3d::radians(-90.0), k3d::vector3(1, 0, 0))));
		k3d::ri::setup_material(m_material.object(), State);
		State.engine.RiSphereV(radius, radius * zmin, radius * zmax, sweepangle);
		State.engine.RiTransformEnd();
	}

	k3d::iplugin_factory& factory()
	{
		return get_factory();
	}

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<atom_implementation>,
				k3d::interface_list<k3d::itransform_source,
				k3d::interface_list<k3d::itransform_sink > > > factory(
				k3d::uuid(0x45588899, 0x635d46d4, 0xa5aef51d, 0x0c4d019a),
				"Atom",
				"Atom primitive",
				"Objects",
				k3d::iplugin_factory::EXPERIMENTAL);

		return factory;
	}

private:
	friend class properties;

	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_radius;

private:
	static unsigned long    fsSides;
	static double           fsStart;
	static double           fsStep;
	static float*		    fsVertexArray;
};

unsigned long	atom_implementation::fsSides = 0;
double			atom_implementation::fsStart = 0.0;
double			atom_implementation::fsStep = 0.0;
float*			atom_implementation::fsVertexArray = NULL;

} // namespace

namespace libk3dchem
{

/////////////////////////////////////////////////////////////////////////////
// atom_factory

k3d::iplugin_factory& atom_factory()
{
	return atom_implementation::get_factory();
}

} // namespace libk3dchem
