/*
 * server/Character.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 "Character.h"
#include "Item.h"
#include "Zone.h"
#include "LightSource.h"
#include "Sound.h"
#include "Projectile.h"
#include "Sector.h"
#include "Player.h"
#include "ProtoChar.h"
#include "Camera.h"

#include "../util/VMath.h"

#include <math.h>
#include <string>
#include <cstdio>

const char * const Character::STAT_NAMES[ NUM_STATS ] = {
        "Strength",
        "Dexterity",
        "Speed",
        "Constitution",
        "Charisma"
};

const char * const Character::STAT_ABBREVS[ NUM_STATS ] = {
        "STR",
        "DEX",
        "SPD",
        "CON",
        "CHA"
};


void Character::init() {

        _AC = 0;
        _maxHPs = 0;
        _curHPs = 0;
        _gold = 0;
        _expValue = 0;
        _exp = 0;
        _skillPoints = 0;
        _eyeLevel = 0;
        _wornWeight = 0;
        _affectFlags = 0;
        _lastFootstepTick = 0;
        _footstepTickMin = 500;
        _weaponModelName = 0;
        _weaponSkinName = 0;

        for ( int i = 0; i < NUM_STATS; ++i ) {
                _stats[i] = 0;
        }
        
        for ( int i = 0; i < NUM_WORN_POSITIONS; ++i ) {
                _eq[i] = 0;
        }
        
        setEyeLevel( ( getHeight() * 4 ) / 5 );
        
}

Character::Character( const char * name, const char * alias )
        : Entity( name, alias )
{
        init();
}

Character::Character( class ProtoChar * proto ) 
        : Entity( proto ) {
        init();
}
 
Character::~Character() {
}

bool Character::itemToEquipment( class Item * item, WornPosition pos ) {

        if ( ! item->isWearableAt( pos ) ) {
                cout << " itemToEquipment ERROR item cannot be equipped at " << (int) pos << endl;
                return false;
        }

        if ( _eq[ pos ] ) {
                cout << " itemToEquipment ERROR position " << (int)pos << " is already filled" << endl;
                return false;
        }

        if ( item->getWornBy() ) {
                cout << " itemToEquipment ERROR item claims to be worn already." << endl;
                return false;
        }
        
        _eq[ item->getWornPos() ] = item;
        item->_wornBy = this;
        item->_wornPos = pos;

        if ( pos == WEAR_WIELD ) {
                setWeaponModelName( item->getModelName() );
                setWeaponSkinName( item->getSkinName() );
        }

        return true;
}

bool Character::itemFromInventory( class Item * item ) {
        return removeContent( item );
}

bool Character::itemFromEquipment( class Item * item ) {
        _eq[ item->getWornPos() ] = 0;
        item->_wornBy = 0;
        item->_wornPos = WEAR_NONE;
        return true;
}

/**
 * default inventoryPos = -1
 */

bool Character::itemToInventory( class Item * item, int inventoryPos ) {
        
        if ( _contents.size() >= NUM_INVENTORY_SLOTS ) {
                cout << " inventory is full!" << endl;
                return false;
        }
        
        bool filled[ NUM_INVENTORY_SLOTS ];
        
        for ( unsigned int i = 0; i < NUM_INVENTORY_SLOTS; ++i ) {
                filled[i] = false;
        }

        Vector< class Entity * >::const_iterator end = _contents.end();
        for ( Vector< class Entity * >::const_iterator iter = _contents.begin();
              iter != end; ++iter ) {
                int pos = (int) (*iter)->getPos()[0];
                if ( pos < (int)NUM_INVENTORY_SLOTS &&
                     pos >= 0 ) {
                        filled[ pos ] = true;
                }
                else {
                        cout << " inventory item has bogus pos " << pos << endl;
                }
        }

        if ( inventoryPos >= 0 ) {
                if ( filled[ inventoryPos ] ) {
                        cout << " unable to put item in inventory at " << inventoryPos << endl;
                        return false;
                }
        }
        else {
                for ( unsigned int i = 0; i < NUM_INVENTORY_SLOTS; ++i ) {
                        if ( ! filled[ i ] ) {
                                inventoryPos = i;
                                break;
                        }
                }
                if ( inventoryPos == -1 ) {
                        cout << " unable to find a free slot for inventory item" << endl;
                        return false;
                }
        }
        
        addContent( item );
        item->setPosition( inventoryPos, 0, 0 );

        return true;
}

bool Character::equipItem( class Item * item, WornPosition pos ) {

        if ( item->getContainer() != this ) {
                cout << " error, item in equipItem not carried by char" << endl;
                return false;
        }
        
        if ( _eq[pos] ) {
                cout << " error, char already equipped at " << (int) pos << endl;
                return false;
        }
        
        if ( ! item->isWearableAt( pos ) ) {
                cout << " error, item " << *item << " cannot equip at " << (int) pos << endl;
                return false;
        }

        if ( ! itemFromInventory( item ) ) {
                cout << " error, item failed to itemFromInventory in equipItem" << endl;
                return false;
        }
        
        if ( ! itemToEquipment( item, pos ) ) {
                cout << " error, unable to place item in equipment in equipItem." << endl;

                if ( ! itemToInventory( item ) )
                        cout << " error, unable to replace item in inventory" << endl;
                return false;

        }

        return true;
}

bool Character::reEquipItemFromEquipment( class Item * item, WornPosition pos ) {
        
        if ( item->getWornBy() != this ) {
                cout << " error, item in equipItemFromEquipment not worn by char" << endl;
                return false;
        }

        if ( ! item->isWearableAt( pos ) ) {
                cout << " error, in reEquipItemFromEquipment item cannot be equipped at " << (int)pos << endl;
                return false;
        }

        //
        // the Item that already resides in the taret slot
        //

        class Item * cur_equipped = _eq[pos];

        //
        // this is the trivial case, no action needs to be performed
        //

        if ( cur_equipped == item )
                return true;

        WornPosition old_pos = item->getWornPos();

        //
        // if the inventory slot is empty, just do a normal equip
        //

        if ( cur_equipped ) {
                
                if ( ! cur_equipped->isWearableAt( old_pos ) ) {
                        cout << " error, in reEquipItemFromEquipment target item cannot be re-equipped at " << (int)old_pos << endl;
                        return false;
                }
                
                if ( ! itemFromEquipment( cur_equipped ) ) {
                        cout << " error, in reEquipItemFromEquipment target item could not be popped from " << (int)pos << endl;
                        return false;
                }
        }

        if ( ! itemFromEquipment( item ) ) {
                cout << " error, in reEquipItemFromEquipment removing item " << endl;

                if ( cur_equipped && ! itemToEquipment( cur_equipped, old_pos ) )
                        cout << " error, in reEquipItemFromEquipment replacing target item to " << (int)old_pos << endl;                

                return false;
        }
        
        if ( ! itemToEquipment( item, pos ) ) {
                cout << " error, in reEquipItemFromEquipment putting item at " << (int) pos << endl;
                
                if ( cur_equipped && ! itemToEquipment( cur_equipped, old_pos ) )
                        cout << " error, in reEquipItemFromEquipment replacing target item to " << (int)old_pos << endl;
                
                return false;
        }
        
        if ( cur_equipped ) {
                if ( ! itemToEquipment( cur_equipped, old_pos ) ) {
                        cout << " error, in reEquipItemFromEquipment, moving item to " << (int)old_pos << endl;
                        
                        if  ( ! itemFromEquipment( item ) ) {
                                cout << " error, in reEquipItemFromEquipment, removing item from target pos " << (int)pos << endl;
                        }

                        else if ( !itemToEquipment( cur_equipped, pos ) || !itemToEquipment( item, old_pos ) )
                                cout << " error, in reEquipItemFromEquipment, replacing both items." << endl;
                        
                        return false;
                        
                }
        }

        return true;
        
}

bool Character::reEquipItemFromInventory( class Item * item, WornPosition pos ) {
        
        if ( item->getContainer() != this ) {
                cout << " error, item in reEquipItemFromInventory not carried by char" << endl;
                return false;
        }

        class Item * cur_equipped = _eq[pos];
        
        //
        // if the equipment slot is empty, just do a normal equip
        //

        if ( !cur_equipped ) {
                return equipItem( item, pos );
        }
        
        WornPosition old_eq_pos = cur_equipped->getWornPos();
        if ( ! itemFromEquipment( cur_equipped ) ) {
                cout << " error, unable to temporarily pop equipment item in reEquipItemFromInventory" << endl;
                return false;
        }

        int old_inv_pos = (int) item->getPos()[0];
        
        if ( ! equipItem( item, pos ) ) {
                cout << " error, unable to equip item  in reEquipItem " << endl;
                if ( ! itemToEquipment( cur_equipped, old_eq_pos ) )
                        cout << " error, cleanup in reEquipItem failed." << endl;
                return false;
        }
        
        if ( ! itemToInventory( cur_equipped, old_inv_pos ) ) {
                cout << " error, unable to place item in inventory after reEquipItemFromInventory WARNING" << endl;
                return false;
        }

        return true;
        
}

bool Character::reEquipItem( class Item * item, WornPosition pos ) {
        
        if ( item->getContainer() == this ) {
                return reEquipItemFromInventory( item, pos );
        }
        else if ( item->getWornBy() == this ) {
                return reEquipItemFromEquipment( item, pos );
        }

        return false;
}

/**
 * default inventoryPos = -1
 */

bool Character::unequipItem( class Item * item, int inventoryPos ) {

        if ( item->getWornBy() != this ) {
                cout << " error, item is not equipped on this" << endl;
                return false;
        }

        if ( _eq[ item->getWornPos() ] != item ) {
                cout << " error, item wornpos inconsistent with pointer" << endl;
                return false;
        }

        WornPosition old_wornpos = item->getWornPos();
        
        if ( ! itemFromEquipment( item ) ) {
                cout << " unequipItem unable to temporarily pop item from eq" << endl;
                return false;
        }

        if ( ! itemToInventory( item, inventoryPos ) ) {
                cout << " unequipItem unable to put item in inventory pos " << inventoryPos << endl;
                if ( ! itemToEquipment( item, old_wornpos ) )
                        cout << " unequipItem unable to replace item in equipment, "
                             << " WARNING: item may be lost in space." << endl;
                
                return false;
        }

        return true;
        
}

void Character::getInventorySlots( class Item * slots[ NUM_INVENTORY_SLOTS ] ) const {

        for ( unsigned int i = 0; i < NUM_INVENTORY_SLOTS; ++i ) {
                slots[i] = 0;
        }

        Vector< class Entity * >::const_iterator end = _contents.end();
        for ( Vector< class Entity * >::const_iterator iter = _contents.begin();
              iter != end; ++iter ) {
                int pos = (int) (*iter)->getPos()[0];
                if ( pos < (int)NUM_INVENTORY_SLOTS &&
                     pos >= 0 ) {
                        slots[ pos ] = (class Item*) *iter;
                }
                else {
                        cout << " inventory item has bogus pos in getInventorySlots" << pos << endl;
                }
        }     
}

class Item * Character::getInventoryItemAt( int pos ) const {
        Vector< class Entity * >::const_iterator end = _contents.end();
        for ( Vector< class Entity * >::const_iterator iter = _contents.begin();
              iter != end; ++iter ) {
                if ( pos == (*iter)->getPos()[0] )
                        return (class Item*) *iter;
        }
        return 0;
}

bool Character::slideInventoryItem( class Item *item, int targetPos ) {

        if ( targetPos < 0 || targetPos >= (int)NUM_INVENTORY_SLOTS ) {
                cout << " errorm target pos " << targetPos << "  in slideInventoryItem out of bounds" << endl;
                return false;
        }

        if ( item->getContainer() != this ) {
                cout << " error, item in slideInventoryItem not carried by char" << endl;
                return false;
        }

        class Item *targetItem = getInventoryItemAt( targetPos );
        
        if ( targetItem ) {
                targetItem->setPosition( item->getPos()[0], 0, 0 );
        }

        item->setPosition( targetPos, 0, 0 );
        return true;
}

void Character::attack( class Entity * vict ) {

        if ( _frame < FRAME_attack1 ||
             _frame > FRAME_attack8 )
                _frame = FRAME_attack1;
        
        if ( vict )
                return;

        class Zone * zone = getContainerZone();

        
        
        Vector< class Entity * >::const_iterator end = zone->getContents().end();
        for ( Vector< class Entity * >::const_iterator iter = zone->getContents().begin();
              iter != end; ++iter ) {
                
                if ( *iter == this )
                        continue;
                
                class Character * ch = dynamic_cast< class Character * >( *iter );
                
                if ( ch )
                        ch->attack( ch );//takeDamage( 0, 0, 0 );
        }

        return;
        
        float angle_x = getTilt();

        float theta = VMath::degrees2radians( angle_x );//- angle_x - 90 );
        
        class Projectile * bullet = new Projectile( Projectile::FIREBALL, this );

        bullet->setAngle( _angle );
        bullet->setTilt( angle_x );
        
        //        float attack_angle = VMath::degrees2radians( _angle );
        
        bullet->setPosition( getPos()[0],// + getBoundingRadius() * 2 * cos( attack_angle ),
                             getPos()[1],// + getBoundingRadius() * 2 * sin( attack_angle ),
                             _eyeLevel + getPos()[2] - bullet->getHeight()/2 );
        bullet->setRelativeSelfPropulsion( 40 * cos( theta ) , 0, 40 * sin( theta ) );
        
        zone = getContainerZone();
       
        zone->addEntity( bullet );

        
}

void Character::jump() {
        if ( getMinZ() < getPos()[2] )
                return;

        addRelativeSelfPropulsion( 0, 0, 28 );
        _frame = FRAME_jump1;
        emitSound( new Sound( Sound::SND_HUMAN_MALE_JUMP, 100, 0, true ) );
}

bool Character::collideWith( class Entity * other, const float * normal, unsigned int tick ) {
        if ( normal ) {
                if ( normal[0] == 0 && normal[1] == 0 ) {
                        // hit the floor
                        if ( normal[2] > 0 ) {
                                
                                float av = _velocity[2];
                                if ( getFloorSector() )
                                        av -= getFloorSector()->getSurfaceVelocity( Sector::FLOOR );
                                av = fabs( av );
                                
                                if ( av > 32 ||
                                     ( false && _frame >= FRAME_jump1 && _frame < FRAME_jump4 ) ) {
                                        if ( av < 48 ) {
                                                emitSound( new Sound( Sound::SND_LANDING, 100, 0, true ) );
                                        }
                                        else {
                                                emitSound( new Sound( Sound::SND_PAIN_FALL, 100, 0, true ) );
                                        }
                                        
                                        _frame = FRAME_jump4;
                                        
                                        _lastFootstepTick = 0;
                                }
                        }
                
                        else if ( normal[2] < 0 ) {
                                emitSound( new Sound( Sound::SND_PAIN_MEDIUM, 100, 0, true ) );
                        }

                }
        }

        if ( other ) {
                class Item * item = dynamic_cast<class Item *>( other );

                if ( item ) {
                        // pick it up
                        getContainerZone()->removeEntity( item );
                        itemToInventory( item );

                        if ( camera ) {
                                char buf[128];
                                sprintf( buf, "You got the %.100s", item->getName() );
                                camera->setMessage( buf, tick + 2000 );
                                emitSound( new Sound( Sound::SND_PICKUP_ITEM, 180, 0, true ) );

                        }
                }
        }

        return false;
}


bool Character::think( unsigned int tick ) {

        if ( tick > ( _last_tick + 100 ) ) {
                
                _last_tick = tick;

                _frame++;
                
                float minZ = getMinZ();
                
                const class Sector * fsect = getFloorSector();
                
                // allow attack animation to fully complete

                if ( _frame > FRAME_attack1 &&
                     _frame <= FRAME_attack8 ) {

                        
                        if ( _frame == FRAME_attack3 && dynamic_cast<class Player *>(this))
                                emitSound( new Sound( Sound::SND_SWORD_SLASH, 100, 0, true ) );

                        return false;


                        // do nothing
                }

                if ( _frame > FRAME_pain101 &&
                     _frame <= FRAME_pain304 &&
                     _frame != FRAME_pain201 &&
                     _frame != FRAME_pain301 ) {
                        return false;
                        // do nothing
                }

                // midair
                
                if ( minZ < _pos[2] ) {
                        if ( _velocity[2] > -32 ) {
                                if ( _velocity[2] > 0 ||
                                     ( minZ + 64 ) <  _pos[2] ||
                                     ( _frame > FRAME_jump1 &&
                                       _frame <= FRAME_jump6 ) ) {
                                        _frame = FRAME_jump2;
                                        return false;
                                }
                        }
                        else {
                                _frame = FRAME_jump3;
                                return false;
                        }
                }
                
                // currently in a jump or landing animation

                if ( _frame > FRAME_jump1 &&
                          _frame <= FRAME_jump6 ) {
                        return false;
                        // do nothing
                }

                // running or walking

                if ( _selfPropulsion[0] ||
                          _selfPropulsion[1] ) {
                        
                        if ( _frame < FRAME_run1 ||
                             _frame > FRAME_run6 ) {
                                _frame = FRAME_run1;
                        }

                        if ( fsect && fsect->getFloorHeight() == _pos[2] && 
                             ( _frame == FRAME_run2 || _frame == FRAME_run5 || tick > ( _lastFootstepTick + 400 ) ) ) {
                                emitSound( new Sound( Sound::SND_FOOTSTEP_MEDIUM, 50, 0, true ) );
                                _lastFootstepTick = tick;
                        }

                        return false;
                }
                
                //
                // idling
                //

                if ( _frame > FRAME_stand40 )
                        _frame = 0;
        
        }

        return false;
}

bool Character::takeDamage( int type, int location, int amount ) {
        
        static int damage_frame_num = 0;
        
        if ( damage_frame_num == 0 )
                _frame = FRAME_pain101;
        else if ( damage_frame_num == 1 )
                _frame = FRAME_pain201;
        else 
                _frame = FRAME_pain301;
        
        ++damage_frame_num;

        if ( damage_frame_num > 2 )
                damage_frame_num = 0;
        
        return false;
}
