/*
 * server/Zone_dlight.cc: methods for dynamic lighting
 *
 * 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.
 * 
 */


#ifdef WIN32
#include <windows.h>
#endif

#include <SDL/SDL.h>

#include <cstdio>
#include <cmath>
#include <string>
#include <iostream>
#include <iomanip>
#include <fstream>

#include "Zone.h"
#include "../util/VMath.h"
#include "Sector.h"
#include "SectorSpecial.h"
#include "LineDef.h"
#include "SideDef.h"
#include "Lightmap.h"
#include "LightSource.h"

void Zone::setUseDynamicLights( bool b ) {
        _useDynamicLights = b;

        updateDynamicLightmaps();
}

/** used internally for color values */
static int comps[3];
/** used internally for color values */
static int orig_comps[3];

/**
 * light a flat surface
 */

static void paintFlat( const int actual_range,
                       class Lightmap * lm,
                       const int * rel_pos, 
                       const int range ) {
        
        const int s_start = max( rel_pos[0] - range, 0 );
        const int t_start = max( rel_pos[1] - range, 0 );
        
        const int s_end = min( rel_pos[0] + range, (int) lm->size[0] );
        const int t_end = min( rel_pos[1] + range, (int) lm->size[1] );
        
        const int world_rel_pos[] = {
                rel_pos[0] * Lightmap::TEX_RES,
                rel_pos[1] * Lightmap::TEX_RES,
        };
        
        for ( int t = t_start, dt = world_rel_pos[1] - t*Lightmap::TEX_RES;
              t < t_end; 
              ++t, dt -= Lightmap::TEX_RES ) {
                
                int ddt = dt < 0 ? -dt : dt;

                unsigned char * p = lm->rgb_pixels + ( t * lm->size[0] + s_start ) * 3;

                for ( int s = s_start, ds = world_rel_pos[0] - s*Lightmap::TEX_RES; 
                      s < s_end; 
                      ++s, ds -= Lightmap::TEX_RES ) {
                        
                        int dds = ds < 0 ? -ds : ds;

                        int val = 0;

                        if ( dds > ddt )
                                val = dds + (ddt>>1);
                        else
                                val = ddt + (dds>>1);
                        
                        if ( val >= actual_range ) {
                                p += 3;
                                continue;
                        }

                        val = (actual_range - val);
                        
                        lm->setFlag( Lightmap::MODIFIED );
                        lm->setFlag( Lightmap::NEEDS_REBIND );

                        for ( int rgb_i = 0; rgb_i < 3; ++rgb_i ) {
                                if ( *p < orig_comps[ rgb_i ] ) {
                                        
                                        int addit = ( val * comps[ rgb_i ] ) >> 16;
                                        *p = (unsigned char) min( *p + addit, 
                                                                  orig_comps[ rgb_i ] );
                                }
                                ++p;
                        }
                }
        }     
}


void Zone::paintWallLightmaps( const class Entity * ent,
                               const int * lightPt,
                               const class LineDef * line ) const {
        
        const class LightSource * lt = ent->getLight();

        int i_adj;
        int actual_range;
        int line_len = (int) line->length;
        
        // vertical line
        if ( line->verts[0][0] == line->verts[1][0] ) {
                actual_range = lt->getIntensity() - (int) fabs( line->verts[0][0] - lightPt[0] );
                
                if ( actual_range <= 0 )
                        return;

                // pointing north
                if ( line->verts[1][1] > line->verts[0][1] ) {
                        i_adj = (int) ( lightPt[1] - line->verts[0][1] );
                }
                // pointing south
                else {
                        i_adj = (int) ( line->verts[0][1] - lightPt[1] );
                }
        }

        // horizontal line
        else if ( line->verts[0][1] == line->verts[1][1] ) {
                actual_range = lt->getIntensity() - (int) fabs( line->verts[0][1] - lightPt[1] );
                
                if ( actual_range <= 0 )
                        return;
        
                // pointing east
                if ( line->verts[1][0] > line->verts[0][0] ) {
                        i_adj = (int) ( lightPt[0] - line->verts[0][0] );
                }
                // pointing west
                else {
                        i_adj = (int) ( line->verts[0][0] - lightPt[0] );
                }
        }

        // all others
        
        else {
                const float trans_verts[2] = {
                        line->verts[1][0] - line->verts[0][0],
                        line->verts[1][1] - line->verts[0][1]
                };
                
                const float trans_pos[2] = { lightPt[0] - line->verts[0][0],
                                             lightPt[1] - line->verts[0][1] };
                
                float adj;
                actual_range = lt->getIntensity() - (int) VMath::distFromLine2d( trans_pos, trans_verts, &adj, 0 );
                
                if ( actual_range <= 0 )
                        return;

                i_adj = (int) adj;
        }
        

        //
        // illuminate the front of the line
        //

        class SideDef * frontSide = line->sides[0];
        class SideDef * backSide = line->sides[1];
        
        int theRange = actual_range / Lightmap::TEX_RES;
        
        for ( int section_i = 0; section_i < 3; ++section_i ) {
                class Lightmap * lm = frontSide->lightmaps[ section_i ];

                if ( ! lm )
                        continue;

                int z_origin = (int) frontSide->getWallSectionZOrigin( backSide, section_i );

                if ( backSide && backSide->sector->spec ) {
                        if ( section_i == SideDef::UPPER && 
                             backSide->sector->spec->isDoor() ) {
                                z_origin -= backSide->sector->spec->travel - backSide->sector->spec->cur_travel;
                        }
                        // no special handling needed for LIFT
                }

                if ( frontSide->sector->spec &&
                     frontSide->sector->spec->isLift() ) {
                        z_origin -= frontSide->sector->spec->cur_travel;
                }
                
                int rel_pos[2] = {
                        i_adj / Lightmap::TEX_RES,
                        ( lightPt[2] - z_origin ) / Lightmap::TEX_RES
                };
                
                paintFlat( actual_range,
                           lm,
                           rel_pos,
                           theRange );
        }

        if ( backSide ) {
                
                i_adj = line_len - i_adj;

                for ( int section_i = 0; section_i < 3; ++section_i ) {
                        class Lightmap * lm = backSide->lightmaps[ section_i ];

                        if ( ! lm )
                                continue;

                        int z_origin = (int) backSide->getWallSectionZOrigin( frontSide, section_i );

                        if ( frontSide->sector->spec ) {
                                if ( section_i == SideDef::UPPER && 
                                     frontSide->sector->spec->isDoor() ) {
                                        z_origin -= frontSide->sector->spec->travel - frontSide->sector->spec->cur_travel;
                                }
                                // no special handling needed for LIFT
                        }

                        if ( backSide->sector->spec &&
                             backSide->sector->spec->isLift() ) {
                                z_origin -= backSide->sector->spec->cur_travel;
                        }

                        int rel_pos[2] = {
                                i_adj / Lightmap::TEX_RES,
                                ( lightPt[2] - z_origin ) / Lightmap::TEX_RES
                        };
                
                        paintFlat( actual_range,
                                   lm,
                                   rel_pos,
                                   theRange );
                }
        }
}

/**
 * attempts to illuminate the specified sector
 * you should do rough checks in advance, using the sectors offsets and extents
 */

void Zone::paintSectorLightmaps( const int lightrange,
                                 const int * lightPt,
                                 const class Sector * sect ) const {
        
        const int z_dist[2] = {
                lightrange - abs( lightPt[2] - sect->getFloorHeight() ),
                lightrange - abs( lightPt[2] - sect->getCeilingHeight() )
        };

        if ( z_dist[0] <= 0 && z_dist[1] <= 0 )
                return;
        
        //
        // position of the lightsource relative to the surface
        //

        const int i_extents[2] = {
                (int) sect->_extent[0],
                (int) sect->_extent[1]
        };

        int rel_pos[2] = {
                ( lightPt[0] - (int) sect->_offset[0] ),
                ( lightPt[1] - (int) sect->_offset[1] )
        };
        
        if ( rel_pos[0] > ( i_extents[0] + lightrange ) ||
             rel_pos[0] < -lightrange ||
             rel_pos[1] > ( i_extents[1] + lightrange ) ||
             rel_pos[1] < -lightrange )
                return;

        rel_pos[0] /= Lightmap::TEX_RES;
        rel_pos[1] /= Lightmap::TEX_RES;

        if ( z_dist[0] > 0 )
                paintFlat( z_dist[0], sect->floor_lightmap, rel_pos, 
                           z_dist[0] / Lightmap::TEX_RES );

        if ( z_dist[1] > 0 )
                paintFlat( z_dist[1], sect->ceiling_lightmap, rel_pos, 
                           z_dist[1] / Lightmap::TEX_RES );
}

void Zone::modifyDynamicLightmap( const class Entity *ent ) const {
        
        const class Sector * sect = ent->getSector();
        
        if ( ! sect || !sect->floor_lightmap )
                return;

        const class LightSource * lt = ent->getLight();
        
        const int lightPt[3] = {
                (int) ( ent->getPos()[0] + lt->getPos()[0] ),
                (int) ( ent->getPos()[1] + lt->getPos()[1] ),
                (int) ( ent->getPos()[2] + lt->getPos()[2] )
        };

        const int lightrange = lt->getIntensity();

        orig_comps[0] = ( (int) lt->getRGB()[ 0 ] );
        orig_comps[1] = ( (int) lt->getRGB()[ 1 ] );
        orig_comps[2] = ( (int) lt->getRGB()[ 2 ] );

        comps[0] = ( orig_comps[0] << 16 ) / lightrange;
        comps[1] = ( orig_comps[1] << 16 ) / lightrange;
        comps[2] = ( orig_comps[2] << 16 ) / lightrange;

        for ( int i = 0; i < sect->_visibleSectors.length; ++i ) {
                paintSectorLightmaps( lightrange, lightPt, _sectors[ sect->_visibleSectors[i] ] );
        }
        
        paintSectorLightmaps( lightrange, lightPt, sect );

        for ( int i = 0; i < sect->_visibleLines.length; ++i ) {
                class LineDef * line = _lines[ sect->_visibleLines[i] ];
                paintWallLightmaps( ent, lightPt, line );
        }

        for ( int i = 0; i < sect->_myLines.length; ++i ) {
                class LineDef * line = _lines[ sect->_myLines[i] ];
                paintWallLightmaps( ent, lightPt, line );
        }
        
}

void Zone::updateDynamicLightmaps() const {        
        
        //        cout << " updating dynlights " << SDL_GetTicks() << endl;

        //
        // we need the vis data to do this right
        //

        if ( ! _isVisLoaded )
                return;
        
        for ( int i = 0; i < _lightmaps.length; ++i ) {
                class Lightmap * lm = _lightmaps[ i ];
                if ( lm->isModified() ) {
                        memcpy( lm->rgb_pixels, lm->static_pixels, lm->size[0] * lm->size[1] * 3 );
                        lm->removeFlag( Lightmap::MODIFIED );
                        lm->setFlag( Lightmap::NEEDS_REBIND );
                }
        }
        
        if ( ! _useDynamicLights )
                return;
        
        Vector< class Entity * >::const_iterator end = _contents.end();
        for ( Vector< class Entity * >::const_iterator iter = _contents.begin();
              iter != end; ++iter ) {
                
                class Entity * ent = *iter;

                // TODO: only if ent is a lightsource
                
                class LightSource * lt = ent->getLight();

                if ( lt ) {
                        if ( (lt->_intensity + lt->_delta) <= 0 ) {
                                ent->setLight( 0 );
                                continue;
                        }

                        lt->_intensity += lt->_delta;
                        

                        modifyDynamicLightmap( ent );
                }
        }
}

