/*
 * server/Zone.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 <cstdio>
#include <cmath>
#include <string>
#include <iomanip>

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

#include "StartPoint.h"

Zone::Zone(  const char * name, const char * alias )
        : Entity( name, alias ),
          _lines( 0 ),
          _sides( 0 ),
          _texnames( 0 ),
          _isVisLoaded( false ),
          _useDynamicLights( true )
{
}

Zone::~Zone() {

        cout << " ++ deleting Zone" << endl;

        for ( int i = 0; i < _lines.length; ++i ) {
                delete _lines[i];
        }
        for ( int i = 0; i < _lightmaps.length; ++i ) {
                if ( _lightmaps[i]->clientLightmap )
                        cout << " :: WARNING: leaking clientLightmap" << endl;

                delete _lightmaps[i];
        }
        for ( int i = 0; i < _texnames.length; ++i ) {
                free( const_cast<char *>(_texnames[i]) );
        }

        for ( int i = 0; i < _vertexes.length; ++i ) {
                delete[] _vertexes[i];
        }
        
        for ( int i = 0; i < _sectSpecials.length; ++i ) {
                delete _sectSpecials[i];
        }
        
        for ( int i = 0; i < _sectors.length; ++i ) {
                if ( _sectors[i]->clientSector )
                        cout << " :: WARNING: leaking clientSector" << endl;

                delete _sectors[i];
        }
        
        for ( int i = 0; i < _sides.length; ++i ) {

                if ( _sides[i]->clientSide )
                        cout << " :: WARNING: leaking clientSide" << endl;

                delete _sides[i];
        }
        
        for ( int i = 0; i < _scripts.length; ++i ) {
                delete _scripts[i];
        }
        
        for ( int i = 0; i < _lights.length; ++i ) {
                delete _lights[i];
        }

        for ( unsigned int i = 0; i < _cmds.size(); ++i ) {
                delete _cmds[i];
        }
        
        for ( unsigned int i = 0; i < _startPoints.size(); ++i ) {
                delete _startPoints[i];
        }
}


/**
 * default comment = 0
 */
void Zone::printStatistics( const char * comment ) const {

        cout << " :: > Zone stats ( " << comment << " ): " << endl;
        
        cout << " :: >      verts:  " << setw(5) << _vertexes.length << endl
             << " :: >      sides:  " << setw(5) << _sides.length << endl
             << " :: >      lines:  " << setw(5) << _lines.length << endl
             << " :: >      sects:  " << setw(5) << _sectors.length << endl
             << " :: > sect specs:  " << setw(5) << _sectSpecials.length << endl
             << " :: >    scripts:  " << setw(5) << _scripts.length << endl
             << " :: >   textures:  " << setw(5) << _texnames.length << endl
             << " :: >     lights:  " << setw(5) << _lights.length << endl;

        int num_lightmap_bytes = 0;
        int num_wasted_lightmap_bytes = 0;
        int num_wasteful_lightmaps = 0;
        
        for ( int i = 0; i < _lightmaps.length; ++i ) {
                if ( _lightmaps[i] ) {
                        int amount = _lightmaps[i]->size[0] * _lightmaps[i]->size[1];
                        num_lightmap_bytes += amount;
                        int use = _lightmaps[i]->use_size[0] * _lightmaps[i]->use_size[1];

                        if ( use < amount ) {
                                num_wasted_lightmap_bytes += use;
                                ++num_wasteful_lightmaps;
                        }
                }
        }
        
        num_lightmap_bytes = num_lightmap_bytes * 3 / 1024;
        
        num_wasted_lightmap_bytes = num_lightmap_bytes - num_wasted_lightmap_bytes * 3 / 1024;

        cout << " :: >  lightmaps:  " << setw(5) << _lightmaps.length 
             << " ( " << num_lightmap_bytes << "/"
             << num_wasted_lightmap_bytes << " kB total/wasted in " 
             << num_wasteful_lightmaps << " wasteful surfaces )" << endl;

        if ( _isVisLoaded ) {
                int total_vis = 0;
                int max_vis = 0;
                int total_visfloors = 0;
                int max_visfloors = 0;
                int total_visceilings = 0;
                int max_visceilings = 0;
                int total_vislines = 0;
                int max_vislines = 0;

                unsigned int max_texmatch_all_floors = 0;
                unsigned int avg_texmatch_all_floors = 0;
                
                for ( int i = 0; i < _sectors.length; ++i ) {
                        max_vis = max( _sectors[i]->_visibleSectors.length, max_vis );
                        total_vis += _sectors[i]->_visibleSectors.length;

                        max_visfloors = max( _sectors[i]->_visibleFloors.length, max_visfloors );
                        total_visfloors += _sectors[i]->_visibleFloors.length;

                        max_visceilings = max( _sectors[i]->_visibleCeilings.length, max_visceilings );
                        total_visceilings += _sectors[i]->_visibleCeilings.length;

                        max_vislines = max( _sectors[i]->_visibleLines.length, max_vislines );
                        total_vislines += _sectors[i]->_visibleLines.length;
                        
                        bool first_time = true;
                        
                        unsigned int texmatch = 0;
                        unsigned int num_texglobs = 0;
                        
                        unsigned int max_texmatch = 0;
                        unsigned int avg_texmatch = 0;
                        
                        const char * last_tex = 0;
                        
                        for ( int vfi = 0; vfi < _sectors[i]->_visibleFloors.length; ++vfi ) {
                                class Sector * s = _sectors[ _sectors[i]->_visibleFloors[ vfi ]  ];
                                
                                const char * my_tex = s->getFloorTex();
                                
                                if ( first_time == true ) {
                                        last_tex = my_tex;
                                        first_time = false;
                                        continue;
                                }
                        
                                if ( my_tex == last_tex ) {
                                        ++texmatch;
                                }
                        
                                else {
                                        ++texmatch;
                                        max_texmatch = max( texmatch, max_texmatch );
                                        avg_texmatch += texmatch;
                                        ++num_texglobs;
                                        texmatch = 0;
                                        last_tex = my_tex;
                                }
                        }
                        
                        if ( texmatch > 0 ) {
                                ++texmatch;
                                max_texmatch = max( texmatch, max_texmatch );
                                avg_texmatch += texmatch;
                                ++num_texglobs;
                        }
                        
                        if ( num_texglobs )
                                avg_texmatch /= num_texglobs;
                        else 
                                avg_texmatch = 0;

                        max_texmatch_all_floors = max( max_texmatch_all_floors, max_texmatch );
                        avg_texmatch_all_floors += avg_texmatch;
                }
                
                total_vis         /= _sectors.length;
                total_visfloors   /= _sectors.length;
                total_visceilings /= _sectors.length;
                total_vislines    /= _lines.length;

                avg_texmatch_all_floors /= _sectors.length;
                
                cout << " :: > visible  sectors: max=" << setw(5) << max_vis 
                     << ", avg=" << setw(5) << total_vis << endl;
                cout << " :: > visible   floors: max=" << setw(5) << max_visfloors 
                     << ", avg=" << setw(5) << total_visfloors
                     << ", maxtex=" << setw( 5 ) << max_texmatch_all_floors
                     << ", avgtex=" << setw( 5 ) << avg_texmatch_all_floors << endl;
                cout << " :: > visible ceilings: max=" << setw(5) << max_visceilings
                     << ", avg=" << setw(5) << total_visceilings << endl;
                cout << " :: > visible    lines: max=" << setw(5) << max_vislines
                     << ", avg=" << setw(5) << total_vislines << endl;

        }
        else {
                cout << " :: > no visibility data loaded." << endl;
        }
}

/**
 * set the rgb illumination of this entity based solely on the floor/ceiling lightmaps of this sector
 */
static inline void getSectIllumination( class Entity * ent, 
                                        const class Sector * s,
                                        unsigned char * rgb ) {
        
        //
        // set fullbright if no lightmaps appear to be loaded.
        //

        if ( s->floor_lightmap == 0 || s->ceiling_lightmap == 0 ) {
                rgb[0] = rgb[1] = rgb[2] = 255;
                return;
        }

        const int s_height = s->getCeilingHeight() - s->getFloorHeight() - ent->getHeight();
        int floor_pct = 50;

        if ( s_height > 0 )
                floor_pct = max( 0, min( 100, ( (int) ent->getPos()[2] - s->getFloorHeight() ) * 100 / s_height ) );
                
        int relative_pos[2] = {
                (int) max( 0.0F, ent->getPos()[0] - s->_offset[0] ),
                (int) max( 0.0F, ent->getPos()[1] - s->_offset[1] )
        };
        
        int lm_coords[2] = {
                min( (int) s->floor_lightmap->use_size[0] - 1,
                     relative_pos[0] * s->floor_lightmap->use_size[0] / (int) s->_extent[0] ),
                min( (int) s->floor_lightmap->use_size[1] - 1,
                     relative_pos[1] * s->floor_lightmap->use_size[1] / (int) s->_extent[1] )
        };
        
        int lm_lumel = lm_coords[0] + lm_coords[1] * s->floor_lightmap->size[0];
        lm_lumel *= 3;
        
        for ( int i = 0; i < 3; ++i ) {

                rgb[i] = (int) s->floor_lightmap->rgb_pixels[ lm_lumel + i ] * ( 100 - floor_pct ) / 100 +
                        (int) s->ceiling_lightmap->rgb_pixels[ lm_lumel + i ] * ( floor_pct ) / 100;
        }
}


const unsigned char * Zone::setEntityIllumination( class Entity * ent ) const {

       
        const class Sector * s = ent->getSector();
        
        unsigned char rgb[3] = { 0, 0, 0 };
        
        if ( s ) {
                getSectIllumination( ent, s, rgb );
                
        }
        else {
                rgb[0] = 255;
                rgb[1] = 255;
                rgb[2] = 255;
        }
        //        ent->setIllumination( rgb[0], rgb[1], rgb[2] );

        //        return ent->getIllumination();

        const class Sector * fs = ent->getFloorSector();

        if ( fs == s )
                fs = 0;
        
        const class Sector * cs = ent->getCeilingSector();
        
        if ( cs == s || cs == fs )
                cs = 0;

        unsigned char f_rgb[3] = {0,0,0};
        
        int div = 1;
        
        if ( fs ) {
                ++div;
                getSectIllumination( ent, fs, f_rgb );
        }

        unsigned char c_rgb[3] = {0,0,0};
        
        if ( cs ) {//&& false ) {
                ++div;
                getSectIllumination( ent, cs, c_rgb );
        }
        
        if ( div > 1 ) {
                for ( int i = 0; i < 3; ++i ) {
                        rgb[i] = rgb[i] / div +
                                ( fs ? ( f_rgb[i] / div ) : 0 ) +
                                ( cs ? ( c_rgb[i] / div ) : 0 );
                }
        }

        const class LightSource * lt = ent->getLight();

        if ( lt ) {
                rgb[0] = max( rgb[0], lt->getRGB()[0] );
                rgb[1] = max( rgb[1], lt->getRGB()[1] );
                rgb[2] = max( rgb[2], lt->getRGB()[2] );
        };

        ent->setIllumination( rgb[0], rgb[1], rgb[2] );
        return ent->getIllumination();
}

/**
 * angle_z
 * angle_x should be in radians
 */
class Entity * Zone::setTargetEntity( class Character * ch, 
                                      float angle_z, 
                                      float angle_x ) const {
        
        float view_vect[3] = {
                cos( angle_z ),
                sin( angle_z ),
                0
        };
        
        ch->setTargetEntity( 0 );

        float min_opp = 99999999;
        float min_dist = 9999999;
        
        Vector< class Entity * >::const_iterator end = _contents.end();
        for ( Vector< class Entity * >::const_iterator iter = _contents.begin();
              iter != end; ++iter ) {
                class Entity *other = *iter;
                
                if ( other == ch )
                        continue;

                float trans_pt[3] = {
                        other->getPos()[0] - ch->getPos()[0],
                        other->getPos()[1] - ch->getPos()[1],
                        other->getPos()[2] - ch->getPos()[2] - ch->getEyeLevel()
                };

                float dist;
                float adj;
                float opp = VMath::distFromLine2d( trans_pt, view_vect, &adj, & dist );

                //                if ( adj <= 0 )
                //                        continue;
                
                if ( opp < other->getBoundingRadius() ) {

                        // eye_targ_z is word-coord z pos of eyepoint target
                        float eye_targ_z = adj * tan( angle_x - M_PI/2 ) + ch->getPos()[2] + ch->getEyeLevel();
                        
                        //                        cout << " eye_targ_z=" << eye_targ_z << ", angle_x=" << angle_x << endl;
                        
                        // if eye_targ_z is above/below the midpoint of the entity, check for
                        // the actual bounds
                        //
                        // This may be overkill, and may be disabled to improve performance if needed
                        //

                        if ( eye_targ_z < other->getPos()[2] ||
                             eye_targ_z > ( other->getPos()[2] + other->getHeight() ) ) {
                                
                                //
                                // view is looking directly down
                                //

                                if ( angle_x == 0 ) {
                                        continue;
                                }
                                
                                //
                                // viewpoint is angled down
                                //

                                else if ( angle_x < (M_PI/2) ) {

                                        //
                                        // looking fully below this one, skip it
                                        //

                                        if ( trans_pt[2] > 0 ) {
                                                continue;
                                        }
                                        
                                        //
                                        // looking under the other entity
                                        //

                                        if ( eye_targ_z < other->getPos()[2] ) {

                                                float d = sqrt( VMath::square( other->getBoundingRadius() ) -
                                                                VMath::square( opp ) );
                                                
                                                float tan_theta = trans_pt[2] / ( dist - d );

                                                float z_diff_max =  d * tan_theta;
                                                
                                                if ( eye_targ_z < ( z_diff_max + other->getPos()[2] ) ) {
                                                        continue;
                                                }
                                        }

                                        //
                                        // looking over the other entity
                                        //
                                        
                                        else {
                                                float d = sqrt( VMath::square( other->getBoundingRadius() ) -
                                                                VMath::square( opp ) );
                                                
                                                //
                                                // invert the tangent
                                                //

                                                float tan_theta = - ( trans_pt[2] + other->getHeight() ) / ( dist + d );

                                                float z_diff_max =  d * tan_theta;
                                                
                                                if ( eye_targ_z > ( z_diff_max + other->getPos()[2] + other->getHeight() ) ) {
                                                        continue;
                                                }
                                        }
                                }

                                //
                                // viewpoint is angled up
                                //


                                else if ( angle_x > ( M_PI/2 ) ) {
                                        continue;
                                }
                                
                                //
                                // view is looking directly up
                                //
                                else if ( angle_x == M_PI ) {
                                        continue;
                                }
                        }

                        if ( dist < min_dist || ( dist == min_dist && opp < min_opp ) ) {
                                min_opp = opp;
                                min_dist = dist;
                                ch->setTargetEntity( other );
                        }
                }
        }
        
        return ch->getTargetEntity();

}

void Zone::setFloorCeilingSectors( class Entity * ent ) const {
        
        //
        // often, it will simply be your current sector
        //
        
        ent->_currentSector = findSector( ent );
        class Sector * fs = ent->_currentSector;
        class Sector * cs = ent->_currentSector;
        
        //
        // however, if your bounding cylinder overlaps any other sectors,
        // your floor sector will be the one with the highest floor
        //
        
        for ( int i = 0; i < _lines.length; ++i ) {

                class LineDef * line = _lines[i];

                //
                // one-sided lines can't be straddled ( hopefully! )
                //

                if ( ! line->sides[1] )
                        continue;

                if ( VMath::lineSegmentIntersectsCircle( line->verts[0], 
                                                         line->verts[1], 
                                                         ent->getPos(), 
                                                         ent->getBoundingRadius() ) ) {

                        class Sector * sects[2] = { 
                                line->sides[0]->sector,
                                line->sides[1]->sector
                        };

                        if ( ! fs || sects[0]->getFloorHeight() > fs->getFloorHeight() ||
                             ( sects[0]->spec && sects[0]->spec->isLift() &&
                               sects[0]->getFloorHeight() == fs->getFloorHeight() ) ) {
                                fs = sects[0];
                        }

                        if ( sects[1]->getFloorHeight() > fs->getFloorHeight() ||
                             ( sects[1]->spec && sects[1]->spec->isLift() &&
                               sects[1]->getFloorHeight() == fs->getFloorHeight() ) ) {
                                fs = sects[1];
                        }
                        
                        if ( ! cs || sects[0]->getCeilingHeight() < cs->getCeilingHeight() ||
                             ( sects[0]->spec && sects[0]->spec->isDoor() &&
                               sects[0]->getCeilingHeight() == cs->getCeilingHeight() ) ) {
                                cs = sects[0];
                        }
                        
                        if ( sects[1]->getCeilingHeight() < cs->getCeilingHeight() ||
                             ( sects[1]->spec && sects[1]->spec->isDoor() &&
                               sects[1]->getCeilingHeight() == cs->getCeilingHeight() ) ) {
                                cs = sects[1];
                        }
                }
        }

        ent->_floorSector = fs;
        ent->_ceilingSector = cs;
        
}

bool Zone::addEntity( class Entity * ent )   
{
        if ( ! addContent( ent ) )
                return false;
        
        setFloorCeilingSectors( ent );
        setFloorCeilingEntities( ent );

        setEntityIllumination( ent );

        return true;
}

bool Zone::removeEntity( class Entity * ent ) { 
        bool b = removeContent( ent );
        
        ent->_currentSector = 0;
        ent->_floorSector   = 0;
        ent->_ceilingSector = 0;

        ent->_floorEntity = ent->_ceilingEntity = 0;

        return b;
}


class Sector * Zone::findSector( const float * pt ) const {
        for ( int i = 0; i < _sectors.length; ++i ) {
                Sector * s = _sectors[i];
                
                bool bad = false;
                
                for ( int j = 1; j < s->verts.length; ++j ) {
                        if ( VMath::whichSide( s->verts[j-1], s->verts[j], pt ) != 0 ) {
                                bad = true;
                                break;
                        }
                }
                
                if ( bad )
                        continue;
                
                if ( VMath::whichSide( s->verts[ s->verts.length - 1 ], s->verts[0], pt ) != 0 )
                        continue;

                return s;
        }

        return 0;
}

class Sector * Zone::findSector( const class Entity * ent ) const {

        return findSector( ent->getPos() );
}


bool Zone::sectorsCanSee( const class Sector * s1, const class Sector * s2 ) const {

        if ( ! _isVisLoaded )
                return true;
                
        if ( s1 == s2 )
                return true;

        if ( s1->_visibleSectors.cur_index < s2->_visibleSectors.cur_index )
                return s1->_visibleSectors.findElement( s2->_num );
        
        return s2->_visibleSectors.findElement( s1->_num );
}

