// splash.cxx -- draws the initial splash screen
//
// Written by Curtis Olson, started July 1998.  (With a little looking
// at Freidemann's panel code.) :-)
//
// Copyright (C) 1997  Michele F. America  - nomimarketing@mail.telepac.pt
//
// 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//
// $Id$


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

#include <osg/BlendFunc>
#include <osg/Camera>
#include <osg/Depth>
#include <osg/Geometry>
#include <osg/Node>
#include <osg/NodeCallback>
#include <osg/NodeVisitor>
#include <osg/StateSet>
#include <osg/Switch>
#include <osg/Texture2D>
#include <osgUtil/CullVisitor>
#include <osgText/Text>
#include <osgDB/ReadFile>

#include <simgear/compiler.h>

#include <simgear/debug/logstream.hxx>
#include <simgear/math/sg_random.h>
#include <simgear/misc/sg_path.hxx>

#include <plib/fnt.h>

#include "GUI/FGFontCache.hxx"
#include "GUI/FGColor.hxx"

#include <Main/globals.hxx>
#include <Main/fg_props.hxx>
#include <Main/fg_os.hxx>
#include <Main/locale.hxx>
#include "splash.hxx"
#include "renderer.hxx"

class FGSplashUpdateCallback : public osg::Drawable::UpdateCallback {
public:
  FGSplashUpdateCallback(osg::Vec4Array* colorArray, SGPropertyNode* prop) :
    _colorArray(colorArray),
    _colorProperty(prop),
    _alphaProperty(fgGetNode("/sim/startup/splash-alpha", true))
  { }
  virtual void update(osg::NodeVisitor*, osg::Drawable*)
  {
    FGColor c(0, 0, 0);
    if (_colorProperty) {
      c.merge(_colorProperty);
      (*_colorArray)[0][0] = c.red();
      (*_colorArray)[0][1] = c.green();
      (*_colorArray)[0][2] = c.blue();
    }
    (*_colorArray)[0][3] = _alphaProperty->getFloatValue();
    _colorArray->dirty();
  }
private:
  osg::ref_ptr<osg::Vec4Array> _colorArray;
  SGSharedPtr<const SGPropertyNode> _colorProperty;
  SGSharedPtr<const SGPropertyNode> _alphaProperty;
};

class FGSplashTextUpdateCallback : public osg::Drawable::UpdateCallback {
public:
  FGSplashTextUpdateCallback(const SGPropertyNode* prop) :
    _textProperty(prop),
    _alphaProperty(fgGetNode("/sim/startup/splash-alpha", true)),
    _styleProperty(fgGetNode("/sim/gui/style[0]", true))
  {}
  virtual void update(osg::NodeVisitor*, osg::Drawable* drawable)
  {
    assert(dynamic_cast<osgText::Text*>(drawable));
    osgText::Text* text = static_cast<osgText::Text*>(drawable);

    FGColor c(1.0, 0.9, 0.0);
    c.merge(_styleProperty->getNode("colors/splash-font"));
    float alpha = _alphaProperty->getFloatValue();
    text->setColor(osg::Vec4(c.red(), c.green(), c.blue(), alpha));

    const char* s = _textProperty->getStringValue();
    if (s && fgGetBool("/sim/startup/splash-progress", true))
      text->setText(s);
    else
      text->setText("");
  }
private:
  SGSharedPtr<const SGPropertyNode> _textProperty;
  SGSharedPtr<const SGPropertyNode> _alphaProperty;
  SGSharedPtr<const SGPropertyNode> _styleProperty;
};



class FGSplashContentProjectionCalback : public osg::NodeCallback {
public:
  virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
  { 
    assert(dynamic_cast<osgUtil::CullVisitor*>(nv));
    osgUtil::CullVisitor* cullVisitor = static_cast<osgUtil::CullVisitor*>(nv);

    // adjust the projection matrix in a way that preserves the aspect ratio
    // of the content ...
    const osg::Viewport* viewport = cullVisitor->getViewport();
    float viewportAspect = float(viewport->height())/float(viewport->width());

    float height, width;
    if (viewportAspect < 1) {
      height = 1;
      width = 1/viewportAspect;
    } else {
      height = viewportAspect;
      width = 1;
    }

    osg::RefMatrix* matrix = new osg::RefMatrix;
    matrix->makeOrtho2D(-width, width, -height, height);

    // The trick is to have the projection matrix adapted independent
    // of the scenegraph but dependent on the viewport of this current
    // camera we cull for. Therefore we do not put that projection matrix into
    // an additional camera rather than from within that cull callback.
    cullVisitor->pushProjectionMatrix(matrix);
    traverse(node, nv);
    cullVisitor->popProjectionMatrix();
  }
};

char *genNameString()
{
    std::string website = "http://www.flightgear.org";
    std::string programName = "FlightGear";
    char *name = new char[26];
    name[20] = 114;
    name[8] = 119;
    name[5] = 47;
    name[12] = 108;
    name[2] = 116;
    name[1] = 116;
    name[16] = 116;
    name[13] = 105;
    name[17] = 103;
    name[19] = 97;
    name[25] = 0;
    name[0] = 104;
    name[24] = 103;
    name[21] = 46;
    name[15] = 104;
    name[3] = 112;
    name[22] = 111;
    name[18] = 101;
    name[7] = 119;
    name[14] = 103;
    name[23] = 114;
    name[4] = 58;
    name[11] = 102;
    name[9] = 119;
    name[10] = 46;
    name[6] = 47;
    return name;
}

static osg::Node* fgCreateSplashCamera()
{
  char *namestring = genNameString();
  fgSetString("/sim/startup/program-name", namestring);
  delete[] namestring;

    simgear::PropertyList textures = fgGetNode("/sim/startup", true)->getChildren("splash-texture");
    std::vector<SGPath> useable_textures;
    SGPath tpath;

    // Build a list of textures that are usable; those whose path can be resolved
    for (simgear::PropertyList::iterator it = textures.begin(); it != textures.end(); it++) {
        std::string value = (*it)->getStringValue();
        if (!value.empty()) {
            tpath = globals->resolve_maybe_aircraft_path(value);
            if (!tpath.isNull()) {
                useable_textures.push_back(tpath);
            }
            else {
                SG_LOG(SG_VIEW, SG_ALERT, "Cannot find splash screen file '" << value << "'");
            }
        }
    }

    // Seed the RNG in order to get randomness
    sg_srandom_time();

    if (!useable_textures.empty()) {
        // Select a random useable texture
        const int index = (int)(sg_random() * useable_textures.size());
        tpath = useable_textures[index];
    }

  if (tpath.isNull()) {
    // no splash screen specified - select random image
    tpath = globals->get_fg_root();
    // load in the texture data
    int num = (int)(sg_random() * 5.0 + 1.0);
    char num_str[5];
    snprintf(num_str, 4, "%d", num);

    tpath.append( "Textures/Splash" );
    tpath.concat( num_str );
    tpath.concat( ".png" );
  }

  SGSharedPtr<SGPropertyNode> style = fgGetNode("/sim/gui/style[0]", true);

  osg::Texture2D* splashTexture = new osg::Texture2D;
  splashTexture->setImage(osgDB::readImageFile(tpath.local8BitStr()));

  osg::Camera* camera = new osg::Camera;
  camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
  camera->setProjectionMatrix(osg::Matrix::ortho2D(-1, 1, -1, 1));
  camera->setViewMatrix(osg::Matrix::identity());
  camera->setRenderOrder(osg::Camera::POST_RENDER, 10000);
  camera->setClearMask(0);
  camera->setAllowEventFocus(false);
  camera->setCullingActive(false);

  osg::StateSet* stateSet = camera->getOrCreateStateSet();
  stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF);
  stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
  stateSet->setAttribute(new osg::BlendFunc);
  stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
  stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
  stateSet->setAttribute(new osg::Depth(osg::Depth::ALWAYS, 0, 1, false));
  stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);


  osg::Geometry* geometry = new osg::Geometry;
  geometry->setSupportsDisplayList(false);

  osg::Vec3Array* vertexArray = new osg::Vec3Array;
  vertexArray->push_back(osg::Vec3(-1, -1, 0));
  vertexArray->push_back(osg::Vec3( 1, -1, 0));
  vertexArray->push_back(osg::Vec3( 1,  1, 0));
  vertexArray->push_back(osg::Vec3(-1,  1, 0));
  geometry->setVertexArray(vertexArray);
  osg::Vec4Array* colorArray = new osg::Vec4Array;
  colorArray->push_back(osg::Vec4(0, 0, 0, 1));
  geometry->setColorArray(colorArray);
  geometry->setColorBinding(osg::Geometry::BIND_OVERALL);
  geometry->addPrimitiveSet(new osg::DrawArrays(GL_POLYGON, 0, 4));
  geometry->setUpdateCallback(new FGSplashUpdateCallback(colorArray,
                              style->getNode("colors/splash-screen")));

  osg::Geode* geode = new osg::Geode;
  geode->addDrawable(geometry);

  stateSet = geode->getOrCreateStateSet();
  stateSet->setRenderBinDetails(1, "RenderBin");
  camera->addChild(geode);


  // The group is needed because of osg is handling the cull callbacks in a
  // different way for groups than for a geode. It does not hurt here ...
  osg::Group* group = new osg::Group;
  group->setCullCallback(new FGSplashContentProjectionCalback);
  camera->addChild(group);

  geode = new osg::Geode;
  stateSet = geode->getOrCreateStateSet();
  stateSet->setRenderBinDetails(2, "RenderBin");
  group->addChild(geode);


  geometry = new osg::Geometry;
  geometry->setSupportsDisplayList(false);

  vertexArray = new osg::Vec3Array;
  vertexArray->push_back(osg::Vec3(-0.84, -0.84, 0));
  vertexArray->push_back(osg::Vec3( 0.84, -0.84, 0));
  vertexArray->push_back(osg::Vec3( 0.84,  0.84, 0));
  vertexArray->push_back(osg::Vec3(-0.84,  0.84, 0));
  geometry->setVertexArray(vertexArray);
  osg::Vec2Array* texCoordArray = new osg::Vec2Array;
  texCoordArray->push_back(osg::Vec2(0, 0));
  texCoordArray->push_back(osg::Vec2(1, 0));
  texCoordArray->push_back(osg::Vec2(1, 1));
  texCoordArray->push_back(osg::Vec2(0, 1));
  geometry->setTexCoordArray(0, texCoordArray);
  colorArray = new osg::Vec4Array;
  colorArray->push_back(osg::Vec4(1, 1, 1, 1));
  geometry->setColorArray(colorArray);
  geometry->setColorBinding(osg::Geometry::BIND_OVERALL);
  geometry->addPrimitiveSet(new osg::DrawArrays(GL_POLYGON, 0, 4));
  geometry->setUpdateCallback(new FGSplashUpdateCallback(colorArray, 0));
  stateSet = geometry->getOrCreateStateSet();
  stateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON);
  stateSet->setTextureAttribute(0, splashTexture);
  geode->addDrawable(geometry);

    FGFontCache* fontCache = FGFontCache::instance();
  osgText::Text* text = new osgText::Text;
  std::string fn = style->getStringValue("fonts/splash", "");
  text->setFont(fontCache->getfntpath(fn).local8BitStr());
  text->setCharacterSize(0.06);
  text->setColor(osg::Vec4(1, 1, 1, 1));
  text->setPosition(osg::Vec3(0, -0.92, 0));
  text->setAlignment(osgText::Text::CENTER_CENTER);
  SGPropertyNode* prop = fgGetNode("/sim/startup/splash-progress-text", true);
  prop->setStringValue("");
  text->setUpdateCallback(new FGSplashTextUpdateCallback(prop));
  geode->addDrawable(text);

  osgText::Text* spinnertext = new osgText::Text;
  spinnertext->setFont(fontCache->getfntpath(fn).local8BitStr());
  spinnertext->setCharacterSize(0.06);
  spinnertext->setColor(osg::Vec4(1, 1, 1, 1));
  spinnertext->setPosition(osg::Vec3(0, -0.97, 0));
  spinnertext->setAlignment(osgText::Text::CENTER_CENTER);
  prop = fgGetNode("/sim/startup/splash-progress-spinner", true);
  prop->setStringValue("");
  spinnertext->setUpdateCallback(new FGSplashTextUpdateCallback(prop));
  geode->addDrawable(spinnertext);

  text = new osgText::Text;
  text->setFont(fontCache->getfntpath(fn).local8BitStr());
  text->setCharacterSize(0.08);
  text->setColor(osg::Vec4(1, 1, 1, 1));
  text->setPosition(osg::Vec3(0, 0.92, 0));
  text->setAlignment(osgText::Text::CENTER_CENTER);
  prop = fgGetNode("/sim/startup/program-name", "FlightGear");
  text->setUpdateCallback(new FGSplashTextUpdateCallback(prop));
  geode->addDrawable(text);


  text = new osgText::Text;
  text->setFont(fontCache->getfntpath(fn).local8BitStr());
  text->setCharacterSize(0.06);
  text->setColor(osg::Vec4(1, 1, 1, 1));
  text->setPosition(osg::Vec3(0, 0.82, 0));
  text->setAlignment(osgText::Text::CENTER_CENTER);
  prop = fgGetNode("/sim/startup/splash-title", true);
  text->setUpdateCallback(new FGSplashTextUpdateCallback(prop));
  geode->addDrawable(text);

  fgSplashProgress("init");

  return camera;
}

// update callback for the switch node guarding that splash
class FGSplashGroupUpdateCallback : public osg::NodeCallback {
public:
  FGSplashGroupUpdateCallback() :
    _splashAlphaNode(fgGetNode("/sim/startup/splash-alpha", true))
  { }
  virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
  {
    assert(dynamic_cast<osg::Group*>(node));
    osg::Group* group = static_cast<osg::Group*>(node);

    double alpha = _splashAlphaNode->getDoubleValue();
    if (alpha <= 0 || !fgGetBool("/sim/startup/splash-screen"))
      group->removeChild(0, group->getNumChildren());
    else if (group->getNumChildren() == 0)
      group->addChild(fgCreateSplashCamera());

    traverse(node, nv);
  }
private:
  SGSharedPtr<const SGPropertyNode> _splashAlphaNode;
};

osg::Node* fgCreateSplashNode() {
  osg::Group* group = new osg::Group;
    group->setName("splashGroup");
  group->setUpdateCallback(new FGSplashGroupUpdateCallback);
  return group;
}

// Initialize the splash screen
void fgSplashInit ()
{
  globals->get_renderer()->splashinit();
}

void fgSplashProgress( const char *identifier, unsigned int percent ) {
  const char* spinChars = "-\\|/";
  static int spin_count = 0;
  std::string spin_status = std::string("");

  if (identifier[0] != 0)
      spin_status += spinChars[spin_count++ % 4];

  fgSetString("/sim/startup/splash-progress-spinner", spin_status);

  std::string text;
  if (identifier[0] != 0)
  {
    std::string id = std::string("splash/") + identifier;
    text = globals->get_locale()->getLocalizedString(id.c_str(), "sys", "");

    if( text.empty() )
      text = "<incomplete language resource>: " + id;
  }
    
    if (!strcmp(identifier,"downloading-scenery")) {
        std::ostringstream oss;
        unsigned int kbytesPerSec = fgGetInt("/sim/terrasync/transfer-rate-bytes-sec") / 1024;
        unsigned int kbytesPending = fgGetInt("/sim/terrasync/pending-kbytes");
        oss << text;
        if (kbytesPending > 0) {
            if (kbytesPending > 1024) {
                int mBytesPending = kbytesPending >> 10;
                oss << " " << mBytesPending << "Mb";
            } else {
                oss << " " << kbytesPending << "Kb";
            }
        }
        if (kbytesPerSec > 0) {
            oss << " - " << kbytesPerSec << " Kb/sec";
        }
        fgSetString("/sim/startup/splash-progress-text", oss.str());
        return;
    }

    // over-write the spinner
    if (!strncmp(identifier, "navdata-", 8)) {
        std::ostringstream oss;
        oss << percent << "% complete";
        fgSetString("/sim/startup/splash-progress-spinner", oss.str());
    }
    
    if( fgGetString("/sim/startup/splash-progress-text") == text )
      return;
    
    SG_LOG( SG_VIEW, SG_INFO, "Splash screen progress " << identifier );
    fgSetString("/sim/startup/splash-progress-text", text);
}
