/*
===========================================================================
Copyright (C) 2015 the OpenMoHAA team

This file is part of OpenMoHAA source code.

OpenMoHAA source code 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.

OpenMoHAA source code 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 OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

// item.cpp: Base class for respawnable, carryable objects.
//

#include "g_local.h"
#include "g_phys.h"
#include "entity.h"
#include "trigger.h"
#include "item.h"
#include "inventoryitem.h"
#include "scriptmaster.h"
#include "health.h"
#include "game.h"

typedef struct {
    str  name;
    str  prefix;
    bool mohprefix;
} prefix_t;

static prefix_t r_prefixlist[256] = {
    {"Unarmed",                "unarmed",          true},
    {"Binoculars",             "binoculars",       true},
    {"Papers",                 "papers",           true},
    {"Packed MG42 Turret",     "mg42portable",     true},
    {"Colt 45",                "colt45",           true},
    {"Walther P38",            "p38",              true},
    {"Hi-Standard Silenced",   "histandard",       true},
    {"Webley Revolver",        "webley",           true},
    {"Nagant Revolver",        "nagantrev",        true},
    {"Beretta",                "beretta",          true},
    {"M1 Garand",              "garand",           true},
    {"Mauser KAR 98K",         "kar98",            true},
    {"KAR98 - Sniper",         "kar98sniper",      true},
    {"Springfield '03 Sniper", "springfield",      true},
    {"Lee-Enfield",            "enfield",          true},
    {"SVT 40",                 "svt",              true},
    {"Mosin Nagant Rifle",     "mosin",            true},
    {"G 43",                   "g43",              true},
    {"Enfield L42A1",          "enfieldl42a1",     true},
    {"Carcano",                "carcano",          true},
    {"DeLisle",                "delisle",          true},
    {"Thompson",               "thompson",         true},
    {"MP40",                   "mp40",             true},
    {"Sten Mark II",           "sten",             true},
    {"PPSH SMG",               "ppsh",             true},
    {"Moschetto",              "moschetto",        true},
    {"BAR",                    "bar",              true},
    {"StG 44",                 "mp44",             true},
    {"FG 42",                  "fg42",             true},
    {"Vickers-Berthier",       "vickers",          true},
    {"Breda",                  "breda",            true},
    {"Frag Grenade",           "fraggrenade",      true},
    {"Stielhandgranate",       "stielhandgranate", true},
    {"F1 Grenade",             "f1grenade",        true},
    {"Mills Grenade",          "millsgrenade",     true},
    {"Nebelhandgranate",       "nebelhandgranate", true},
    {"M18 Smoke Grenade",      "m18smokegrenade",  true},
    {"RDG-1 Smoke Grenade",    "rdg1smokegrenade", true},
    {"Bomba A Mano",           "bomba",            true},
    {"Bomba A Mano Breda",     "bombabreda",       true},
    {"Landmine",               "mine",             true},
    {"LandmineAllies",         "minedetector",     true},
    {"LandmineAxis",           "minedetectoraxis", true},
    {"Unarmed",                "detonator",        true},
    {"Bazooka",                "bazooka",          true},
    {"Panzerschreck",          "panzerschreck",    true},
    {"Gewehrgranate",          "kar98mortar",      true},
    {"Shotgun",                "shotgun",          true},
    {"PIAT",                   "PIAT",             true},
};

void AddItemToList(const char *name, const char *prefix)
{
    for (int i = 0; i < sizeof(r_prefixlist) / sizeof(r_prefixlist[0]); i++) {
        if (!r_prefixlist[i].name.length()) {
            r_prefixlist[i].name      = name;
            r_prefixlist[i].prefix    = prefix;
            r_prefixlist[i].mohprefix = false;
            return;
        }
    }
}

const char *GetItemName(const char *prefix, qboolean *mohprefix)
{
    for (int i = 0; i < sizeof(r_prefixlist) / sizeof(r_prefixlist[0]); i++) {
        if (!r_prefixlist[i].prefix.c_str()) {
            continue;
        }

        if (r_prefixlist[i].prefix == prefix) {
            if (mohprefix) {
                *mohprefix = r_prefixlist[i].mohprefix;
            }

            return r_prefixlist[i].name;
        }
    }

    if (mohprefix) {
        *mohprefix = false;
    }

    return "Unarmed";
}

const char *GetItemPrefix(const char *name, qboolean *mohprefix)
{
    for (int i = 0; i < sizeof(r_prefixlist) / sizeof(r_prefixlist[0]); i++) {
        if (r_prefixlist[i].name == name) {
            if (mohprefix) {
                *mohprefix = r_prefixlist[i].mohprefix;
            }

            return r_prefixlist[i].prefix;
        }
    }

    if (mohprefix) {
        *mohprefix = false;
    }

    return "unarmed";
}

Event EV_Item_Pickup
(
    "item_pickup",
    EV_DEFAULT,
    "e",
    "item",
    "Pickup the specified item.",
    EV_NORMAL
);
Event EV_Item_DropToFloor
(
    "item_droptofloor",
    EV_DEFAULT,
    NULL,
    NULL,
    "Drops the item to the ground.",
    EV_NORMAL
);
Event EV_Item_Respawn
(
    "respawn",
    EV_DEFAULT,
    NULL,
    NULL,
    "Respawns the item.",
    EV_NORMAL
);
Event EV_Item_SetRespawn
(
    "set_respawn",
    EV_DEFAULT,
    "i",
    "respawn",
    "Turns respawn on or off.",
    EV_NORMAL
);
Event EV_Item_SetRespawnTime
(
    "set_respawn_time",
    EV_DEFAULT,
    "f",
    "respawn_time",
    "Sets the respawn time.",
    EV_NORMAL
);
Event EV_Item_SetAmount
(
    "amount",
    EV_DEFAULT,
    "i",
    "amount",
    "Sets the amount of the item.",
    EV_NORMAL
);
Event EV_Item_SetMaxAmount
(
    "maxamount",
    EV_DEFAULT,
    "i",
    "max_amount",
    "Sets the max amount of the item.",
    EV_NORMAL
);
Event EV_Item_SetDMAmount
(
    "dmamount",
    EV_DEFAULT,
    "i",
    "amount",
    "Sets the amount of the item for DM.",
    EV_NORMAL
);
Event EV_Item_SetDMMaxAmount
(
    "dmmaxamount",
    EV_DEFAULT,
    "i",
    "max_amount",
    "Sets the max amount of the item fmr DM.",
    EV_NORMAL
);
Event EV_Item_SetItemName
(
    "name",
    EV_DEFAULT,
    "s",
    "item_name",
    "Sets the item name.",
    EV_NORMAL
);
Event EV_Item_RespawnSound
(
    "respawnsound",
    EV_DEFAULT,
    NULL,
    NULL,
    "Turns on the respawn sound for this item.",
    EV_NORMAL
);
Event EV_Item_DialogNeeded
(
    "dialogneeded",
    EV_DEFAULT,
    "s",
    "dialog_needed",
    "Sets the dialog needed string.",
    EV_NORMAL
);
Event EV_Item_NoRemove
(
    "no_remove",
    EV_DEFAULT,
    NULL,
    NULL,
    "Makes it so the item is not removed from the world when it is picked up.",
    EV_NORMAL
);
Event EV_Item_RespawnDone
(
    "respawn_done",
    EV_DEFAULT,
    NULL,
    NULL,
    "Called when the item respawn is done.",
    EV_NORMAL
);
Event EV_Item_PickupDone
(
    "pickup_done",
    EV_DEFAULT,
    NULL,
    NULL,
    "Called when the item pickup is done.",
    EV_NORMAL
);

Event EV_Item_SetPickupSound
(
    "pickupsound",
    EV_DEFAULT,
    "s",
    "name",
    "sets the item's pickup sound alias",
    EV_NORMAL
);

#ifdef OPM_FEATURES
Event EV_Item_ViewModelPrefix
(
    "viewmodelprefix",
    EV_DEFAULT,
    "s",
    "prefix",
    "Sets the item's prefix for viewmodelanim.",
    EV_NORMAL
);

Event EV_Item_UpdatePrefix
(
    "_updateprefix",
    EV_CODEONLY,
    NULL,
    NULL,
    "internal event - update the custom viewmodel prefix",
    EV_NORMAL
);
#endif

CLASS_DECLARATION(Trigger, Item, NULL) {
    {&EV_Trigger_Effect,       &Item::ItemTouch            },
    {&EV_Item_DropToFloor,     &Item::DropToFloor          },
    {&EV_Item_Respawn,         &Item::Respawn              },
    {&EV_Item_SetAmount,       &Item::SetAmountEvent       },
    {&EV_Item_SetMaxAmount,    &Item::SetMaxAmount         },
    {&EV_Item_SetItemName,     &Item::SetItemName          },
    {&EV_Item_Pickup,          &Item::Pickup               },
    {&EV_Use,                  &Item::TriggerStuff         },
    {&EV_Item_RespawnSound,    &Item::RespawnSound         },
    {&EV_Item_DialogNeeded,    &Item::DialogNeeded         },
    {&EV_Item_NoRemove,        &Item::SetNoRemove          },
    {&EV_Item_RespawnDone,     &Item::RespawnDone          },
    {&EV_Item_PickupDone,      &Item::PickupDone           },
    {&EV_Item_SetRespawn,      &Item::setRespawn           },
    {&EV_Item_SetRespawnTime,  &Item::setRespawnTime       },
    {&EV_Stop,                 &Item::Landed               },
    {&EV_SetAngle,             &SimpleEntity::SetAngleEvent},
    {&EV_Item_SetDMAmount,     &Item::SetDMAmountEvent     },
    {&EV_Item_SetDMMaxAmount,  &Item::SetDMMaxAmount       },
    {&EV_Item_SetPickupSound,  &Item::SetPickupSound       },
#ifdef OPM_FEATURES
    {&EV_Item_ViewModelPrefix, &Item::EventViewModelPrefix },
    {&EV_Item_UpdatePrefix,    &Item::updatePrefix         },
#endif
    {NULL,                     NULL                        }
};

Item::Item()
{
    str fullname;

    entflags |= ECF_ITEM;

    AddWaitTill(STRING_PICKUP);

    if (LoadingSavegame) {
        return;
    }

    setSolidType(SOLID_NOT);

    // Set default respawn behavior
    // Derived classes should use setRespawn
    // if they want to override the default behavior
    setRespawn(deathmatch->integer ? true : false);
    setRespawnTime(20);

    //
    // set a minimum mins and maxs for the model
    //
    if (size.length() < 10) {
        mins = "-10 -10 0";
        maxs = "10 10 20";
    }

    //
    // reset the mins and maxs to pickup the FL_ROTATEDBOUNDS flag
    //
    setSize(mins, maxs);

    // Items can't be immediately dropped to floor, because they might
    // be on an entity that hasn't spawned yet.
    PostEvent(EV_Item_DropToFloor, EV_POSTSPAWN);

    respondto = TRIGGER_PLAYERS;

    // items should collide with everything that the player does
    edict->clipmask = MASK_ITEM;

    item_index     = 0;
    maximum_amount = 1;
    playrespawn    = false;

    // this is an item entity
    edict->s.eType = ET_ITEM;

    amount    = 1;
    no_remove = false;
    setName("Unknown Item");

    sPickupSound = "snd_pickup";

    m_sVMprefix  = "Unarmed";
    m_bMOHPrefix = true;
}

Item::~Item()
{
    if (owner) {
        owner->RemoveItem(this);
        owner = NULL;
    }

    entflags &= ~ECF_ITEM;
}

void Item::RemoveFromOwner(void)
{
    owner->RemoveItem(this);
    owner = NULL;
}

void Item::Delete(void)
{
    if (g_iInThinks && owner) {
        RemoveFromOwner();
    }

    Animate::Delete();
}

void Item::SetNoRemove(Event *ev)
{
    no_remove = true;
}

/*
============
PlaceItem

Puts an item back in the world
============
*/
void Item::PlaceItem(void)
{
    setSolidType(SOLID_TRIGGER);
    setMoveType(MOVETYPE_TOSS);
    showModel();

    groundentity = NULL;

    setSize(Vector(-12, -12, -2), Vector(12, 12, 12));
}

/*
============
DropToFloor

plants the object on the floor
============
*/
void Item::DropToFloor(Event *ev)
{
    str    fullname;
    Vector save;

    PlaceItem();
    setMoveType(MOVETYPE_NONE);

    /*
	addOrigin( Vector( "0 0 1" ) );

	save = origin;
	if( !droptofloor( 8192 ) )
	{
		gi.DPrintf( "%s (%d) stuck in world at '%5.1f %5.1f %5.1f'\n",
			getClassID(), entnum, origin.x, origin.y, origin.z );
		setOrigin( save );
		setMoveType( MOVETYPE_NONE );
	}
	else
	{
		setMoveType( MOVETYPE_NONE );
	}
	//
	// if the our global variable doesn't exist, lets zero it out
	//
	fullname = str( "playeritem_" ) + getName();

	game.vars->GetOrCreateVariable( fullname.c_str() );
	level.vars->GetOrCreateVariable( fullname.c_str() );
	*/
}

qboolean Item::Drop(void)
{
    if (!owner) {
        return false;
    }

    setOrigin(owner->origin + Vector("0 0 40"));

    // drop the item
    PlaceItem();
    velocity = owner->velocity * 0.5 + Vector(G_CRandom(50), G_CRandom(50), 100);
    setAngles(owner->angles);
    avelocity = Vector(0, G_CRandom(360), 0);

    trigger_time = level.time + 1;

    if (owner->isClient()) {
        spawnflags |= DROPPED_PLAYER_ITEM;
    } else {
        spawnflags |= DROPPED_ITEM;
    }

    // Remove this from the owner's item list
    RemoveFromOwner();

    PostEvent(EV_Remove, g_droppeditemlife->value);

    return true;
}

void Item::ItemTouch(Event *ev)

{
    Entity *other;
    Event  *e;

    if (owner) {
        // Don't respond to trigger events after item is picked up.
        // we really don't need to see this.
        //gi.DPrintf( "%s with targetname of %s was triggered unexpectedly.\n", getClassID(), TargetName() );
        return;
    }

    other = ev->GetEntity(1);

    e = new Event(EV_Item_Pickup);
    e->AddEntity(other);
    ProcessEvent(e);
}

void Item::SetOwner(Sentient *ent)
{
    assert(ent);
    if (!ent) {
        // return to avoid any buggy behaviour
        return;
    }

    owner = ent;
    setRespawn(false);

    setSolidType(SOLID_NOT);
    hideModel();
    CancelEventsOfType(EV_Touch);
    CancelEventsOfType(EV_Item_DropToFloor);
    CancelEventsOfType(EV_Remove);

#ifdef OPM_FEATURES
    Event *ev = new Event(EV_Item_UpdatePrefix);
    ev->AddEntity(ent);

    PostEvent(ev, EV_POSTSPAWN);
#endif
}

Sentient *Item::GetOwner(void)
{
    return owner;
}

Item *Item::ItemPickup(Entity *other, qboolean add_to_inventory)
{
    Sentient *sent;
    Item     *item = NULL;
    str       realname;

    if (!Pickupable(other)) {
        return NULL;
    }

    sent = (Sentient *)other;

    if (add_to_inventory) {
        item = sent->giveItem(model, getAmount());

        if (!item) {
            return NULL;
        }
    } else {
        item = this;
    }

    //
    // let our sent know they received it
    // we put this here so we can transfer information from the original item we picked up
    //
    sent->ReceivedItem(item);

    Sound(sPickupSound);

    if (!Removable()) {
        // leave the item for others to pickup
        return item;
    }

    CancelEventsOfType(EV_Item_DropToFloor);
    CancelEventsOfType(EV_Item_Respawn);
    CancelEventsOfType(EV_FadeOut);

    setSolidType(SOLID_NOT);

    if (HasAnim("pickup")) {
        NewAnim("pickup", EV_Item_PickupDone);
    } else {
        if (!no_remove) {
            hideModel();

            if (!Respawnable()) {
                PostEvent(EV_Remove, FRAMETIME);
            }
        }
    }

    if (Respawnable()) {
        PostEvent(EV_Item_Respawn, RespawnTime());
    }

    return item;
}

void Item::Respawn(Event *ev)
{
    showModel();

    // allow it to be touched again
    setSolidType(SOLID_TRIGGER);

    // play respawn sound
    if (playrespawn) {
        Sound("snd_itemspawn");
    }

    setOrigin();

    if (HasAnim("respawn")) {
        NewAnim("respawn", EV_Item_RespawnDone);
    }
}

void Item::setRespawn(Event *ev)

{
    if (ev->NumArgs() < 1) {
        return;
    }

    setRespawn(ev->GetInteger(1));
}

void Item::setRespawnTime(Event *ev)

{
    if (ev->NumArgs() < 1) {
        return;
    }

    setRespawnTime(ev->GetFloat(1));
}

void Item::RespawnDone(Event *ev)
{
    NewAnim("idle");
}

void Item::PickupDone(Event *ev)
{
    if (!no_remove) {
        hideModel();

        if (!Respawnable()) {
            PostEvent(EV_Remove, FRAMETIME);
        }
    } else {
        if (HasAnim("pickup_idle")) {
            NewAnim("pickup_idle");
        } else {
            NewAnim("pickup");
        }
    }
}

void Item::setRespawn(qboolean flag)
{
    respawnable = flag;
}

qboolean Item::Respawnable(void)

{
    return respawnable;
}

void Item::setRespawnTime(float time)
{
    respawntime = time;
}

float Item::RespawnTime(void)

{
    return respawntime;
}

int Item::getAmount(void)

{
    return amount;
}

int Item::MaxAmount(void)

{
    return maximum_amount;
}

qboolean Item::Pickupable(Entity *other)
{
    if (!other->IsSubclassOfSentient()) {
        return false;
    } else {
        Sentient *sent;
        Item     *item;

        sent = (Sentient *)other;

        item = sent->FindItem(getName());

        if (item && (item->getAmount() >= item->MaxAmount())) {
            return false;
        }
    }
    return true;
}

void Item::Pickup(Event *ev)
{
    ItemPickup(ev->GetEntity(1));
}

void Item::setName(const char *i)
{
    const char *prefix;

    item_name  = i;
    item_index = gi.itemindex(i);
    Q_strncpyz(edict->entname, i, sizeof(edict->entname));

    prefix = GetItemPrefix(item_name);
    if (prefix) {
        m_sVMprefix  = prefix;
        m_bMOHPrefix = true;
    }
}

str Item::getName(void)

{
    return (item_name);
}

int Item::getIndex(void)

{
    return item_index;
}

void Item::setAmount(int startamount)

{
    amount = startamount;
    if (amount >= MaxAmount()) {
        SetMax(amount);
    }
}

void Item::SetMax(int maxamount)

{
    maximum_amount = maxamount;
}

void Item::SetAmountEvent(Event *ev)
{
    if (g_protocol <= protocol_e::PROTOCOL_MOH && g_gametype->integer != GT_SINGLE_PLAYER) {
        return;
    }
    setAmount(ev->GetInteger(1));
}

void Item::SetMaxAmount(Event *ev)

{
    if (g_protocol <= protocol_e::PROTOCOL_MOH && g_gametype->integer != GT_SINGLE_PLAYER) {
        return;
    }
    SetMax(ev->GetInteger(1));
}

void Item::SetDMAmountEvent(Event *ev)
{
    if (g_gametype->integer == GT_SINGLE_PLAYER) {
        return;
    }
    setAmount(ev->GetInteger(1));
}

void Item::SetDMMaxAmount(Event *ev)
{
    if (g_gametype->integer == GT_SINGLE_PLAYER) {
        return;
    }
    setAmount(ev->GetInteger(1));
}

void Item::SetPickupSound(Event *ev)
{
    sPickupSound = ev->GetString(1);
}

void Item::SetItemName(Event *ev)

{
    setName(ev->GetString(1));
}

void Item::Add(int num)

{
    amount += num;
    if (amount >= MaxAmount()) {
        amount = MaxAmount();
    }
}

void Item::Remove(int num)
{
    amount -= num;
    if (amount < 0) {
        amount = 0;
    }
}

qboolean Item::Use(int num)

{
    if (num > amount) {
        return false;
    }

    amount -= num;
    return true;
}

qboolean Item::Removable(void)

{
    return true;
}

void Item::RespawnSound(Event *ev)

{
    playrespawn = true;
}

void Item::DialogNeeded(Event *ev)

{
    //
    // if this item is needed for a trigger, play this dialog
    //
    dialog_needed = ev->GetString(1);
}

str Item::GetDialogNeeded(void)

{
    return dialog_needed;
}

//
// once item has landed on the floor, go to movetype none
//
void Item::Landed(Event *ev)
{
    if (groundentity && (groundentity->entity != world)) {
        warning("Item::Landed", "Item %d has landed on an entity that might move\n", entnum);
    }
    setMoveType(MOVETYPE_NONE);
}

Listener *Item::GetScriptOwner()
{
    return owner;
}

void Item::SetScriptOwner(Listener *newOwner)
{
    if (!newOwner || !newOwner->isSubclassOf(Sentient)) {
        owner = NULL;
        return;
    }

    owner = static_cast<Sentient*>(newOwner);
}

#ifdef OPM_FEATURES
void Item::EventViewModelPrefix(Event *ev)
{
    int        i;
    gentity_t *ent;

    m_sVMprefix = ev->GetString(1);

    if (!GetItemPrefix(item_name, &m_bMOHPrefix)) {
        AddItemToList(item_name, m_sVMprefix);
    }

    for (i = 0, ent = g_entities; i < game.maxclients; i++, ent++) {
        if (!ent->inuse || !ent->entity) {
            continue;
        }

        Event *ev = new Event(EV_Item_UpdatePrefix);
        ev->AddEntity(ent->entity);

        PostEvent(ev, EV_POSTSPAWN);
    }
}

void Item::updatePrefix(Event *ev)
{
    if (!sv_specialgame->integer) {
        return;
    }

    if (m_bMOHPrefix) {
        return;
    }

	Entity *ent = ev->GetEntity( 1 );

	gi.MSG_SetClient( ent->edict - g_entities );

	gi.MSG_StartCGM( CGM_VIEWMODELPREFIX );
		gi.MSG_WriteString( item_name.c_str() );
		gi.MSG_WriteString( m_sVMprefix.c_str() );
	gi.MSG_EndCGM();
}
#endif

CLASS_DECLARATION(Item, DynItem, NULL) {
    {&EV_Kill,   &DynItem::UnlinkItem},
    {&EV_Damage, &DynItem::UnlinkItem},
    {NULL,       NULL                }
};

DynItem::DynItem()
{
    if (LoadingSavegame) {
        return;
    }

    setSolidType(SOLID_BBOX);
    setMoveType(MOVETYPE_BOUNCE);
    takedamage = DAMAGE_YES;
}

void DynItem::UnlinkItem(Event *ev)
{
    if (!owner) {
        return;
    }

    setOrigin(owner->origin + Vector(0, 0, 40));
    PlaceItem();

    velocity = owner->velocity * 0.5 + Vector(G_CRandom(50), G_CRandom(50), 100);
    setAngles(owner->angles);
    avelocity = Vector(0, G_CRandom(360), 0);

    trigger_time = level.time + 1;

    if (owner->isClient()) {
        spawnflags |= DROPPED_PLAYER_ITEM;
    } else {
        spawnflags |= DROPPED_ITEM;
    }

    // Remove this from the owner's item list
    RemoveFromOwner();
}

void DynItem::DynItemTouched(Event *ev)
{
    // Empty
}

void DynItem::DynItemUse(Event *ev)
{
    // Empty
}

void DynItem::Archive(Archiver& arc)
{
    Item::Archive(arc);

    arc.ArchiveString(&m_attachPrime);
    arc.ArchiveString(&m_attachSec);
    arc.ArchiveString(&m_dynItemName);

    if (arc.Loading()) {
        setName(m_dynItemName.c_str());
    }
}
