/**************************************************************************
 * solarsystem.cxx
 * Written by Durk Talsma. Originally started October 1997, for distribution  
 * with the FlightGear project. Version 2 was written in August and 
 * September 1998. This code is based upon algorithms and data kindly 
 * provided by Mr. Paul Schlyter. (pausch@saaf.se). 
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id$
 * (Log is kept at end of this file)
 **************************************************************************/

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#ifdef HAVE_WINDOWS_H
#  include <windows.h>
#endif


#ifdef __BORLANDC__
#  define exception c_exception
#endif
#include <math.h>

#include <GL/glut.h>
#include <XGL/xgl.h>
#include <Debug/logstream.hxx>
#include <Time/sunpos.hxx>
#include "solarsystem.hxx"

/***************************************************************************
 * default constructor for class  SolarSystem:   
 * or course there can only be one way to create an entire solar system -:) )
 * the fgTIME argument is needed to properly initialize the the current orbital
 * elements
 *************************************************************************/
SolarSystem::SolarSystem(fgTIME *t)
{
  if (theSolarSystem)
    {
      FG_LOG( FG_GENERAL, FG_ALERT, "Error: only one solarsystem allowed" );
      exit(-1);
    }
  theSolarSystem = this;
  ourSun     = new Star(t);   
  earthsMoon = new Moon(t);
  mercury    = new Mercury(t);
  venus      = new Venus(t);
  mars       = new Mars(t);
  jupiter    = new Jupiter(t);
  saturn     = new Saturn(t);
  uranus     = new Uranus(t);
  neptune    = new Neptune(t);

  displayList = 0;
};

/* --------------------------------------------------------------------------
   the destructor for class SolarSystem;
   danger: Huge Explosions ahead! (-:))
   ------------------------------------------------------------------------*/
SolarSystem::~SolarSystem()
{
  delete ourSun;
  delete earthsMoon;
  delete mercury;
  delete venus;
  delete mars;
  delete jupiter;
  delete saturn;
  delete uranus;
  delete neptune;
  //delete pluto;
}
/****************************************************************************
 * void SolarSystem::rebuild()
 *
 * this member function updates the positions for the sun, moon, and planets,
 * and then rebuilds the display list. 
 *
 * arguments: none
 * return value: none
 ***************************************************************************/
void SolarSystem::rebuild()
{
  fgLIGHT *l = &cur_light_params;
  fgTIME  *t = &cur_time_params;  
  float x, y, z,
    xx, yy,zz;
  double ra, dec;
  double x_2, x_4, x_8, x_10;
  double magnitude;
  GLfloat ambient;
  GLfloat amb[4];
  GLfloat moonColor[4] = {0.85, 0.75, 0.35, 1.0};
  GLfloat black[4] = {0.0, 0.0,0.0,1.0};
  GLfloat white[4] = {1.0, 1.0,1.0,1.0};

  // Step 1: update all the positions
  ourSun->updatePosition(t);
  earthsMoon->updatePosition(t, ourSun);
  mercury->updatePosition(t, ourSun);
  venus->updatePosition(t, ourSun);
  mars->updatePosition(t, ourSun);
  jupiter->updatePosition(t, ourSun);
  saturn->updatePosition(t, ourSun);
  uranus->updatePosition(t, ourSun);
  neptune->updatePosition(t, ourSun);
  
  fgUpdateSunPos();   // get the right sun angle (especially important when 
                      // running for the first time.
  if (displayList)
    xglDeleteLists(displayList, 1);

  displayList = xglGenLists(1);
  // Step 2: rebuild the display list
  xglNewList( displayList, GL_COMPILE);
  {
    // Step 2a: Add the moon...
    xglEnable( GL_LIGHTING );
    xglEnable( GL_LIGHT0 );
    // set lighting parameters
    xglLightfv(GL_LIGHT0, GL_AMBIENT, white );
    xglLightfv(GL_LIGHT0, GL_DIFFUSE, white );
    xglEnable( GL_CULL_FACE );
    
    // Enable blending, in order to effectively eliminate the dark side of the
    // moon
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);
    earthsMoon->getPos(&ra, &dec);
    x = 60000.0 * cos(ra) * cos (dec);
    y = 60000.0 * sin(ra) * cos (dec);
    z = 60000.0 * sin(dec);
    xx = cos(ra) * cos(dec);
    yy = sin(ra) * cos(dec);
    zz = sin(dec);
    xglMaterialfv(GL_FRONT, GL_AMBIENT, black);
    xglMaterialfv(GL_FRONT, GL_DIFFUSE, moonColor); 
    xglPushMatrix();
    {
	earthsMoon->newImage(ra,dec);
    }
    xglPopMatrix();
    glDisable(GL_BLEND);
    xglDisable(GL_LIGHTING);
 
    // Step 2b:  Add the sun
    x_2 = l -> sun_angle * l->sun_angle;
    x_4 = x_2 * x_2;
    x_8 = x_4 * x_4;
    x_10 = x_8 * x_2;
    ambient = (0.4 * pow (1.1, - x_10 / 30.0));
    if (ambient < 0.3) ambient = 0.3;
    if (ambient > 1.0) ambient = 1.0;

    amb[0] = 0.00 + ((ambient * 6.0)  - 1.0); // minimum value = 0.8
    amb[1] = 0.00 + ((ambient * 11.0) - 3.0); // minimum value = 0.3
    amb[2] = 0.00 + ((ambient * 12.0) - 3.6); // minimum value = 0.0
    amb[3] = 1.00;

    if (amb[0] > 1.0) amb[0] = 1.0;
    if (amb[1] > 1.0) amb[1] = 1.0;
    if (amb[2] > 1.0) amb[2] = 1.0;

    ourSun->getPos(&ra, &dec);
    x = 60000.0 * cos(ra) * cos(dec);
    y = 60000.0 * sin(ra) * cos(dec);
    z = 60000.0 * sin(dec);
    xglPushMatrix();
    {
      // xglPushMatrix();
      xglTranslatef(x,y,z);
      xglColor3f(amb[0], amb[1], amb[2]);
      glutSolidSphere(1400.0, 10, 10);
    }
    glPopMatrix();
    // Step 2c: Add the planets
    xglBegin(GL_POINTS);
    mercury->getPos(&ra, &dec, &magnitude);addPlanetToList(ra, dec, magnitude);
    venus  ->getPos(&ra, &dec, &magnitude);addPlanetToList(ra, dec, magnitude);
    mars   ->getPos(&ra, &dec, &magnitude);addPlanetToList(ra, dec, magnitude);
    jupiter->getPos(&ra, &dec, &magnitude);addPlanetToList(ra, dec, magnitude);
    saturn ->getPos(&ra, &dec, &magnitude);addPlanetToList(ra, dec, magnitude);
    uranus ->getPos(&ra, &dec, &magnitude);addPlanetToList(ra, dec, magnitude);
    neptune->getPos(&ra, &dec, &magnitude);addPlanetToList(ra, dec, magnitude);
    xglEnd();
    xglEnable(GL_LIGHTING);
  }
  xglEndList();
}

/*****************************************************************************
 * double SolarSystem::scaleMagnitude(double magn)
 * This private member function rescales the original magnitude, as used in the
 * astronomical sense of the word, into a value used by OpenGL to draw a 
 * convincing Star or planet
 * 
 * Argument: the astronomical magnitude
 *
 * return value: the rescaled magnitude
 ****************************************************************************/
double SolarSystem::scaleMagnitude(double magn)
{
  double magnitude = (0.0 - magn) / 5.0 + 1.0;
  magnitude = magnitude * 0.7 + (3 * 0.1);
  if (magnitude > 1.0) magnitude = 1.0;
  if (magnitude < 0.0) magnitude = 0.0;
  return magnitude;
}

/***************************************************************************
 * void SolarSytem::addPlanetToList(double ra, double dec, double magn);
 *
 * This private member function first causes the magnitude to be properly
 * rescaled, and then adds the planet to the display list.
 * 
 * arguments: Right Ascension, declination, and magnitude
 *
 * return value: none
 **************************************************************************/
void SolarSystem::addPlanetToList(double ra, double dec, double magn)
{
  double
    magnitude = scaleMagnitude ( magn );

  fgLIGHT *l = &cur_light_params;

  if ((double) (l->sun_angle - FG_PI_2) > 
      ((magnitude - 1.0) * - 20 * DEG_TO_RAD)) 
    {
      xglColor3f (magnitude, magnitude, magnitude);
      xglVertex3f( 50000.0 * cos (ra) * cos (dec),
		   50000.0 * sin (ra) * cos (dec),
		   50000.0 * sin (dec));
    }
}


SolarSystem* SolarSystem::theSolarSystem = 0;

/******************************************************************************
 * void solarSystemRebuild()
 * this a just a wrapper function, provided for use as an interface to the 
 * event manager
 *****************************************************************************/
void solarSystemRebuild()
{
  SolarSystem::theSolarSystem->rebuild();
}