/*
 * 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 <fstream>
#include <vector>

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


bool Zone::loadAllData() {
        
        char buf[1024];
        sprintf( buf, "lib/maps/%.256s.zon", _alias );
        
        ifstream ifs( buf, ios::binary | ios::in );
        
        if ( !ifs ) {
                cout << " -- NO zone file: " << buf << endl;
                return false;
        }

        loadEntity( ifs );

        ifs.close();

        sprintf( buf, "lib/maps/%.256s.cmd", _alias );

        ifs.open( buf, ios::binary | ios::in );
        
        if ( !ifs ) {
                cout << " -- No zone cmds file: " << buf << endl;
        }
        else {
                loadCmds( ifs );
        }
        
        ifs.close();

        return true;
}

void Zone::setInitialStates() {
        for ( int i = 0; i < _sectSpecials.length; ++i ) {
                class SectorSpecial * spec = _sectSpecials[i];

                if ( spec->sect == 0 )
                        continue;

                if ( spec->flags & SectorSpecial::START_EXTENDED ) {
                        spec->cur_travel = spec->travel;
                        
                        if ( spec->isDoor() )
                                spec->sect->_surfaceHeights[1] -= spec->cur_travel;
                        else if ( spec->isLift() )
                                spec->sect->_surfaceHeights[0] += spec->cur_travel;
                }
        }
}

/**
 * Zone format:
 * Entity header:
 * ----------------
 * name \n
 * alias \n
 * icon name \n
 * ----------------
 * short num_verts
 * short num_sides
 * short num_lines
 * short num_sects
 * ---------------
 * num_verts: < short x
 *            < short y
 * num_sides: < short sect
 *            < short line
 * num_lines: < short v1
 *            < short v2
 *            < short side1
 *            < short side2
 * num_sects: < Sector
 */

Zone::Header::Header( const class Zone * zone ) {
        num_verts     = zone->_vertexes.length;
        num_sides     = zone->_sides.length;
        num_lines     = zone->_lines.length;
        num_scripts   = zone->_scripts.length;
        num_sects     = zone->_sectors.length;
        num_textures  = zone->_texnames.length;
        num_lightmaps = zone->_lightmaps.length;
        vis_loaded    = zone->_isVisLoaded;
        num_lights    = zone->_lights.length;
        num_specials  = zone->_sectSpecials.length;
}

Zone::Header::Header( istream & is ) {
        num_verts     = Persist::readShort( is );
        num_sides     = Persist::readShort( is );
        num_lines     = Persist::readShort( is );
        num_scripts   = Persist::readShort( is );
        num_sects     = Persist::readShort( is );
        num_specials  = Persist::readShort( is );
        num_textures  = Persist::readShort( is );
        num_lightmaps = Persist::readShort( is );
        vis_loaded    = (bool) is.get();
        num_lights    = Persist::readShort( is );
}

bool Zone::Header::writeHeader( ostream & os ) const {
        Persist::writeShort( os, num_verts );
        Persist::writeShort( os, num_sides );
        Persist::writeShort( os, num_lines );
        Persist::writeShort( os, num_scripts );
        Persist::writeShort( os, num_sects );
        Persist::writeShort( os, num_specials );
        Persist::writeShort( os, num_textures );
        Persist::writeShort( os, num_lightmaps );
        
        os.put( (int) vis_loaded );
        
        Persist::writeShort( os, num_lights );

        return true;
}

bool Zone::writeZone( ostream &os ) {

        Entity::writeEntity( os );
        
        Zone::Header header( this );
        header.writeHeader( os );

        printStatistics( "writeZone" );

        //
        // dump the texture table
        //
        
        for ( int i = 0; i < _texnames.length; ++i ) {

                if ( _texnames[i] ) {
                        char * slashpos = strchr( _texnames[i], '/' );
                        
                        Persist::writeString( os, slashpos ? ( slashpos + 1 ) : 0, -1 );                        
                }
                else {
                        Persist::writeString( os, 0, 0 );
                }
        }

        //
        // dump the vertexes
        //

        for ( int i = 0; i < _vertexes.length; ++i ) {
                Persist::writeShort( os, (short) _vertexes[i][0] );
                Persist::writeShort( os, (short) _vertexes[i][1] );
        }

        //
        // dump the sector specials
        //

        for ( int i = 0; i < _sectSpecials.length; ++i ) {
                _sectSpecials[i]->writeSectorSpecial( os );
        }
        
        //
        // dump the sects
        //

        for ( int i = 0; i < _sectors.length; ++i ) {
                _sectors[i]->writeSector( os );
        }

        // dump the sides

        for ( int i = 0; i < _sides.length; ++i ) {
                _sides[i]->writeSide( os );
        }


        //
        // dump the scripts
        // 

        for ( int i = 0; i < _scripts.length; ++i ) {
                _scripts[i]->writeScript( os );
        }
        
        //
        // dump the lines
        //

        for ( int i = 0; i < _lines.length; ++i ) {
                _lines[i]->writeLine( os, this );
        }

        //
        // dump the lightmaps
        //

        cout << " :: dumping " << _lightmaps.length << " lightmaps" << endl;
        
        for ( int i = 0; i < _lightmaps.length; ++i ) {
                _lightmaps[i]->writeLightmap( os );
        }

        //
        // dump the lights
        //

        for ( int i = 0; i < _lights.length; ++i ) {
                _lights[i]->writeLightSource( os );
        }

        return true;
}


void Zone::loadEntity( istream & is ) {

        //
        // alias for zones is the base filename, special handling
        //

        char old_alias[1024];
        strncpy( old_alias, _alias, 1023 );
        
        Entity::loadEntity( is );

        setAlias( old_alias );
        
        Zone::Header header( is );

        _vertexes.setSize( header.num_verts );
        _sides.setSize(    header.num_sides );
        _lines.setSize(    header.num_lines );
        _scripts.setSize( header.num_scripts );
        _sectSpecials.setSize( header.num_specials );
        _sectors.setSize(  header.num_sects );
        _texnames.setSize( header.num_textures );
        _lightmaps.setSize( header.num_lightmaps );
        _lights.setSize( header.num_lights );

        _isVisLoaded = header.vis_loaded;
        
        //
        // pre-create the lines objects since the sides need to reference them
        //
        
        for ( int i = 0; i < _lines.length; ++i ) {
                _lines[i] = new LineDef();
        }
        
        //
        // pre-create the lightmaps objects since the surfaces need to reference them
        //

        for ( int i = 0; i < _lightmaps.length; ++i ) {
                _lightmaps[i] = new Lightmap();
        }
        
        //
        // load the texture table
        //

        for ( int i = 0; i < _texnames.length; ++i ) {
                char buf[128];

                if ( Persist::readString( is, buf, 128 ) ) {

                        char buf2[256];
                        
                        sprintf( buf2, "textures/%s", buf );

                        _texnames[i] = strdup( buf2 );
                }
                else 
                        _texnames[i] = 0;

        }

        //
        // load the vertexes
        //

        for ( int i = 0; i < _vertexes.length; ++i ) {
                short s[2];
                
                s[0] = Persist::readShort( is );
                s[1] = Persist::readShort( is );

                _vertexes[i] = new float[3];
                _vertexes[i][0] = s[0];
                _vertexes[i][1] = s[1];
                _vertexes[i][2] = 0;

        }

        //
        // load the sector specials
        //
        
        for ( int i = 0; i < _sectSpecials.length; ++i ) {
                _sectSpecials[i] = new SectorSpecial( is );
        }
        
        //
        // load the sects
        //

        for ( int i = 0; i < _sectors.length; ++i ) {
                _sectors[i] = new Sector( is, _vertexes, _texnames, _lightmaps, this );
                _sectors[i]->_num = i;
        }

        //
        // load the sides
        //
        
        for ( int i = 0; i < _sides.length; ++i ) {
                _sides[i] = new SideDef( is, _lines, _sectors, _texnames, _lightmaps );
        }

        //
        // load the line specials
        // 

        for ( int i = 0; i < _scripts.length; ++i ) {
                _scripts[i] = new Script( is );
        }
        
        //
        // load the lines
        //

        for ( int i = 0; i < _lines.length; ++i ) {
                _lines[i]->load( is, _vertexes, _sides, this );
                if ( ! _lines[i]->sides[0] ) {
                        cout << " error, line with no front side" << endl;
                }
        }

        //
        // load the lightmaps
        //

        for ( int i = 0; i < _lightmaps.length; ++i ) {
                _lightmaps[i]->load( is );
        }

        //
        // load the lights
        //

        for ( int i = 0; i < _lights.length; ++i ) {
                _lights[i] = new LightSource( is );
        }
        
        //
        // fill out the Sector::_myLines Arrays.
        // these arrays are created in the Sector loader
        //

        for ( int i = 0; i < _lines.length; ++i ) {

                class LineDef * line = _lines[i];
                
                line->sides[0]->sector->_myLines.addElement( i );

                if ( line->sides[1] ) {
                        line->sides[1]->sector->_myLines.addElement( i );
                }
        }

        //
        // fill out the SideDef::components bitvector
        //
        for ( int i = 0; i < _lines.length; ++i ) {

                class SideDef * front = _lines[i]->sides[0];
                class SideDef * back  = _lines[i]->sides[1];

                front->setSideComponents( back );

                if ( back )
                        back->setSideComponents( front );

        }
        
        printStatistics( "loadEntity" );

        //        cout << *this << endl;

        //        illuminate( 8, 16 );


}

bool Zone::spliceZone( const class Zone & other ) {
 
        //
        // test compatibility
        //

        if ( _sectors.length != other._sectors.length ||
             _vertexes.length != other._vertexes.length ||
             _lines.length != other._lines.length ||
             _sides.length != other._sides.length ) {
                cout << " :: ERROR: zones are incompatible." << endl;
                return false;
        }
        
        //
        // splice the texture array first
        //

        _texnames.setSize( other._texnames.length );

        for ( int i = 0; i < _texnames.length; ++i ) {
                //                free( const_cast<char*>(_texnames[i] ) );
                if ( other._texnames[i] )
                        _texnames[i] = strdup( other._texnames[i] );
                else
                        _texnames[i] = 0;
        }

        //
        // splice in the scripts and sector specials
        //

        _scripts.setSize( other._scripts.length );

        for ( int i = 0; i < _scripts.length; ++i ) {
                _scripts[i] = new Script( * other._scripts[i] );
        }

        _sectSpecials.setSize( other._sectSpecials.length );

        for ( int i = 0; i < _sectSpecials.length; ++i ) {
                _sectSpecials[i] = new SectorSpecial( * other._sectSpecials[i] );
        }
        
        int texi;               // texture index
                
        //
        // splice in the sector texture and scripts info
        //

        for ( int i = 0; i < _sectors.length; ++i ) {                
                texi = other._texnames.findElement( other._sectors[i]->floorTex );
                _sectors[i]->floorTex  = _texnames[ texi ];
                texi = other._texnames.findElement( other._sectors[i]->ceilingTex );
                _sectors[i]->ceilingTex = _texnames[ texi ];

                if ( other._sectors[i]->spec ) {
                        _sectors[i]->spec = _sectSpecials[ other._sectSpecials.findElement( other._sectors[i]->spec ) ];
                        _sectors[i]->spec->sect = _sectors[i];
                }
        }

        //
        // splice in the linedef trigger info
        //

        for ( int i = 0; i < _lines.length; ++i ) {
                if ( other._lines[i]->script ) {
                        _lines[i]->script = _scripts[ other._scripts.findElement( other._lines[i]->script ) ];
                }
                
                _lines[i]->trigger_flags = other._lines[i]->trigger_flags;
        }

        //
        // splice in the sidedef texture info
        //
        
        for ( int i = 0; i < _sides.length; ++i ) {
                for ( int j = 0; j < 3; ++j ) {
                        texi = other._texnames.findElement( other._sides[i]->textures[j] );
                        _sides[i]->textures[j] = _texnames[ texi ];
                }
        }
        
        // splice in the lights
        
        _lights.setSize( other._lights.length );
        for ( int i = 0; i < _lights.length; ++i ) {
                _lights[i] = other._lights[i];
        }

        return true;
        
}
