/* -*-c++-*- Producer - Copyright (C) 2001-2004  Don Burns
 *
 * This library is open source and may be redistributed and/or modified under
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 *
 * This library 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
 * OpenSceneGraph Public License for more details.
 */

#ifndef PRODUCER_CAMERA_CONFIG
#define PRODUCER_CAMERA_CONFIG

#include <stdio.h>

#include <Producer/Export>
#include <Producer/Referenced>

#include <string>
#include <map>

#include <Producer/Types>
#include <Producer/VisualChooser>
#include <Producer/RenderSurface>
#include <Producer/Camera>
#include <Producer/InputArea>

namespace Producer {

class PR_EXPORT CameraConfig : public Referenced
{
    public :
        CameraConfig() :
            _can_add_visual_attributes(false),
            _current_render_surface(NULL),
            _can_add_render_surface_attributes(false),
            _current_camera(NULL),
            _can_add_camera_attributes(false),
            _input_area(NULL),
            _can_add_input_area_entries(false),
            _offset_shearx(0.0f),
            _offset_sheary(0.0f)
    
        {
            _offset_matrix[0] = 1.0; _offset_matrix[1] = 0.0; _offset_matrix[2] = 0.0; _offset_matrix[3] = 0.0;
            _offset_matrix[4] = 0.0; _offset_matrix[5] = 1.0; _offset_matrix[6] = 0.0; _offset_matrix[7] = 0.0;
            _offset_matrix[8] = 0.0; _offset_matrix[9] = 0.0; _offset_matrix[10] = 1.0; _offset_matrix[11] = 0.0;
            _offset_matrix[12] = 0.0; _offset_matrix[13] = 0.0; _offset_matrix[14] = 0.0; _offset_matrix[15] = 1.0;
        }


        void beginVisual( void )
        {
            _current_visual_chooser = new VisualChooser;
            _can_add_visual_attributes = true;
        }

        void beginVisual( const char * name )
        {
            std::pair<std::map<std::string,VisualChooser *>::iterator,bool> res = 
              _visual_map.insert(std::pair<std::string,VisualChooser *>(std::string(name), new VisualChooser));
            _current_visual_chooser = (res.first)->second;
            _can_add_visual_attributes = true;
        }

        void setVisualSimpleConfiguration( void )
        {
             if( !_current_visual_chooser.valid() || _can_add_visual_attributes == false )
             {
                 std::cerr << "CameraConfig::setVisualSimpleConfiguration() : ERROR no current visual\n";
                 return;
             }
             _current_visual_chooser->setSimpleConfiguration();
        }

        void setVisualByID( unsigned int id )
        {
             if( !_current_visual_chooser.valid() || _can_add_visual_attributes == false )
             {
                 std::cerr << "CameraConfig::setVisualByID(id) : ERROR no current visual\n";
                 return;
             }
             _current_visual_chooser->setVisualID( id );
        }

        void addVisualAttribute( VisualChooser::AttributeName token, int param )
        {
             if( !_current_visual_chooser.valid() || _can_add_visual_attributes == false )
             {
                 std::cerr << "CameraConfig::addVisualAttribute(token,param) : ERROR no current visual\n";
                 return;
             }
             _current_visual_chooser->addAttribute( token, param );
        }

        void addVisualAttribute( VisualChooser::AttributeName token )
        {
             if( !_current_visual_chooser.valid()  || _can_add_visual_attributes == false)
             {
                 std::cerr << "CameraConfig::addVisualAttribute(token) : ERROR no current visual\n";
                 return;
             }
             _current_visual_chooser->addAttribute( token );
        }

        void addVisualExtendedAttribute( unsigned int token )
        {
             if( !_current_visual_chooser.valid()  || _can_add_visual_attributes == false)
             {
                 std::cerr << "CameraConfig::addVisualExtendedAttribute(token) : ERROR no current visual\n";
                 return;
             }
             _current_visual_chooser->addExtendedAttribute( token );
        }

        void addVisualExtendedAttribute( unsigned int token, int param )
        {
             if( !_current_visual_chooser.valid()  || _can_add_visual_attributes == false)
             {
                 std::cerr << "CameraConfig::addVisualExtendedAttribute(token, param) : ERROR no current visual\n";
                 return;
             }
             _current_visual_chooser->addExtendedAttribute( token, param );
        }

        void endVisual( void )
        {
            _can_add_visual_attributes = false;
        }

        VisualChooser *findVisual( const char *name )
        {
            std::map<std::string, VisualChooser *>::iterator p;
            p = _visual_map.find( std::string(name) );
            if( p == _visual_map.end() )
                return NULL;
            else
                    return (*p).second;
        }

        bool parseFile( const std::string &file );

        void beginRenderSurface( const char *name )
        {
            std::pair<std::map<std::string,  Producer::ref_ptr<RenderSurface> >::iterator,bool> res = 
              _render_surface_map.insert(std::pair<std::string, Producer::ref_ptr< RenderSurface> >(
                                              std::string(name), 
                                              new RenderSurface));
            _current_render_surface = (res.first)->second.get();
            _current_render_surface->setWindowName( std::string(name) );
            _can_add_render_surface_attributes = true;
        }

        void setRenderSurfaceVisualChooser( const char *name )
        {
            VisualChooser *vc = findVisual( name );
            if( vc != NULL && _current_render_surface != NULL )
                _current_render_surface->setVisualChooser( vc );
        }

        void setRenderSurfaceVisualChooser( void )
        {
            if( _current_render_surface != NULL && _current_visual_chooser.valid() )
                _current_render_surface->setVisualChooser( _current_visual_chooser.get() );
        }

        void setRenderSurfaceWindowRectangle( int x, int y,  unsigned int width, unsigned int height )
        {
            if( _current_render_surface != NULL )
                _current_render_surface->setWindowRectangle( x, y, width, height );
        }

        void setRenderSurfaceCustomFullScreenRectangle( int x, int y, unsigned int width, unsigned int height )
        {
            if( _current_render_surface != NULL )
                _current_render_surface->setCustomFullScreenRectangle( x, y, width, height );
        }

        void setRenderSurfaceHostName( const std::string &name )
        {
            if( _current_render_surface != NULL )
                _current_render_surface->setHostName( name );
        }

        void setRenderSurfaceDisplayNum( int n )
        {
            if( _current_render_surface != NULL )
                _current_render_surface->setDisplayNum( n );
        }

        void setRenderSurfaceScreen( int n )
        {
            if( _current_render_surface != NULL )
                _current_render_surface->setScreenNum( n );
        }

        void setRenderSurfaceBorder( bool flag )
        {
            if( _current_render_surface != NULL )
                _current_render_surface->useBorder( flag );
        }

        void setRenderSurfaceInputRectangle( float x0, float x1, float y0, float y1 )
        {
            if( _current_render_surface != NULL )
                _current_render_surface->setInputRectangle( 
                    RenderSurface::InputRectangle(x0,x1,y0,y1) );
        }

        void endRenderSurface( void )
        {
            _can_add_render_surface_attributes = false;
        }

        RenderSurface *findRenderSurface( const char *name )
        {
            std::map<std::string,  Producer::ref_ptr<RenderSurface> >::iterator p;
            p = _render_surface_map.find( std::string(name) );
            if( p == _render_surface_map.end() )
                return NULL;
            else
                return (*p).second.get();
        }

        unsigned int getNumberOfRenderSurfaces()
        {
            return _render_surface_map.size();
        }

        RenderSurface *getRenderSurface( unsigned int index )
        {
            if( index >= _render_surface_map.size() ) 
                    return NULL;
            std::map <std::string,  Producer::ref_ptr<RenderSurface> >::iterator p;

            unsigned int i = 0;
            for( p = _render_surface_map.begin(); p != _render_surface_map.end(); p++ )
                if( i++ == index ) 
                        break;
            if(  p == _render_surface_map.end() )
                    return NULL;
            return (p->second.get());
        }

        void addCamera( std::string name, Camera *camera )
        {
            std::pair<std::map<std::string, Producer::ref_ptr<Camera> >::iterator,bool> res =
              _camera_map.insert(std::pair<std::string, Producer::ref_ptr<Camera> >(name, camera));
            _current_camera = (res.first)->second.get();
            _can_add_camera_attributes = true;

            RenderSurface *rs = camera->getRenderSurface();
            if( rs->getWindowName() == Producer::RenderSurface::defaultWindowName )
            {
                char name[80];
                sprintf( name, "%s (%02d)", Producer::RenderSurface::defaultWindowName.c_str(), (int)_render_surface_map.size() );
                rs->setWindowName( name );
            }
            _render_surface_map.insert(std::pair<std::string, Producer::ref_ptr<RenderSurface> >( rs->getWindowName(), rs ));
        }


        void beginCamera( std::string name )
        {
            Camera *camera = new Camera;
            std::pair<std::map<std::string,  Producer::ref_ptr<Camera> >::iterator,bool> res = 
              _camera_map.insert(std::pair<std::string, Producer::ref_ptr<Camera> >(name, camera));
            _current_camera = (res.first)->second.get();
            _can_add_camera_attributes = true;
        }

        void setCameraRenderSurface( const char *name )
        {
            RenderSurface *rs = findRenderSurface( name );
            if( rs == NULL )
            {
                std::cerr << "setCameraRenderSurface(): No Render Surface by name of \"" << name << "\" was found!\n";
                return;
            }
            if( rs != NULL && _current_camera != NULL )
                _current_camera->setRenderSurface( rs );
        }

        void setCameraRenderSurface( void )
        {
            if( _current_camera != NULL && _current_render_surface != NULL )
                _current_camera->setRenderSurface( _current_render_surface.get() );
        }

        void setCameraProjectionRectangle( float x0, float x1, float y0, float y1 )
        {
            if( _current_camera != NULL )
                _current_camera->setProjectionRectangle( x0, x1, y0, y1 );
        }

        void setCameraProjectionRectangle( int x0, int x1, int y0, int y1 )
        {
            if( _current_camera != NULL )
                _current_camera->setProjectionRectangle( x0, x1, y0, y1 );
        }

    void setCameraOrtho( float left, float right, float bottom, float top, float nearClip, float farClip,
                                float xshear=0.0, float yshear=0.0 )
        {
            if( _current_camera != NULL )
                _current_camera->setLensOrtho( left, right, bottom, top, nearClip, farClip, xshear, yshear );
        }

        void setCameraPerspective( float hfov, float vfov, float nearClip, float farClip,
                                float xshear=0.0, float yshear=0.0 )
        {
            if( _current_camera != 0 )
                _current_camera->setLensPerspective( hfov, vfov, nearClip, farClip, xshear, yshear );
        }

        void setCameraFrustum( float left, float right, float bottom, float top, float nearClip, float farClip,
                                float xshear=0.0, float yshear=0.0 )
        {
            if( _current_camera != 0 )
                _current_camera->setLensFrustum( left, right, bottom, top, nearClip, farClip, xshear, yshear );
        }

        void setCameraLensShear( Matrix::value_type xshear, Matrix::value_type yshear )
        {
            if( _current_camera != NULL )
                _current_camera->setLensShear(xshear,yshear);
        }

        void beginCameraOffset()
        {
            Matrix::value_type id[] = {
                1, 0, 0, 0,
                0, 1, 0, 0,
                0, 0, 1, 0,
                0, 0, 0, 1
                };
            memcpy( _offset_matrix, id, sizeof(Matrix::value_type[16]));
            _offset_shearx = _offset_sheary = 0.0;
        }

        void rotateCameraOffset( Matrix::value_type deg, Matrix::value_type x, Matrix::value_type y, Matrix::value_type z );
        void translateCameraOffset( Matrix::value_type x, Matrix::value_type y, Matrix::value_type z );
        void scaleCameraOffset( Matrix::value_type x, Matrix::value_type y, Matrix::value_type z );
        void shearCameraOffset( Matrix::value_type shearx, Matrix::value_type sheary )
        {
            _offset_shearx = shearx;
            _offset_sheary = sheary;
        }


        void endCameraOffset()
        {
            _current_camera->setOffset( _offset_matrix, _offset_shearx, _offset_sheary );
        }

        void  endCamera( void )
        {
            _can_add_camera_attributes = false;
        }

        Camera *findCamera( const char *name )
        {
            std::map<std::string,  Producer::ref_ptr<Camera> >::iterator p;
            p = _camera_map.find( std::string(name) );
            if( p == _camera_map.end() )
                return NULL;
            else
                return (*p).second.get();
        }

        unsigned int getNumberOfCameras() const
        {
            return _camera_map.size();
        }
        
        const Camera *getCamera( unsigned int n ) const
        {
            if( n >= _camera_map.size() ) 
                    return NULL;

            unsigned int i;
            std::map <std::string,  Producer::ref_ptr<Camera> >::const_iterator p;
            for( i = 0, p = _camera_map.begin(); p != _camera_map.end(); p++ )
                if( i++ == n ) 
                        break;
            if(  p == _camera_map.end() )
                    return NULL;
            return p->second.get();
        }

        Camera *getCamera( unsigned int n )
        {
            if( n >= _camera_map.size() ) 
                    return NULL;

            unsigned int i;
            std::map <std::string,  Producer::ref_ptr<Camera> >::iterator p;
            for( i = 0, p = _camera_map.begin(); p != _camera_map.end(); p++ )
                if( i++ == n ) 
                        break;
            if(  p == _camera_map.end() )
                    return NULL;
            return p->second.get();
        }

        void beginInputArea()
        {
            _input_area = new Producer::InputArea;
            _can_add_input_area_entries = true;
        }

        void addInputAreaEntry( char *renderSurfaceName )
        {    
            Producer::RenderSurface *rs = findRenderSurface( renderSurfaceName );
            if( rs == NULL )
            {
                std::cerr << "setInputAreaEntry(): No Render Surface by name of \"" << renderSurfaceName << "\" was found!\n";
                return;
            }
            if( _input_area != NULL && _can_add_input_area_entries == true )
                _input_area->addRenderSurface( rs );
        }

        void endInputArea() 
        {
            _can_add_input_area_entries = false;
        }

        void setInputArea(Producer::InputArea *ia) { _input_area=ia; }

        Producer::InputArea *getInputArea() { return _input_area.get(); }

        const Producer::InputArea *getInputArea() const { return _input_area.get(); }

        void realize( void )
        {
            std::map <std::string,  Producer::ref_ptr<RenderSurface> >::iterator p;
            for( p = _render_surface_map.begin(); p != _render_surface_map.end(); p++ )
            {
                ((*p).second)->realize();
            }
        }

        bool defaultConfig()
        {
            if( getNumberOfCameras() != 0 ) return false;

            char *env = getenv( "PRODUCER_CONFIG_FILE" );
            if( env != NULL )
            {
                std::string file = findFile(env);
                return parseFile( file.c_str() );
            }

            unsigned int numScreens =  getNumberOfScreens();
            if( numScreens == 0 )
                return false;
                
            float xshear = float(numScreens-1);
            float yshear = 0.0;

            for( unsigned int i = 0; i < numScreens; i++ )
            {
              std::string name = "Screen" + i;
              std::pair<std::map<std::string, Producer::ref_ptr<Camera> >::iterator,bool> res = 
              _camera_map.insert(std::pair<std::string, Producer::ref_ptr<Camera> >(name, new Camera));
              ((res.first)->second)->getRenderSurface()->setScreenNum( i );
              ((res.first)->second)->setLensShear( xshear, yshear );

               RenderSurface *rs = ((res.first)->second)->getRenderSurface();
               rs->setWindowName( name );
               _render_surface_map.insert(std::pair<std::string, 
                    Producer::ref_ptr<RenderSurface> >( rs->getWindowName(), rs ));

              xshear -= 2.0;
            }
            return true;
        }        

        struct StereoSystemCommand
        {
            int _screen;
            std::string _setStereoCommand;
            std::string _restoreMonoCommand;

            StereoSystemCommand(int screen, std::string setStereoCommand, std::string restoreMonoCommand ):
                _screen(screen),
                _setStereoCommand(setStereoCommand),
                _restoreMonoCommand(restoreMonoCommand) {}
        };

        static std::string findFile( std::string );

        void addStereoSystemCommand( int screen, std::string stereoCmd, std::string monoCmd )
        {
            _stereoSystemCommands.push_back(StereoSystemCommand( screen, stereoCmd, monoCmd ));
        }

        const std::vector<StereoSystemCommand> &getStereoSystemCommands() { return _stereoSystemCommands; }

    protected:

        ~CameraConfig() {}

    private :

        std::map <std::string, VisualChooser *> _visual_map;
        Producer::ref_ptr< VisualChooser >_current_visual_chooser;
        bool _can_add_visual_attributes;

        std::map <std::string,  Producer::ref_ptr<RenderSurface > > _render_surface_map;
	    Producer::ref_ptr<RenderSurface> _current_render_surface;
        bool _can_add_render_surface_attributes;

        std::map <std::string,  Producer::ref_ptr< Camera > > _camera_map;
        Producer::ref_ptr<Camera> _current_camera;
        bool _can_add_camera_attributes;

        Producer::ref_ptr< Producer::InputArea > _input_area;
        bool _can_add_input_area_entries;

        unsigned int getNumberOfScreens();

        static bool fileExists(const std::string& );

        Matrix::value_type  _offset_matrix[16];
        Matrix::value_type _offset_shearx, _offset_sheary;

        std::vector<StereoSystemCommand> _stereoSystemCommands;
};

}

#endif
