#include "OsgMainApp.hpp"

#include "SilverLining.h"
#include "SkyDrawable.h"
#include "CloudsDrawable.h"
#include "AtmosphereReference.h"
#include "SilverLiningProjectionMatrixCallback.h"
#include "AndroidResourceLoader.h"

#define FIXED_NEAR_FAR

void integrateSilverLining(osg::ref_ptr<osg::Node> sceneGraphRoot, osgViewer::Viewer& viewer,
		AAssetManager *assetManager)
{
    // No need for OSG to clear the color buffer, the sky will fill it for you.
    viewer.getCamera()->setClearMask(GL_DEPTH_BUFFER_BIT);

    // Make sure lighting mode is correct for setting sun direction in SilverLining's world space
    viewer.setLightingMode(osgViewer::View::SKY_LIGHT);

	// Instantiate an Atmosphere and associate it with this camera. If you have multiple cameras
	// in multiple contexts, be sure to instantiate seperate Atmosphere objects for each.
	SilverLining::Atmosphere *atm = new SilverLining::Atmosphere("Your user name", "Your license code");

	osg::notify(osg::ALWAYS)<<"Creating SilverLining resource loader..."<<std::endl;

	// Pass the Android asset manager to SilverLining so it can load its own resources
	AndroidResourceLoader *rl = SL_NEW AndroidResourceLoader(assetManager);
    atm->SetResourceLoader(rl);

    // Add the sky (calls Atmosphere::DrawSky and handles initialization once you're in
    // the rendering thread)
    osg::Geode *skyGeode = new osg::Geode;
	SkyDrawable *skyDrawable = new SkyDrawable(&viewer);

    skyGeode->addDrawable(skyDrawable);
    skyGeode->setCullingActive(false);

    /* If you let OSG auto-calculate the near and far clip planes; it'll exclude the
       sky box and clouds. One solution is to set the near and far clip planes explicitly
	   like this:
    */
#ifdef FIXED_NEAR_FAR
    viewer.getCamera()->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR);
    double fovy, aspect, zNear, zFar;
    viewer.getCamera()->getProjectionMatrixAsPerspective(fovy, aspect, zNear, zFar);
    viewer.getCamera()->setProjectionMatrixAsPerspective(fovy, aspect, 10, 100000);
#endif
 /*
       or, you can use the included projection matrix callback to intercept how OSG computes
	   the near and far clip planes and take SilverLining's objects into account, like this:
 */

#ifndef FIXED_NEAR_FAR
	SilverLiningProjectionMatrixCallback *cb = new SilverLiningProjectionMatrixCallback(
		atm, viewer.getCamera());
    viewer.getCamera()->setClampProjectionMatrixCallback(cb);
    cb->setSkyDrawable(skyDrawable);
#endif

	AtmosphereReference *ar = new AtmosphereReference;
	ar->atmosphere = atm;
	viewer.getCamera()->setUserData(ar);

    // Use a RenderBin to enforce that the scene gets drawn first, then the sky, then the clouds
    skyGeode->getOrCreateStateSet()->setRenderBinDetails(98, "RenderBin");
    skyGeode->getOrCreateStateSet()->setAttributeAndModes( new osg::Depth( osg::Depth::LEQUAL, 0.0, 1.0, false ) );

    // Add the models
    sceneGraphRoot.get()->getOrCreateStateSet()->setRenderBinDetails(1, "RenderBin");

    // Add the clouds (note, you need this even if you don't want clouds - it calls
    // Atmosphere::DrawObjects() )
    osg::Geode *cloudsGeode = new osg::Geode;
    CloudsDrawable * cloudsDrawable = new CloudsDrawable(&viewer);
    cloudsGeode->addDrawable(cloudsDrawable);
    cloudsGeode->getOrCreateStateSet()->setRenderBinDetails(99, "RenderBin");
    cloudsGeode->setCullingActive(false);

#ifndef FIXED_NEAR_FAR
    cb->setCloudsDrawable(cloudsDrawable);
#endif

    viewer.getSceneData()->asGroup()->addChild(skyGeode);
    viewer.getSceneData()->asGroup()->addChild(cloudsGeode);

    osg::notify(osg::ALWAYS)<<"SilverLining integration completed..."<<std::endl;
}


OsgMainApp::OsgMainApp(){

    _lodScale = 1.0f;
    _prevFrame = 0;

    _initialized = false;
    _clean_scene = false;

    _assetManager = 0;

}
OsgMainApp::~OsgMainApp(){

}
void OsgMainApp::loadModels(){
    if(_vModelsToLoad.size()==0) return;

    osg::notify(osg::ALWAYS)<<"There are "<<_vModelsToLoad.size()<<" models to load"<<std::endl;

    Model newModel;
    osg::ref_ptr<osg::Node> loadedModel = 0;
    for(unsigned int i=0; i<_vModelsToLoad.size(); i++){
        newModel = _vModelsToLoad[i];
        osg::notify(osg::ALWAYS)<<"Loading: "<<newModel.filename<<std::endl;

        loadedModel = osgDB::readNodeFile(newModel.filename);
        if (loadedModel == 0) {
            osg::notify(osg::ALWAYS)<<"Model not loaded"<<std::endl;
        } else {
            osg::notify(osg::ALWAYS)<<"Model loaded"<<std::endl;
            _vModels.push_back(newModel);

            loadedModel->setName(newModel.name);

            osg::Shader * vshader = new osg::Shader(osg::Shader::VERTEX, gVertexShader );
            osg::Shader * fshader = new osg::Shader(osg::Shader::FRAGMENT, gFragmentShader );

            osg::Program * prog = new osg::Program;
            prog->addShader ( vshader );
            prog->addShader ( fshader );

            loadedModel->getOrCreateStateSet()->setAttribute ( prog );

            _root->addChild(loadedModel);
        }
    }

    osgViewer::Viewer::Windows windows;
    _viewer->getWindows(windows);
    for(osgViewer::Viewer::Windows::iterator itr = windows.begin();itr != windows.end();++itr)
    {
      (*itr)->getState()->setUseModelViewAndProjectionUniforms(true);
      (*itr)->getState()->setUseVertexAttributeAliasing(true);
    }

    _viewer->setSceneData(NULL);
    _viewer->setSceneData(_root.get());
    _manipulator->getNode();
    _viewer->home();

    _viewer->getDatabasePager()->clear();
    _viewer->getDatabasePager()->registerPagedLODs(_root.get());
    _viewer->getDatabasePager()->setUpThreads(3, 1);
    _viewer->getDatabasePager()->setTargetMaximumNumberOfPageLOD(2);
    _viewer->getDatabasePager()->setUnrefImageDataAfterApplyPolicy(true, true);

    _vModelsToLoad.clear();

}
void OsgMainApp::deleteModels(){
    if(_vModelsToDelete.size()==0) return;

    osg::notify(osg::ALWAYS)<<"There are "<<_vModelsToDelete.size()<<" models to delete"<<std::endl;

    Model modelToDelete;
    for(unsigned int i=0; i<_vModelsToDelete.size(); i++){
        modelToDelete = _vModelsToDelete[i];
        osg::notify(osg::ALWAYS)<<"Deleting: "<<modelToDelete.name<<std::endl;

        for(unsigned int j=_root->getNumChildren(); j>0; j--){
            osg::ref_ptr<osg::Node> children = _root->getChild(j-1);
            if(children->getName() == modelToDelete.name){
                _root->removeChild(children);
            }
        }

    }

    _vModelsToDelete.clear();
    osg::notify(osg::ALWAYS)<<"finished"<<std::endl;
}
//Initialization function
void OsgMainApp::initOsgWindow(int x,int y,int width,int height, AAssetManager *assetManager){

    __android_log_write(ANDROID_LOG_ERROR, "OSGANDROID",
            "Initializing geometry");

    _assetManager = assetManager;

    //Pending
    _notifyHandler = new OsgAndroidNotifyHandler();
    _notifyHandler->setTag("Osg Viewer");
    osg::setNotifyHandler(_notifyHandler);

    osg::notify(osg::ALWAYS)<<"Testing"<<std::endl;

    _viewer = new osgViewer::Viewer();
    _viewer->setUpViewerAsEmbeddedInWindow(x, y, width, height);
    _viewer->setThreadingModel(osgViewer::ViewerBase::SingleThreaded);

    _root = new osg::Group();
    _viewer->setSceneData(_root);

	integrateSilverLining(_root, *_viewer, _assetManager);

    _viewer->realize();

    _viewer->addEventHandler(new osgViewer::StatsHandler);
    _viewer->addEventHandler(new osgGA::StateSetManipulator(_viewer->getCamera()->getOrCreateStateSet()));
    _viewer->addEventHandler(new osgViewer::ThreadingHandler);
    _viewer->addEventHandler(new osgViewer::LODScaleHandler);

    _manipulator = new osgGA::KeySwitchMatrixManipulator;

    _manipulator->addMatrixManipulator( '1', "Trackball", new osgGA::TrackballManipulator() );
    _manipulator->addMatrixManipulator( '2', "Flight", new osgGA::FlightManipulator() );
    _manipulator->addMatrixManipulator( '3', "Drive", new osgGA::DriveManipulator() );
    _manipulator->addMatrixManipulator( '4', "Terrain", new osgGA::TerrainManipulator() );
    _manipulator->addMatrixManipulator( '5', "Orbit", new osgGA::OrbitManipulator() );
    _manipulator->addMatrixManipulator( '6', "FirstPerson", new osgGA::FirstPersonManipulator() );
    _manipulator->addMatrixManipulator( '7', "Spherical", new osgGA::SphericalManipulator() );

    _viewer->setCameraManipulator( _manipulator.get() );

    _viewer->getViewerStats()->collectStats("scene", true);

    _initialized = true;

}
//Draw
void OsgMainApp::draw(){
    //Every load o remove has to be done before any drawing
    loadModels();
    deleteModels();

    _viewer->frame();
}
//Events
void OsgMainApp::mouseButtonPressEvent(float x,float y,int button){
    _viewer->getEventQueue()->mouseButtonPress(x, y, button);
}
void OsgMainApp::mouseButtonReleaseEvent(float x,float y,int button){
    _viewer->getEventQueue()->mouseButtonRelease(x, y, button);
}
void OsgMainApp::mouseMoveEvent(float x,float y){
    _viewer->getEventQueue()->mouseMotion(x, y);
}
void OsgMainApp::keyboardDown(int key){
    _viewer->getEventQueue()->keyPress(key);
}
void OsgMainApp::keyboardUp(int key){
    _viewer->getEventQueue()->keyRelease(key);
}
//Loading and unloading
void OsgMainApp::loadObject(std::string filePath){
    Model newModel;
    newModel.filename = filePath;
    newModel.name = filePath;

    int num = 0;
    for(unsigned int i=0;i<_vModels.size();i++){
        if(_vModels[i].name==newModel.name)
            return;
    }

    _vModelsToLoad.push_back(newModel);

}
void OsgMainApp::loadObject(std::string name,std::string filePath){

    Model newModel;
    newModel.filename = filePath;
    newModel.name = name;

    for(unsigned int i=0;i<_vModels.size();i++){
        if(_vModels[i].name==newModel.name){
            osg::notify(osg::ALWAYS)<<"Name already used"<<std::endl;
            return;
        }
    }

    _vModelsToLoad.push_back(newModel);
}
void OsgMainApp::unLoadObject(int number){
    if(_vModels.size() <= number){
        osg::notify(osg::FATAL)<<"Index number error"<<std::endl;
        return;
    }

    Model modelToDelete = _vModels[number];
    _vModels.erase(_vModels.begin()+number);
    _vModelsToDelete.push_back(modelToDelete);
}
void OsgMainApp::clearScene(){
    _vModelsToDelete = _vModels;
    _vModels.clear();
}
//Other Functions
int OsgMainApp::getNumberObjects(){
    return _vModels.size();
}
std::string OsgMainApp::getObjectName(int number){
    return _vModels[number].name;
}
void OsgMainApp::setClearColor(osg::Vec4f color){
    osg::notify(osg::ALWAYS)<<"Setting Clear Color"<<std::endl;
    _viewer->getCamera()->setClearColor(color);
}
osg::Vec4f OsgMainApp::getClearColor(){
    osg::notify(osg::ALWAYS)<<"Getting Clear Color"<<std::endl;
    return _viewer->getCamera()->getClearColor();
}
