/* 
* Example to test Texture2DArray for texture animation
*/


#include <osg/Notify>
#include <osg/Geometry>
#include <osg/Geode>
#include <osg/Uniform>

#include <osg/Texture3D>
#include <osg/Texture2DArray>

#include <osg/Program>
#include <osg/Shader>

#include <osgDB/ReadFile>

#include <osgViewer/Viewer>
#include <osgViewer/ViewerEventHandlers>

#include <iostream>
#include <sstream>
#include <iomanip>


/************************************************************************/
/* Time Callback                                                        */
/************************************************************************/
class SwitchTexCallback : public osg::Uniform::Callback
{
public:

   SwitchTexCallback(unsigned int numTex):
   _startTime(DBL_MAX),
   _numTex(numTex)
   {   
   }

   /** do customized update code.*/
   virtual void operator () (osg::Uniform* pUniform, osg::NodeVisitor* pNV) 
   {
      if (DBL_MAX ==_startTime)
         _startTime = pNV->getFrameStamp()->getSimulationTime();

      double currTime = pNV->getFrameStamp()->getSimulationTime();
      double delta_milli = 1000.f * (currTime - _startTime);

      if (delta_milli > 40.f)
      {
         // check next idx
         if(_currTexIdx+1 < _numTex)
            _currTexIdx++;
         else
            _currTexIdx = 0;

         // update uniform
         pUniform->set((float)_currTexIdx);
         // reset counter
         _startTime = pNV->getFrameStamp()->getSimulationTime();

         //std::cout << "set img idx " << _currTexIdx << std::endl;
      }
   }

protected:
   SwitchTexCallback();

   unsigned int _numTex;
   unsigned int _currTexIdx;
   double _startTime; // internal counter   
};



//////////////////////////////////////////////////////////////////////////
void LoadTextureArray(osg::StateSet* pSS, const std::vector<std::string>& imgFileNameVec)
{
   // and the relative uniform array with indices   
   osg::ref_ptr<osg::Texture2DArray> pTextureArray = new osg::Texture2DArray;
   pTextureArray->setTextureDepth(imgFileNameVec.size());
   
   pTextureArray->setUseHardwareMipMapGeneration(false);
   

   for (unsigned int i = 0; i < imgFileNameVec.size(); ++i)
   {
      // NOTE textures in the array must be same size and format
      // apparently there's an issue with DDS and TGA files, PNG work well...
      osg::Image* pImg = osgDB::readImageFile(imgFileNameVec[i]);
      if (pImg)
      {
         pTextureArray->setImage(i, pImg);
         std::cout << "added image " << pImg->getFileName() << " to texArray." << std::endl;
      }
      else
         osg::notify( osg::ALWAYS ) << "Can't open image: " << imgFileNameVec[i] << std::endl;
   }

   // must specify a filter
   pTextureArray->setFilter( osg::Texture2DArray::MIN_FILTER, osg::Texture2DArray::LINEAR);
   pTextureArray->setFilter( osg::Texture2DArray::MAG_FILTER, osg::Texture2DArray::LINEAR);
   // set attribute - NOT mode!!! mode not available on the fixed pipeline...
   pSS->setTextureAttribute(0, pTextureArray.get(), osg::StateAttribute::ON);
}


//////////////////////////////////////////////////////////////////////////
void LoadShaderTexArray(osg::StateSet* pSS, unsigned int numTex)
{
//    osg::Shader* pVert = osgDB::readShaderFile(osg::Shader::VERTEX, "textureAnimationArray.vert");
//    osg::Shader* pFrag = osgDB::readShaderFile(osg::Shader::FRAGMENT, "textureAnimationArray.frag");

   std::string vertSource = 
      "varying vec2 texCoord;\n"
      "\n"
      "void main(void)\n"
      "{\n"
      "// pass the tex coords\n"
      "texCoord = gl_MultiTexCoord0.xy; \n"
      "   \n"
      "// compute the position   \n"
      "gl_Position = ftransform();\n"
      "}";


   std::string fragSource = 
      "uniform sampler2DArray textureArray; \n"
      "uniform float texIdx;\n"
      "\n"
      "// texturing\n"
      "varying vec2 texCoord;\n"
      "\n"
      "void main(void)\n"
      "{\n"
      "vec4 color = texture2DArray(textureArray, vec3(texCoord, texIdx));\n"
      "   \n"
      "gl_FragColor = color;\n"
      "}";

   osg::Shader* pVert = new osg::Shader(osg::Shader::VERTEX, vertSource);
   osg::Shader* pFrag = new osg::Shader(osg::Shader::FRAGMENT, fragSource); 
   
   osg::Program* pProg = new osg::Program;
   pProg->addShader(pVert);
   pProg->addShader(pFrag);

   pSS->setAttribute(pProg, osg::StateAttribute::ON);

   // add sampler uniform
   osg::Uniform* pTexArrayU = new osg::Uniform(osg::Uniform::SAMPLER_2D_ARRAY, "textureArray");
   pTexArrayU->set(0);
   pSS->addUniform(pTexArrayU);

   // add uniform
   osg::Uniform* pTexIdxU = new osg::Uniform(osg::Uniform::FLOAT, "texIdx");
   pTexIdxU->set(0.f);
   pTexIdxU->setUpdateCallback(new SwitchTexCallback(numTex));
   pSS->addUniform(pTexIdxU);
}



//////////////////////////////////////////////////////////////////////////
osg::Node* createModel(const std::vector<std::string>& imgFileNameVec)
{
   // create the quad
    float size = 1.f;
    osg::Vec3 sideVec(size, 0.f, 0.f);
    osg::Vec3 upVec(0.f, 0.f, size);
    osg::Geometry* pGemometry = osg::createTexturedQuadGeometry(osg::Vec3(-0.5, 0, -0.5), sideVec, upVec);

    // load the texture 3d
    osg::StateSet* pSS = pGemometry->getOrCreateStateSet();
    //LoadTexture3D(pSS, imgFileNameVec);
    LoadTextureArray(pSS, imgFileNameVec);

    // load shader
    //LoadShaderTex3D(pSS, imgFileNameVec.size());
    LoadShaderTexArray(pSS, imgFileNameVec.size());

    // create geode and group
    osg::Geode* pGeode = new osg::Geode;
    pGeode->addDrawable(pGemometry);

    osg::Group* root = new osg::Group;
    root->addChild(pGeode);
    return root;
}


int main(int argc, char** argv)
{
    // use an ArgumentParser object to manage the program arguments.
    osg::ArgumentParser arguments(&argc,argv);

    // set up the usage document, in case we need to print out how to use this program.
    arguments.getApplicationUsage()->setDescription(arguments.getApplicationName()+ " Load texture animation from an image sequence.");
    arguments.getApplicationUsage()->setCommandLineUsage(arguments.getApplicationName()+ " [options]");
    arguments.getApplicationUsage()->addCommandLineOption("-h or --help", "Display this information");    
    arguments.getApplicationUsage()->addCommandLineOption("--image","Image filename root. Default is \"test\". Loaded files will be filenameroot_01, etc...");
    arguments.getApplicationUsage()->addCommandLineOption("--numImage", "Number of images to be loaded (based on --image option. Default is 10");
    arguments.getApplicationUsage()->addCommandLineOption("--format", "specify the image format. Default is png");

    // if user request help write it out to cout and exit.
    if (arguments.read("-h") || arguments.read("--help"))
    {
       arguments.getApplicationUsage()->write(std::cout);
       return 1;
    }

    std::string imgRoot("test");
    arguments.read("--image", imgRoot);
    std::cout << "Image root name is " << imgRoot << std::endl;

    std::string format("png");
    arguments.read("--format", format);
    std::cout << "Image format is " << format << std::endl;

    unsigned int numImage = 10;
    arguments.read("--numImage", numImage);
    std::cout << "Number of images is " << numImage << std::endl;

    //build the images filenames
    std::vector<std::string> imgVec;    

    for (unsigned int i = 0; i < numImage; ++i)
    {
       std::stringstream ss;
       ss << imgRoot << "_";
       ss << std::setfill ('0') << std::setw (3) << i+1;
       ss << "." << format;

       imgVec.push_back(ss.str());
    }

    //osg::Node* rootNode = createModel((arguments.argc() > 1 ? arguments[1] : "Images/lz.rgb"));
    osg::Node* rootNode = createModel(imgVec);

    // construct the viewer.
    osgViewer::Viewer viewer;
    // add the thread model handler
    viewer.addEventHandler(new osgViewer::ThreadingHandler);
    // add the window size toggle handler
    viewer.addEventHandler(new osgViewer::WindowSizeHandler);        
    // add the stats handler
    viewer.addEventHandler(new osgViewer::StatsHandler);
    // add the help handler
    viewer.addEventHandler(new osgViewer::HelpHandler(arguments.getApplicationUsage()));


    // add model to viewer.
    viewer.setSceneData(rootNode);
    
    return viewer.run();
}
