/*
 * client/Renderer_multitex.cc:
 *
 * Copyright (C) 2000 John Watson, jwatson@tempusmud.com
 * 
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 * 
 */

#include <GL/gl.h>

#include <vector>

#include "../gl_stubs.h"

#include "ClientState.h"
#include "ClientSector.h"
#include "ClientSide.h"
#include "ClientLightmap.h"

#include "../server/Sector.h"
#include "../server/SectorSpecial.h"
#include "../server/Player.h"
#include "../server/Zone.h"
#include "../server/Lightmap.h"
#include "../server/LineDef.h"
#include "../server/SideDef.h"

#include "../util/TextureObject.h"

#include "../GraphicsState.h"

#include "Renderer_multitex.h"

static GLenum _lm_tex_unit = GL_TEXTURE1_ARB;

#ifdef DEBUG
#define dprint(a) cout << " @@ " << a << endl;
#else
#define dprint(a)
#endif

/**
 * Setup the texture parameters for drawing a multi-textures floor or ceiling surface
 * The lightmap and texture are blended together.
 *
 * @param sect the sector to draw
 * @param which floor (0) or ceiling (1)
 */

void setupHorizontalSurfaceMultiTexture( const class Sector * sect, 
                                         Sector::SurfaceType which ) {
        

        dprint( "setupHorizontalSurfaceMultiTexture" );
        
        const class ClientSector * cs = sect->clientSector;

        if ( cl_state.use_textures ) {
                dprint( "setupHorizontalSurfaceMultiTexture--textures" );

                glActiveTextureARB_ptr( GL_TEXTURE0_ARB );
                glClientActiveTextureARB_ptr( GL_TEXTURE0_ARB );
                
                glBindTexture( GL_TEXTURE_2D,
                               cs->to[ which ] ? 
                               cs->to[ which ]->getIndex() : 0 );
                
                glTexCoordPointer( 2, GL_FLOAT, 0, cs->textureVerts[ which ] );
        }
        
        if ( cl_state.use_lightmaps ) {
                dprint( "setupHorizontalSurfaceMultiTexture--lightmaps" );

                glActiveTextureARB_ptr( _lm_tex_unit );
                glClientActiveTextureARB_ptr( _lm_tex_unit );

                const class Lightmap * lm = which == 0 ? sect->floor_lightmap : sect->ceiling_lightmap;

                if ( lm ) {

                        glTexCoordPointer( 2, GL_FLOAT, 0, cs->lmVerts );

                        // fullbright textures start with '_' for now
                        // FIXME: make a fullbright flag for textures
                        
                        if ( sect->getTex( which ) && sect->getTex( which )[9] == '_' ) {
                                glBindTexture( GL_TEXTURE_2D, 0 );
                                return;
                        }

                        if ( lm->needsRebind() ) {
                                lm->clientLightmap->rebindTexture( lm );
                        }
                        
                        glBindTexture( GL_TEXTURE_2D, lm->clientLightmap->_tex );
                }
                
                else {
                        glTexCoordPointer( 2, GL_FLOAT, 0, cs->textureVerts[ which ] );
                        glBindTexture( GL_TEXTURE_2D, 0 );
                }

        }
        
        dprint( "...setupHorizontalSurfaceMultiTexture" );


}

void Renderer_multitex::drawSectors( const class Zone * zone,
                                     const class Player * player,
                                     const class Sector * pSect ) {
        
        dprint( "drawSectors" );

        const float eye_z = player->getPos()[2] + player->getEyeLevel();
        const Array<Sector *> & sectors = zone->getSectors();
        
        if ( pSect == 0 || cl_state.use_pvs == false || zone->isVisLoaded() == false ) {
                for ( int i = 0; i < sectors.length; ++i ) {
                        setupHorizontalSurfaceMultiTexture( sectors[i], Sector::FLOOR );
                        drawHorizontalSurface( sectors[i], 0 );
                        setupHorizontalSurfaceMultiTexture( sectors[i], Sector::CEILING );
                        drawHorizontalSurface( sectors[i], 1 );
                }

                return;
        }
        
        vector<const class Sector *> specialFloors;
        vector<const class Sector *> specialCeilings;
        
        glEnable( GL_DEPTH_TEST );
        glDepthFunc( GL_ALWAYS );
                        
        for ( int i = 0; i < pSect->_visibleFloors.length; ++i ) {
                class Sector * vsect = sectors[ pSect->_visibleFloors[i] ];
                if ( eye_z > vsect->getFloorHeight() ) {
                        if ( vsect->spec && vsect->spec->isLift() ) {
                                specialFloors.push_back( vsect );
                                continue;
                        }
                                        
                        setupHorizontalSurfaceMultiTexture( vsect, Sector::FLOOR );
                        drawHorizontalSurface( vsect, 0 );
                }
        }

        for ( int i = 0; i < pSect->_visibleCeilings.length; ++i ) {
                class Sector * vsect = sectors[ pSect->_visibleCeilings[i] ];
                if ( eye_z < vsect->getCeilingHeight() ) {

                        if ( vsect->spec && vsect->spec->isDoor() ) {
                                specialCeilings.push_back( vsect );
                                continue;
                        }
                                        
                        setupHorizontalSurfaceMultiTexture( vsect, Sector::CEILING );
                        drawHorizontalSurface( vsect, 1 );
                }
        }
        
        dprint( "drawSectors--pSect" );

        setupHorizontalSurfaceMultiTexture( pSect, Sector::FLOOR );
        drawHorizontalSurface( pSect, 0 );
        setupHorizontalSurfaceMultiTexture( pSect, Sector::CEILING );
        drawHorizontalSurface( pSect, 1 );

        glDepthFunc( GL_LESS );
                        
        if ( specialFloors.size() ) {
                for ( unsigned int i = 0; i < specialFloors.size(); ++i ) {
                        setupHorizontalSurfaceMultiTexture( specialFloors[i], Sector::FLOOR );
                        drawHorizontalSurface( specialFloors[i], 0 ); 
                }
        }

        if ( specialCeilings.size() ) {
                for ( unsigned int i = 0; i < specialCeilings.size(); ++i ) {
                        setupHorizontalSurfaceMultiTexture( specialCeilings[i], Sector::CEILING );
                        drawHorizontalSurface( specialCeilings[i], 1 ); 
                }
        }

        dprint( "...drawSectors" );
}


/**
 * Draw all visible sections of the given side (up to 3 sections), using a
 * single MULTItexture pass.  The multitexture pass draws lightmap and texture,
 * if enabled.
 * 
 * @param side the SideDef to be drawn
 */

static void drawSideMultiTex( const class SideDef * side ) {

        dprint( "drawSideMultiTex" );

        glColor3f( 1, 1, 1 );
        
        for ( int i = 0; i < 3; ++i ) {

                if ( side->clientSide->verts[i] == 0 )
                        continue;
                
                if ( cl_state.use_textures ) {

                        glActiveTextureARB_ptr( GL_TEXTURE0_ARB );
                
                        glBindTexture( GL_TEXTURE_2D, 
                                       side->clientSide->textures[ i ] ?
                                       side->clientSide->textures[ i ]->getIndex() : 0 );
                        
                        glClientActiveTextureARB_ptr( GL_TEXTURE0_ARB );
                        glTexCoordPointer( 2, GL_FLOAT, 0, side->clientSide->texVerts[ i ] );
                        ++g_state.num_surfs;
                }
                
                const class Lightmap * lm = side->lightmaps[i];

                if ( lm && lm->clientLightmap ) {
                        
                        glActiveTextureARB_ptr( _lm_tex_unit );
                        
                        if ( lm->needsRebind() )
                                lm->clientLightmap->rebindTexture( lm );

                        glBindTexture( GL_TEXTURE_2D, lm->clientLightmap->_tex );
                        glClientActiveTextureARB_ptr( _lm_tex_unit );
                        glTexCoordPointer( 2, GL_FLOAT, 0, side->clientSide->lmVerts[ i ] );
                        ++g_state.num_surfs;
                }

                glVertexPointer( 3, GL_FLOAT, 0, side->clientSide->verts[ i ] );

                glDrawArrays( GL_QUADS, 0, 4 );

        }
        dprint( "...drawSideMultiTex" );
}



void Renderer_multitex::drawWalls( const class Zone * zone,
                                   const class Player * player,
                                   const class Sector * pSect,
                                   const Array< class LineDef *> & drawLines ) {

        dprint( "drawWalls" );

        const Array<class LineDef *> & lines = zone->getLines();

        glEnable( GL_DEPTH_TEST );
        glDepthFunc( GL_LESS );
                
        for ( int i = 0; i < drawLines.length; ++i ) {
                        
                class LineDef * line = drawLines[ i ];
                        
                drawSideMultiTex( line->sides[0] );
                        
                if ( line->sides[1] ) {
                        drawSideMultiTex( line->sides[1] );
                }
        }
                
        if ( pSect ) {

                //
                // now draw the walls of the current sector with depth test disabled
                //
                
                glDepthFunc( GL_ALWAYS );
                
                for ( int i = 0; i < pSect->_myLines.length; ++i ) {
                        class LineDef * line = lines[ pSect->_myLines[i] ];
                                
                        if ( line->modified ) {
                                class SideDef ** sides = line->sides;
                                        
                                sides[0]->clientSide->adjustHeights( sides[0], sides[1] );
                                if ( sides[1] )
                                        sides[1]->clientSide->adjustHeights( sides[1], sides[0] );
                                line->modified = false;
                        }

                        if ( line->sides[0]->sector == pSect ) {
                                drawSideMultiTex( line->sides[0] );
                        }
                        else {
                                drawSideMultiTex( line->sides[1] );
                        }
                }

                glDepthFunc( GL_LESS );
        }
        dprint( "...drawWalls" );
}

void Renderer_multitex::setup() {

        glEnableClientState( GL_VERTEX_ARRAY );
        glEnableClientState( GL_TEXTURE_COORD_ARRAY );

        glActiveTextureARB_ptr( GL_TEXTURE0_ARB );
        glBindTexture( GL_TEXTURE_2D, 0 );
                
        if ( cl_state.use_textures ) {
                glEnable( GL_TEXTURE_2D );
                _lm_tex_unit = GL_TEXTURE1_ARB;
                glActiveTextureARB_ptr( _lm_tex_unit );             
                glBindTexture( GL_TEXTURE_2D, 0 );
        }
        else {
                _lm_tex_unit = GL_TEXTURE0_ARB;
        }
                
                
        if ( cl_state.use_lightmaps ) {
                glEnable( GL_TEXTURE_2D );
        }
        else {
                glDisable( GL_TEXTURE_2D );
        }
}

void Renderer_multitex::cleanup() {

        glDisableClientState( GL_VERTEX_ARRAY );
        glDisableClientState( GL_TEXTURE_COORD_ARRAY );  

        if ( cl_state.use_lightmaps ) {
                glActiveTextureARB_ptr( _lm_tex_unit );
                glBindTexture( GL_TEXTURE_2D, 0 );
                glDisable( GL_TEXTURE_2D );
        }

        if ( cl_state.use_textures ) {
                glActiveTextureARB_ptr( GL_TEXTURE0_ARB );
                glBindTexture( GL_TEXTURE_2D, 0 );
                glDisable( GL_TEXTURE_2D );
        }
}
