text: /*
   Copyright (c) 2010-2012, Paul Houx - All rights reserved.
   This code is intended for use with the Cinder C++ library: http://libcinder.org
  
   Redistribution and use in source and binary forms, with or without modification, are permitted provided that
   the following conditions are met:
  
   * Redistributions of source code must retain the above copyright notice, this list of conditions and
   the following disclaimer.
   * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
   the following disclaimer in the documentation and/or other materials provided with the distribution.
  
   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
   PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
   ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   POSSIBILITY OF SUCH DAMAGE.
   */
  
  #include "cinder/CinderMath.h"
  #include "cinder/Xml.h"
  #include "cinder/app/App.h"
  #include "cinder/app/RendererGl.h"
  #include "cinder/gl/Texture.h"
  #include "cinder/gl/gl.h"
  
  #include "ph/TextureStore.h"
  
  using namespace ci;
  using namespace ci::app;
  using namespace std;
  
  class FlickrImageViewerApp : public App {
    public:
  	static void prepare( Settings *settings );
  
  	void setup() override;
  	void cleanup() override;
  	void update() override;
  	void draw() override;
  
  	void mouseDown( MouseEvent event ){};
  	void mouseDrag( MouseEvent event ){};
  	void mouseUp( MouseEvent event ){};
  
  	void keyDown( KeyEvent event );
  
    protected:
  	vector<string> mUrls;
  
  	gl::TextureRef mFront;
  	gl::TextureRef mBack;
  
  	size_t mIndex;
  
  	double mTimeSwapped;
  	double mTimeView;
  	double mTimeFade;
  	double mTimeDuration;
  
  	bool mAsynchronous;
  };
  
  void FlickrImageViewerApp::prepare( Settings *settings )
  {
  	settings->setWindowSize( 600, 600 );
  	settings->setTitle( "Flickr Image Viewer" );
  	settings->setFrameRate( 60.0f );
  
  	// run in windowed mode and watch the Output messages window to learn
  	// more about asynchronous loading. Press 'F" to toggle full screen.
  	settings->setFullScreen( false );
  }
  
  void FlickrImageViewerApp::setup()
  {
  	// which image are we viewing next?
  	mIndex = 0;
  
  	// at what time did we last swap the images?
  	mTimeSwapped = 0.0;
  	// view each image at least 10 seconds
  	mTimeView = 5.0;
  	// crossfade images during 2.5 seconds
  	mTimeFade = 1.5;
  	// how long was the previous image visible?
  	mTimeDuration = 0.0;
  
  	// toggle this using the 'A' key to see the advantage of asynchronous loading
  	mAsynchronous = true;
  }
  
  void FlickrImageViewerApp::cleanup()
  {
  	// The TextureStore is a singleton, running several threads that need to be terminated. Sadly, there is
  	// an issue in Visual Studio that forces us to manually terminate it before application shutdown.
  	// See: http://stackoverflow.com/questions/10915233/stdthreadjoin-hangs-if-called-after-main-exits-when-using-vs2012-rc
  	ph::TextureStore::getInstance().cleanup();
  }
  
  void FlickrImageViewerApp::update()
  {
  	// instead of in setup(), I download the feed here so it doesn't take so long for our window to show up
  	if( mUrls.empty() ) {
  		// connect to Flickr and load a set of images. To view your own set,
  		// find the "RSS feed" link on the Flickr page and copy the url.
  		XmlTree       doc( loadUrl( "http://api.flickr.com/services/feeds/photoset.gne?set=726262&nsid=14684343@N00" ) );
  		XmlTree::Iter itr = doc.begin( "feed/entry/link" );
  		while( itr != doc.end() ) {
  			// check if link contains an image (type == 'image/jpeg')
  			if( itr->getAttributeValue<string>( "type" ) == "image/jpeg" ) {
  				// retrieve and store url
  				if( itr->hasAttribute( "href" ) )
  					mUrls.push_back( itr->getAttributeValue<string>( "href" ) );
  			}
  
  			++itr;
  		}
  
  		// make sure we actually have something to show
  		if( mUrls.empty() )
  			return;
  	}
  
  	// calculate elapsed time in seconds (since last swap)
  	double elapsed = ( getElapsedSeconds() - mTimeSwapped );
  
  	// if there is no front image yet, load it right away
  	if( !mFront ) {
  		if( mAsynchronous ) {
  			// load the texture asynchronously using the TextureManager. The call
  			// will return an empty texture if not ready yet.
  			mFront = ph::fetchTexture( mUrls[mIndex] );
  		}
  		else {
  			// load the texture synchronously using the TextureManager. The call
  			// will load and return the texture, but your application will have
  			// to wait for it to finish. Returns empty texture if load did not succeed.
  			mFront = ph::loadTexture( mUrls[mIndex] );
  		}
  
  		// if texture was loaded...
  		if( mFront ) {
  			// start fading in
  			mTimeSwapped = getElapsedSeconds();
  			// proceed to next texture
  			mIndex = ( mIndex + 1 ) % mUrls.size();
  		}
  	}
  	else if( elapsed > mTimeFade ) {
  		if( mAsynchronous ) {
  			// as soon as the front image has been faded in,
  			// start loading the back image asynchronously using the TextureManager.
  			// The call will return an empty texture while not ready yet.
  			mBack = ph::fetchTexture( mUrls[mIndex] );
  		}
  		else {
  			// load the texture synchronously using the TextureManager. The call
  			// will load and return the texture, but your application will have
  			// to wait for it to finish. Returns empty texture if load did not succeed.
  			mBack = ph::loadTexture( mUrls[mIndex] );
  		}
  
  		// if texture has been loaded and enough time has passed...
  		if( mBack && elapsed > ( mTimeFade + mTimeView ) ) {
  			// swap the two textures
  			gl::TextureRef temp = mFront;
  			mFront = mBack;
  			mBack = temp;
  			// keep track of how long the previous image was shown,
  			// so there will be no visual indication that we swapped the images
  			mTimeDuration = elapsed;
  			// start fading in
  			mTimeSwapped = getElapsedSeconds();
  			// proceed to next texture
  			mIndex = ( mIndex + 1 ) % mUrls.size();
  		}
  	}
  }
  
  void FlickrImageViewerApp::draw()
  {
  	// clear out the window with black
  	gl::clear( Color( 0, 0, 0 ) );
  
  	// calculate elapsed time in seconds (since last swap)
  	double elapsed = ( getElapsedSeconds() - mTimeSwapped );
  	// calculate crossfade alpha value
  	double fade = ci::math<double>::clamp( elapsed / mTimeFade, 0.0, 1.0 );
  	// calculate zoom factor
  	float zoom = getWindowWidth() / 600.0f * 0.025f;
  
  	// draw back image
  	if( mBack ) {
  		Rectf image = mBack->getBounds();
  		Rectf window = getWindowBounds();
  		float sx = window.getWidth() / image.getWidth();                                      // scale width to fit window
  		float sy = window.getHeight() / image.getHeight();                                    // scale height to fit window
  		float s = ci::math<float>::max( sx, sy ) + zoom * (float)( elapsed + mTimeDuration ); // fit window and zoom over time
  		float w = image.getWidth() * s;                                                       // resulting width
  		float h = image.getHeight() * s;                                                      // resulting height
  		float ox = -0.5f * ( w - window.getWidth() );                                         // horizontal position to keep image centered
  		float oy = -0.5f * ( h - window.getHeight() );                                        // vertical position to keep image centered
  		image.set( ox, oy, ox + w, oy + h );
  
  		gl::color( Color( 1, 1, 1 ) );
  		gl::draw( mBack, image );
  	}
  
  	// draw front image
  	if( mFront ) {
  		Rectf image = mFront->getBounds();
  		Rectf window = getWindowBounds();
  		float sx = window.getWidth() / image.getWidth();
  		float sy = window.getHeight() / image.getHeight();
  		float s = ci::math<float>::max( sx, sy ) + zoom * (float)elapsed;
  		float w = image.getWidth() * s;
  		float h = image.getHeight() * s;
  		float ox = -0.5f * ( w - window.getWidth() );
  		float oy = -0.5f * ( h - window.getHeight() );
  		image.set( ox, oy, ox + w, oy + h );
  
  		gl::color( ColorA( 1, 1, 1, (float)fade ) );
  		gl::enableAlphaBlending();
  		gl::draw( mFront, image );
  		gl::disableAlphaBlending();
  	}
  }
  
  void FlickrImageViewerApp::keyDown( KeyEvent event )
  {
  	switch( event.getCode() ) {
  	case KeyEvent::KEY_ESCAPE:
  		quit();
  		break;
  	case KeyEvent::KEY_a:
  		// toggle synchronous and asynchronous loading
  		mAsynchronous = !mAsynchronous;
  		if( mAsynchronous )
  			console() << "Asynchronous loading ENABLED." << std::endl;
  		else
  			console() << "Asynchronous loading DISABLED." << std::endl;
  		break;
  	case KeyEvent::KEY_f:
  		// toggle full screen
  		setFullScreen( !isFullScreen() );
  		break;
  	case KeyEvent::KEY_v:
  		gl::enableVerticalSync( !gl::isVerticalSyncEnabled() );
  		break;
  	}
  }
  
  CINDER_APP( FlickrImageViewerApp, RendererGl, &FlickrImageViewerApp::prepare )