/*
 * client/Renderer_default.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 "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_default.h"

static void setupHorizontalSurfaceTexture( const class Sector * sect,
                                           Sector::SurfaceType which,
                                           bool textype ) {

        const class ClientSector * cs = sect->clientSector;

        // texture

        if ( textype ) {
                glBindTexture( GL_TEXTURE_2D,
                               cs->to[ which ] ? 
                               cs->to[ which ]->getIndex() : 0 );
                glTexCoordPointer( 2, GL_FLOAT, 0, cs->textureVerts[ which ] );

        }

        // lightmap

        else {
                
                const class Lightmap * lm = which == 0 ? sect->floor_lightmap : sect->ceiling_lightmap;
                
                if ( lm ) {

                        // 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 );
                        }
                        else {
                                if ( lm->needsRebind() )
                                        lm->clientLightmap->rebindTexture( lm );
                                
                                glBindTexture( GL_TEXTURE_2D, lm->clientLightmap->_tex );
                        }
                        
                        glTexCoordPointer( 2, GL_FLOAT, 0, cs->lmVerts );
                }   
                else {
                        glBindTexture( GL_TEXTURE_2D, 0 );
                        glTexCoordPointer( 2, GL_FLOAT, 0, cs->textureVerts[ which ] );

                }
        }
}

                                     

static void renderSectorSurfaces( const class Sector * pSect,
                                  const Array<Sector *> & sectors,
                                  const float eye_z,
                                  const bool textype,
                                  const bool handle_specials ) {
        
        // exceeding this amount does not produce an error message, 
        // but possibly visual artifacts

        static const int MAX_SPECIALS = 8;
        
        static const class Sector * specialFloors[ MAX_SPECIALS ];
        static const class Sector * specialCeilings[ MAX_SPECIALS ];

        int num_spec_floors = 0;
        int num_spec_ceils = 0;
        
        for ( int i = 0; i < pSect->_visibleFloors.length; ++i ) {
                const class Sector * vsect = sectors[ pSect->_visibleFloors[i] ];
                if ( eye_z > vsect->getFloorHeight() ) {
                        
                        if ( handle_specials && vsect->spec && vsect->spec->isLift() && 
                             num_spec_floors < MAX_SPECIALS ) {
                                specialFloors[ num_spec_floors++ ] = vsect;
                                continue;
                        }
                        
                        setupHorizontalSurfaceTexture( vsect, Sector::FLOOR, textype );
                        Renderer::drawHorizontalSurface( vsect, 0 );

                }
        }

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

                        if (  handle_specials && vsect->spec && vsect->spec->isDoor() &&
                              num_spec_ceils < MAX_SPECIALS ) {
                                specialCeilings[ num_spec_ceils++ ] = vsect;
                                continue;
                        }
                        
                        setupHorizontalSurfaceTexture( vsect, Sector::CEILING, textype );
                        Renderer::drawHorizontalSurface( vsect, 1 );
                }
        }

        setupHorizontalSurfaceTexture( pSect, Sector::FLOOR, textype );
        Renderer::drawHorizontalSurface( pSect, 0 );
        setupHorizontalSurfaceTexture( pSect, Sector::CEILING, textype );
        Renderer::drawHorizontalSurface( pSect, 1 );
        
        if ( handle_specials && ( num_spec_floors || num_spec_ceils ) ) {
                glDepthFunc( GL_LESS );
                
                if ( num_spec_floors ) {
                        for ( int i = 0; i < num_spec_floors; ++i ) {
                                setupHorizontalSurfaceTexture( specialFloors[i], Sector::FLOOR, textype );
                                Renderer::drawHorizontalSurface( specialFloors[i], 0 ); 
                        }
                }

                if ( num_spec_ceils ) {
                        for ( int i = 0; i < num_spec_ceils; ++i ) {
                                setupHorizontalSurfaceTexture( specialCeilings[i], Sector::CEILING, textype );
                                Renderer::drawHorizontalSurface( specialCeilings[i], 1 ); 
                        }
                }
        } 
}

/**
 * Setup the texture parameters for drawing a single-textured floor or ceiling surface
 * @param sect the sector to draw
 * @param which floor (0) or ceiling (1)
 * @param textype setup the texture (true) or lightmap( false)
 */


void Renderer_default::drawSectors( const class Zone * zone,
                                    const class Player * player,
                                    const class Sector * pSect ) {

        const float eye_z = player->getPos()[2] + player->getEyeLevel();
        const Array<Sector *> & sectors = zone->getSectors();

        glEnable( GL_DEPTH_TEST );
        
        if ( pSect == 0 || cl_state.use_pvs == false || zone->isVisLoaded() == false ) {

                glDepthFunc( GL_LESS );
       
                if ( cl_state.use_lightmaps ) {
                        
                        for ( int i = 0; i < sectors.length; ++i ) {
                                
                                setupHorizontalSurfaceTexture( sectors[i], Sector::FLOOR, false );
                                Renderer::drawHorizontalSurface( sectors[i], 0 );
                                setupHorizontalSurfaceTexture( sectors[i], Sector::CEILING, false );
                                Renderer::drawHorizontalSurface( sectors[i], 1 );
                        }

                }

                if ( cl_state.use_textures ) {

                        if ( cl_state.use_lightmaps ) {
                                glDepthMask( GL_FALSE );
                                glDepthFunc( GL_EQUAL );
                                glEnable( GL_BLEND );
                                glBlendFunc( GL_ZERO, GL_SRC_COLOR );
                        }
                        
                        for ( int i = 0; i < sectors.length; ++i ) {
                                
                                setupHorizontalSurfaceTexture( sectors[i], Sector::FLOOR, true );
                                Renderer::drawHorizontalSurface( sectors[i], 0 );
                                setupHorizontalSurfaceTexture( sectors[i], Sector::CEILING, true );
                                Renderer::drawHorizontalSurface( sectors[i], 1 );
                                
                        }
                        if ( cl_state.use_lightmaps ) {
                                glDepthMask( GL_TRUE );
                                glDepthFunc( GL_LESS );
                                glDisable( GL_BLEND );
                        }
                }

                return;
        }
        
        // use the pvs
        
        glDepthFunc( GL_ALWAYS );

        if ( cl_state.use_lightmaps ) {
                
                renderSectorSurfaces( pSect, sectors, eye_z, false, true );
                
                if ( cl_state.use_textures ) {
                        
                        glDepthFunc( GL_LEQUAL );
                        glDepthMask( GL_FALSE );
                                        
                        glEnable( GL_BLEND );
                        glBlendFunc( GL_ZERO, GL_SRC_COLOR );

                        renderSectorSurfaces( pSect, sectors, eye_z, true, false );

                        glDepthMask( GL_TRUE );
                        glDisable( GL_BLEND );
                }

        }
        else if ( cl_state.use_textures ) {
                
                renderSectorSurfaces( pSect, sectors, eye_z, true, true );
        }

        glDepthFunc( GL_LESS );
        glEnable( GL_DEPTH_TEST );
      
}
/**
 * Setup the texture and texture coord pointer for this wall section
 *
 * @param side the SideDef to be drawn
 * @param which the section of the side to draw [0-2]
 * @param textype true for texture false for lightmap
 */

void setupWallSurfaceTexture( const class SideDef * side,
                              int which,
                              bool textype ) {

        // normal texture
        
        if ( textype ) {
                glBindTexture( GL_TEXTURE_2D, 
                               side->clientSide->textures[ which ] ?
                               side->clientSide->textures[ which ]->getIndex() : 0 );
                glTexCoordPointer( 2, GL_FLOAT, 0, side->clientSide->texVerts[ which ] );
        }
        
        // lightmap

        else {
                class Lightmap * lm = side->lightmaps[ which ];
                
                if ( lm && lm->clientLightmap ) {
                        
                        if ( lm->needsRebind() )
                                lm->clientLightmap->rebindTexture( lm );
                        
                        glBindTexture( GL_TEXTURE_2D, lm->clientLightmap->_tex );
                        glTexCoordPointer( 2, GL_FLOAT, 0, side->clientSide->lmVerts[which] );
                }
                else {
                        glBindTexture( GL_TEXTURE_2D, 0 );
                        glTexCoordPointer( 2, GL_FLOAT, 0, side->clientSide->texVerts[ which ] );
                }
        }
}

/**
 * Draw all visible sections of the given side (up to 3 sections), using a
 * single texturing pass.
 * 
 * @param side the SideDef to be drawn
 * @param textype true if texture, false if lightmap
 */

static void drawSideSingleTex( const class SideDef * side,
                               bool textype  ) {
        
        glColor3f( 1, 1, 1 );
        
        for ( int i = 0; i < 3; ++i ) {
                
                if ( side->clientSide->verts[ i ] == 0 )
                        continue;
                
                setupWallSurfaceTexture( side, i, textype );
                glVertexPointer( 3, GL_FLOAT, 0, side->clientSide->verts[ i ] );
                glDrawArrays( GL_QUADS, 0, 4 );

                ++g_state.num_surfs;
        }
}


/**
 * 
 */
static void renderWallSurfaces( const class Sector * pSect,
                                         const Array< class LineDef * > & drawLines,
                                         const Array< class LineDef * > & lines,
                                         const GLenum depth_type,
                                         const bool textype ) {
        glDepthFunc( depth_type );
        
        for ( int i = 0; i < drawLines.length; ++i ) {
                        
                class LineDef * line = drawLines[ i ];
                        
                drawSideSingleTex( line->sides[0], textype );
                        
                if ( line->sides[1] ) {
                        drawSideSingleTex( line->sides[1], textype );
                }
        }
                
        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 ) {
                                drawSideSingleTex( line->sides[0], textype );
                        }
                        else {
                                drawSideSingleTex( line->sides[1], textype );
                        }
                }
        }
}


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

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

        //
        // draw all the potentially visible walls
        //

        glEnable( GL_DEPTH_TEST );
        glDepthFunc( GL_LESS );
                
        if ( cl_state.use_lightmaps ) {
                renderWallSurfaces( pSect, drawLines, lines, GL_LESS, false );

                if ( cl_state.use_textures ) {

                        glDepthMask( GL_FALSE );
                        glEnable( GL_BLEND );
                        glBlendFunc( GL_ZERO, GL_SRC_COLOR );
                        
                        renderWallSurfaces( pSect, drawLines, lines, GL_EQUAL, true );

                        glDepthMask( GL_TRUE );
                        glDisable( GL_BLEND );

                }
        }
        else if ( cl_state.use_textures ) {
                renderWallSurfaces( pSect, drawLines, lines, GL_LESS, true );
        }
        
        glDepthFunc( GL_LESS );
}


void Renderer_default::setup() {
        glEnable( GL_TEXTURE_2D );
        glBindTexture( GL_TEXTURE_2D, 0 );
        glEnableClientState( GL_TEXTURE_COORD_ARRAY );
        glEnableClientState( GL_VERTEX_ARRAY );
}

void Renderer_default::cleanup() {
        glDisable( GL_TEXTURE_2D );
        glDisableClientState( GL_TEXTURE_COORD_ARRAY );
        glDisableClientState( GL_VERTEX_ARRAY );
}

