/*
 * server/Script.h
 *
 * 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 <Script.h>
#include <iostream>
#include <string>
#include <cstdio>

#include "../util/Persist.h"
#include "Entity.h"
#include "Zone.h"
#include "Camera.h"
#include "Player.h"
#include "Sound.h"
#include "Item.h"
#include "World.h"

Script::Script( istream & is ) {

        running = false;

        num_cmds = is.get();
        num_msgs  = is.get();

        msgs  = new char * [ num_msgs ];
        cmds = new Command * [ num_cmds ];

        for ( int i = 0; i < num_cmds; ++i ) {
                cmds[i] = new Command( is );
        }

        for ( int i = 0; i < num_msgs; ++i ) {
                int msglen = Persist::readShort( is );
                msgs[i] = new char[ msglen + 1 ];
                
                is.read( msgs[i], msglen );
                msgs[i][msglen] = 0;

        }
} 

Script::Script( const Script & other ) {
        running = false;
        num_cmds = other.num_cmds;
        num_msgs = other.num_msgs;

        msgs  = new char * [ num_msgs ];
        cmds = new Command * [ num_cmds ];
        
        for ( int i = 0; i < num_cmds; ++i ) {
                cmds[i] = new Command( *  other.cmds[i] );
        }
        
        for ( int i = 0; i < num_msgs; ++i ) {
                msgs[i] = new char[ strlen( other.msgs[i] ) + 1 ];
                strcpy( msgs[i], other.msgs[i] );
        }
}

Script::~Script() {
        
        for ( int i = 0; i < num_msgs; ++i ) {
                delete msgs[i];
        }

        for ( int i = 0; i < num_cmds; ++i ) {
                delete cmds[i];
        }
        
        delete[] msgs;
        delete[] cmds;
}

void Script::writeScript( ostream & os ) const {

        os.put( num_cmds );
        os.put( num_msgs );
        
        for ( int i = 0; i < num_cmds; ++i ) {
                cmds[i]->writeCommand( os );
        }

        for ( int i = 0; i < num_msgs; ++i ) {
                int len = strlen( msgs[i] );
                Persist::writeShort( os, len );
                os.write( msgs[i], len );
        }
        
}


Script::Command::Command( istream & is ) {

        type = (Type) is.get();
        for ( int i = 0; i < 4; ++i )
                values[i] = Persist::readShort( is );
}

Script::Command::Command( const Command & other ) {

        type = other.type;
        for ( int i = 0; i < 4; ++i )
                values[i] = other.values[i];
}

void Script::Command::writeCommand( ostream & os ) const {
        os.put( type );
        for ( int i = 0; i < 4; ++i )
                Persist::writeShort( os, values[i] );
}

bool Script::Command::execute( class Script * script, unsigned int tick ) {

        class Zone * zone = script->ent->getContainerZone();
        class Camera * cam = script->ent->camera;
        
        bool retval = false;

        if ( zone == 0 )
                return false;

        switch ( type ) {
        case TRIGGER_DOOR:
                zone->triggerSectorSpecials( values[0], values[1] );
                break;
        case CAMERA_MODE:
                if ( cam == 0 )
                        break;
                cam->type = (Camera::Type) values[0];
                break;
        case ROTATE_CAMERA:
                if ( cam == 0 )
                        break;

                cam->angles[0] += values[0];
                cam->angles[1] += values[1];
                cam->angles[2] += values[2];
                break;
        case SET_CAMERA_ANGLE:
                if ( cam == 0 )
                        break;
                cam->angles[0] = values[0];
                cam->angles[1] = values[1];
                cam->angles[2] = values[2];
                break;
        case SET_CAMERA_ZOOM:
                if ( cam == 0 )
                        break;
                cam->zoom = values[0];
                break;
                
        case ZOOM_CAMERA:
                if ( cam == 0 )
                        break;
                cam->zoom += values[0];
                break;

        case CENTERPRINT:
                if ( cam == 0 )
                        break;

                if ( values[0] < 0 || values[0] >= script->num_msgs ) {
                        cout << " :: BOGUS msg num " << values[0] << " in script." << endl;
                        break;
                }

                cam->setMessage( script->msgs[ values[0] ], tick + values[1] );
                break;
                
        case SOUND:
                
                script->ent->emitSound( new Sound( values[0], values[1], 0, true ) );
                break;

        case ALLOW_CONTROL:
                {
                        class Player * player = dynamic_cast<Player *>( script->ent );
                        if ( player ) {
                                player->hasControl = values[0];
                        }
                }
                break;

        case PLAYER_WALK:
                script->ent->setRelativeSelfPropulsion( values[0], values[1], values[2] );
                break;

        case ROTATE_PLAYER:
                script->ent->rotate( values[0], values[1] );
                break;

        case SET_PLAYER_ANGLE:
                script->ent->setAngle( values[0] );
                break;
                
        case CHECK_KEY:
                {
                        class Character *ch = dynamic_cast<Character*>( script->ent );
                        
                        if ( ch == 0 )
                                break;
                        
                        const Vector<class Entity *> & items = ch->getContents();
                        
                        bool found = false;
                        
                        for ( unsigned int i = 0; i < items.size(); ++i ) {
                                if ( items[i]->getVnum() == values[0] ) {
                                        found = true;
                                        break;
                                }
                        }

                        if ( found == false ) {
                                for ( int i = 0; i < NUM_WORN_POSITIONS; ++i ) {
                                        const class Item * eq = ch->getEquippedItem( (WornPosition) i );
                                        if ( eq && eq->getVnum() == values[0] ) {
                                                found = true;
                                                break;
                                        }
                                }
                        }

                        if ( found == true )
                                script->cur_cmd += values[1];
                }

                
                break;

        case TERMINATE:
                script->cur_cmd = script->num_cmds + 1;
                break;

        case CHANGE_MAP:
                {
                        
                        // change map triggers only work for player entities
                        
                        class Player * player = dynamic_cast< class Player * >( script->ent );
                        
                        if ( player == 0 )
                                break;
                        
                        class Zone * newZone = zone->getWorld()->getZone( script->msgs[ values[0] ] );
                        
                        if ( newZone == 0 ) {
                                cout << " :: ERROR: invalid zone name "  
                                     << script->msgs[ values[0] ] << " in script." << endl;
                                break;
                        }

                        float angle = player->getAngle();

                        zone->removeEntity( player );
                        newZone->addEntity( player );
                        newZone->spawnCharacter( player, values[1] );
                        
                        player->setAngle( angle );

                        if ( cam ) {
                                char buf[128];
                                sprintf( buf, "Entering %.64s...", newZone->getName() );
                                cam->setMessage( buf, tick + 2500 );
                        }


                        // terminate the script
                        script->cur_cmd = script->num_cmds + 1;
                        retval = true;
                }
                break;
                        
        default:
                cout << " unimplemented script command " << (int) type 
                     << " in  script cmd " << (int) script->cur_cmd 
                     << "/" << (int) script->num_cmds << endl;
                break;
        }

        return retval;
}

bool Script::execute( class Entity * theEnt, unsigned int tick ) {

        if ( theEnt ) {

                if ( running )
                        return false;

                ent = theEnt;
                cur_cmd = 0;
                iterations = 0;
                next_tick = 0;
                running = true;
        }
        else if ( next_tick > tick )
                return false;

        if ( cur_cmd >= num_cmds ) {
                class Player * player = dynamic_cast<Player *>( ent );
                if ( player ) {
                        player->hasControl = true;
                }
                running = false;
        }
        
        while ( cur_cmd < num_cmds ) {
                
                struct Command & cmd = * cmds[ cur_cmd ];
                 
                if ( cmd.type == Command::LOOP ) {
                        loop_start = cur_cmd;
                        iterations = cmd.values[0];
                        loop_span  = cmd.values[1];
                        ++cur_cmd;
                        continue;
                }

                bool iswait = false;
                
                if ( cmd.type == Command::WAIT ) {
                        next_tick = tick + cmd.values[0];
                        iswait = true;
                }
                else {
                        if ( cmd.execute( this, tick ) ) {
                                return true;
                        }
                }

                // tidy up, loop if necessary
                if ( iterations > 0 ) {

                        if ( ( loop_start + loop_span ) == cur_cmd ) {
                                if ( --iterations > 0 ) {
                                        cur_cmd = loop_start + 1;
                                        if ( iswait )
                                                break;
                                        else
                                                continue;
                                }
                        }
                }

                ++cur_cmd;

                if ( iswait )
                        break;
        }

        return false;
}
