/* marriagemanager.cpp - Author: DivineLight
 *
 * Copyright (C) 2001 Atomic Blue (info@planeshift.it, http://www.atomicblue.org) 
 *
 * 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 (version 2 of the License)
 * 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 <psconfig.h>
#include <csutil/xmltiny.h>

#include <physicallayer/entity.h>
#include <propclass/mesh.h>

#include "chatmanager.h"
#include "bulkobjects/psraceinfo.h"  // for gender 
#include "marriagemanager.h"
#include "util/psstring.h"
#include "psserver.h"
#include "psserverchar.h"
#include "util/psdatabase.h"
#include "bulkobjects/pscharacterloader.h"
#include "util/eventmanager.h"
#include "net/msghandler.h"
#include "net/messages.h"
#include "events.h"
#include "gem.h"
#include "globals.h"
#include "netmanager.h"
#include "invitemanager.h"
#include "usermanager.h"

/** Questions client and Handles YesNo response of client for proposals. */
class PendingMarriageProposal : public PendingInvite
{
public:
    PendingMarriageProposal( Client *inviter, Client *invitee, 
        const char *question ) : PendingInvite( inviter, invitee, false, 
        question, "Accept", "Reject", 
        "You have proposed %s to marry you.", 
        "%s has proposed you for a marriage.", 
        "%s has accepted your proposal.",
        "You have accepted the marriage proposal.",
        "%s has rejected your marriage proposal.",
        "You have rejected %s's marriage proposal.",
        psQuestionMessage::generalConfirm )
    {
    }

    void HandleAnswer(const csString & answer)
    {
        Client * proposedClient = psserver->GetConnections()->Find(clientnum);
        if ( !proposedClient )
            return;
       
        PendingInvite::HandleAnswer(answer);
                
        Client * proposerClient = psserver->GetConnections()->Find(inviterClientNum);
        if ( !proposerClient )
            return;
        
        psMarriageManager* marriageMgr = psserver->GetMarriageManager();
        if ( !marriageMgr )
            return;

        csString tempStr;
        psCharacter* proposedCharData = proposedClient->GetCharacterData();
        psCharacter* proposerCharData = proposerClient->GetCharacterData();
        const char* proposedCharFirstName = proposedCharData->GetCharName();
        const char* proposerFirstName = proposerCharData->GetCharName();

        if ( answer == "no" )
        {
            // Inform proposer that marriage has been rejected
            tempStr.Format( "%s has rejected your Marriage Proposal.", proposedCharFirstName );
            psserver->SendSystemError( proposerClient->GetClientNum(),
                tempStr.GetData() );
                        
            Debug3( LOG_MARRIAGE, proposerClient->GetClientNum(),"%s rejected the marriage proposal from %s.", proposedCharFirstName, proposerFirstName );
            return;
        }

        // Set proposed as spouse of proposer & ViceVersa
        if ( !marriageMgr->SetSpouseName( proposedCharData, proposerCharData )
            || !marriageMgr->SetSpouseName( proposerCharData, proposedCharData ) )
        {
            // Error handling: Should rarely happen.
            psserver->SendSystemError( proposerClient->GetClientNum(), "Unknown occured while completing marriage request." );
            psserver->SendSystemError( proposedClient->GetClientNum(), "Unknown occured while completing marriage request." );
            Error1( "\nMSG_MARRIAGE_ACCEPT: Some error while setting spouse names.\n" );
            return;
        }

        // Inform proposer & proposed char of successfull marriage
        tempStr.Format( "Congratulations! You are now married to %s", proposedCharFirstName );
        psserver->SendSystemResult( proposerClient->GetClientNum(), tempStr.GetData() );

        tempStr.Format( "Congratulations! You are now married to %s", proposerFirstName );
        psserver->SendSystemResult( proposedClient->GetClientNum(), tempStr.GetData() );

        Debug3( LOG_MARRIAGE, proposerClient->GetClientNum(),"\n%s is now married to %s.\n", proposedCharFirstName, proposerFirstName );
    }
};

class PendingMarriageDivorce : public PendingInvite
{
public:
    PendingMarriageDivorce( Client *inviter, Client *invitee, 
        const char *question ) : PendingInvite( inviter, invitee, false, 
        question, "Accept", "Reject", 
        "You have asked %s to divorce.", 
        "%s wants to obtain a divorce from you.", 
        "%s agrees on the divorce.",
        "You agreed to divorce.",
        "%s doesn't want to divorce.",
        "You have declined %s's divorce request.",
        psQuestionMessage::generalConfirm )
    {
    }

    void HandleAnswer(const csString & answer)
    {
        Client * divorcedClient = psserver->GetConnections()->Find(clientnum);
        if ( !divorcedClient )
        {
            return;
        }
       
        PendingInvite::HandleAnswer(answer);
                
        Client * divorcerClient = psserver->GetConnections()->Find(inviterClientNum);
        if ( !divorcerClient )
        {
            return;
        }
        
        psMarriageManager* marriageMgr = psserver->GetMarriageManager();
        if ( !marriageMgr )
        {
            return;
        }

        csString tempStr;
        psCharacter* divorcedCharData = divorcedClient->GetCharacterData();
        psCharacter* divorcerCharData = divorcerClient->GetCharacterData();
        csString divorcedFirstName = divorcedCharData->GetCharName();
        csString divorcerFirstName = divorcerCharData->GetCharName();

        if ( answer == "no" )
        {
            // Inform proposer that marriage has been rejected
            tempStr.Format( "%s has rejected your divorce request.", 
                            divorcedFirstName.GetDataSafe() );
            psserver->SendSystemError( divorcerClient->GetClientNum(),
                tempStr.GetData() );
                        
            Debug3( LOG_MARRIAGE, clientnum, "%s rejected the divorce request from %s.", 
                    divorcedFirstName.GetDataSafe(), divorcerFirstName.GetDataSafe() );
            return;
        }
    
        // Delete entries of both character's from DB
        marriageMgr->DeleteMarriageInfo( divorcerCharData );
        marriageMgr->DeleteMarriageInfo( divorcedCharData );

        // Inform divorcer and divorced of successfull divorce
        psserver->SendSystemResult( divorcerClient->GetClientNum(), "You are now divorced." );
        psserver->SendSystemResult( divorcedClient->GetClientNum(), "You are now divorced.");

        Debug3( LOG_MARRIAGE, divorcedClient->GetClientNum(),"%s divorced from %s.", divorcerFirstName.GetData(), divorcedFirstName.GetData() );
      
    }
};

class PendingMarriageDivorceConfirm : public PendingQuestion
{
protected:
    csString divorceMsg;

public:
    PendingMarriageDivorceConfirm( int cnum, const char* question, const char* msg )
        : PendingQuestion( cnum, question, psQuestionMessage::generalConfirm )
    {
        divorceMsg = msg;
    }

    void HandleAnswer(const csString & answer)
    {
        if (answer == "yes")
        {
            Client* client = psserver->GetConnections()->FindAny(clientnum);
            psserver->GetMarriageManager()->Divorce(client,divorceMsg);
        }
    }

    void HandleTimeout() { HandleAnswer("no"); }
};

void psMarriageManager::Propose( Client* client, csString proposedCharName, csString proposeMsg )
{
    if (!client)
        return;
        
    csString proposerName = client->GetCharacterData()->GetCharName();

    // Character cannot propose itself
    if ( proposedCharName.CompareNoCase( proposerName ) )
    {
        psserver->SendSystemError( client->GetClientNum(), "You cannot marry yourself." );
        return;
    }

    // Make sure the proposer is not already married
    if ( client->GetCharacterData()->GetIsMarried() )
    {
        psserver->SendSystemError( client->GetClientNum(),
            "You are already married to %s",
            client->GetCharacterData()->GetSpouseName() );
        return;
    }

    // Find the client of proposed char so we can send proposal
    Client* proposedClient = psserver->GetCharManager()->FindPlayerClient( proposedCharName, 0 );
    if ( proposedClient )
    {

        // Make sure that the proposed char is also single
        if ( proposedClient->GetCharacterData()->GetIsMarried() )
        {
            psserver->SendSystemResult( client->GetClientNum(), "%s is are already married to %s",
                proposedCharName.GetData(), proposedClient->GetCharacterData()->GetSpouseName() );
            return;
        }

        // Don't let same gender marriages (Kran raceid=9) is an exception)
        if ( (client->GetCharacterData()->GetRaceInfo()->race != 9)
            && (client->GetCharacterData()->GetRaceInfo()->gender ==
            proposedClient->GetCharacterData()->GetRaceInfo()->gender) )
        {
            psserver->SendSystemResult( client->GetClientNum(),
                "You cannot marry a person of your same gender." );
            return;
        }

        // If proposer is a Kran, he can only marry other Krans
        if ( (client->GetCharacterData()->GetRaceInfo()->race == 9) )
        {
            // Don't progress if proposed char is not Kran also
            if ( proposedClient->GetCharacterData()->GetRaceInfo()->race != 9 )
            {
                psserver->SendSystemError( client->GetClientNum(), "Krans can only marry other Krans." );
                return;
            }
        }
        // Proposer is not a Kran, then he cannot marry a Kran
        else
        {
            if ( proposedClient->GetCharacterData()->GetRaceInfo()->race == 9 )
            {
                psserver->SendSystemError( client->GetClientNum(), "Only Krans can marry other Krans." );
                return;
            }
        }

        // Make sure that Proposer & ProposedChar are near enough
        if ( proposedClient->GetActor()->RangeTo( client->GetActor() ) > RANGE_TO_SELECT )
        {
            psserver->SendSystemError( client->GetClientNum(), "You are not near enough to %s to propose.",
                proposedCharName.GetData() );
            return;
        }

        csString tempStr;
        tempStr.Format( "%s has made a marriage proposal telling you: '%s' Do you agree?",
                        proposerName.GetData(), proposeMsg.GetData() );

        PendingQuestion* invite = new PendingMarriageProposal( client, proposedClient, tempStr.GetData() );
        psserver->questionmanager->SendQuestion(invite);

        Debug3( LOG_MARRIAGE, client->GetClientNum(),"%s has proposed %s for Marriage", proposerName.GetData(), proposedCharName.GetData() );
    }
    else
    {
        psserver->SendSystemResult( client->GetClientNum(), "%s is not in Yliakum right now.", proposedCharName.GetData() );
    }
}

void psMarriageManager::ContemplateDivorce( Client* client, csString divorceMsg )
{
    if (!client)
        return;
        
    if ( !client->GetCharacterData()->GetIsMarried() )
    {
        psserver->SendSystemError( client->GetClientNum(), "Before divorcing, you should get married." );
        return;
    }

    csString promptMsg;
    promptMsg.Format( "Are you sure you want to divorce your dear spouse %s?",
                      client->GetCharacterData()->GetSpouseName() );

    PendingQuestion* prompt = new PendingMarriageDivorceConfirm( client->GetClientNum(), promptMsg, divorceMsg );
    psserver->questionmanager->SendQuestion(prompt);
}

void psMarriageManager::Divorce( Client* client, csString divorceMsg )
{
    if (!client)
        return;
        
    // We are sure 200% that we are gonna divorce.
    csString divorcerName = client->GetCharacterData()->GetCharName();

    psString divorcedCharFirstName;
    psString divorcedChar = client->GetCharacterData()->GetSpouseName();
    divorcedChar.GetWord( 0, divorcedCharFirstName );

    Client* divorcedClient = psserver->GetConnections()->Find( divorcedCharFirstName.GetData() );
    if ( divorcedClient )
    {
        // Make sure that the divorcer & divorced are near enough
        if ( divorcedClient->GetActor()->RangeTo( client->GetActor() ) > RANGE_TO_SELECT )
        {
            psserver->SendSystemError( client->GetClientNum(),
                "You are not near enough to %s to discuss a divorce.",
                divorcedChar.GetData());
            return;
        }

        csString tempStr;
        tempStr.Format( "%s wants to divorce for this reason '%s.' Do you agree?", divorcerName.GetData(),
            divorceMsg.GetData() );
        PendingQuestion *invite = new PendingMarriageDivorce( client, divorcedClient, tempStr.GetData() );
        psserver->questionmanager->SendQuestion(invite);

        Debug3( LOG_MARRIAGE, client->GetClientNum(),"%s wants to divorce from %s", divorcerName.GetData(), divorcedChar.GetData() );
        psserver->SendSystemResult( divorcedClient->GetClientNum(), "%s divorced from you saying: %s",
                                    divorcerName.GetData(), divorceMsg.GetData() );

    }
    else
    {
        int result = psserver->GetCharManager()->HasConnected( divorcedCharFirstName );
        //The partner doesn't exist.
        if ( result == 1 )
        {
            //If female
            if ( client->GetCharacterData()->GetRaceInfo()->gender == 1 )
            {
                psserver->SendSystemError( client->GetClientNum(), "%s is probably dead and you are a widow!",
                    divorcedCharFirstName.GetData() );
            }
            //If male or Kran
            else
            {
                psserver->SendSystemError( client->GetClientNum(), "%s is probably dead and you are a widower!",
                    divorcedCharFirstName.GetData() );
            }
            psserver->SendSystemInfo( client->GetClientNum(), "You can divorce anyway, but you have to contact a Game Master." );

        }
        else if ( result == 2 )
        {
            //The partner hasn't logon in the latest two months.
            psserver->SendSystemError( client->GetClientNum(), "%s hasn't walked in Yliakum in a long time.", divorcedChar.GetData());
            psserver->SendSystemInfo( client->GetClientNum(), "Contact a Game Master for obtaining a divorce.");
        }
        else
        {
            //If we are here, the partner char is still existing and it has been connecting recently. Wait to be online.
            psserver->SendSystemInfo( client->GetClientNum(), "%s is not in Yliakum right now.", divorcedChar.GetData());
        }
    }
}

bool psMarriageManager::SetSpouseName(  psCharacter* charData,  psCharacter* spouseData )
{
    // Must not be NULL
    CS_ASSERT(charData);
    //CS_ASSERT(spouseFullName);

    const char* charName = charData->GetCharName();
    const char* spouseFullName = spouseData->GetCharFullName();

    csString query;
    
    // Create this char's entry in character_marriage_details if not present
    this->CreateMarriageEntry( charData, spouseData );
             
    charData->SetSpouseName( spouseFullName );

    // Now if character was female then change her last name to her 
    // husband's last name
    if ( charData->GetRaceInfo()->gender == PSCHARACTER_GENDER_FEMALE )
    {
        // Save old lastname
        charData->SetOldLastName( charData->GetCharLastName() );

        charData->SetLastName( spouseData->GetCharLastName() );
        
        UpdateName( charData );
                 
    }

    Notify3( LOG_MARRIAGE, "SetSpouseName(): %s now has %s as spouse.", charName, spouseFullName );
    return true;
}

bool psMarriageManager::CreateMarriageEntry( psCharacter* charData,  psCharacter* spouseData )
{
    csString query;
    Result rs;
    
    // create entry of this character in 'character_relationships'

    // Get character's Unique ID number
    int id = charData->GetCharacterID();
    int spouse_id = spouseData->GetCharacterID();
    csString spouse_name;
    db->Escape(spouse_name, spouseData->GetCharFullName());        
        
    query.Format( "INSERT INTO character_relationships VALUES(%d,%d,'spouse','%s')", id, spouse_id, spouse_name.GetData() );
    if ( !db->Command( query.GetData() ) )
    {
        Error2( "CreateMarriageEntry(): DB Error: %s", db->GetLastError() );
        return false;
    }

    return true;
}

void psMarriageManager::DeleteMarriageInfo(  psCharacter* charData )
{
        
    // If this character is a female then revert it's lastname change
    if( charData->GetRaceInfo()->gender == PSCHARACTER_GENDER_FEMALE )
    {
        // Update cached old last name
        charData->SetLastName( charData->GetOldLastName() );
        charData->SetOldLastName( "" );

        UpdateName( charData ); 
    }

    //We remove the data about the marriage now.
    csString query;
    query.Format( "DELETE FROM character_relationships WHERE character_id='%d' and relationship_type='spouse'", charData->GetCharacterID() );
    if ( !db->Command( query.GetData() ) )
    {
        Error2( "DeleteMarriageInfo(): DB Error: %s", db->GetLastError() ); 
        return;
    }

    // Update cached spouse name
    charData->SetSpouseName( "" );
}

void psMarriageManager::UpdateName ( psCharacter* charData )
{
     gemActor* actor;

     actor = charData->GetActor();   
     if(!actor)
     {
        Error1("UpdateName(): Couldn't find an actor!");
        return;
     }

     psserver->CharacterLoader.SaveCharacterData( charData, actor, true );

     //charData->SetFullName( charData->GetCharName(), lastName );   
     actor->SetName( charData->GetCharFullName() );       

    //Update label
    psUpdateObjectNameMessage newNameMsg( 0, actor->GetEntity()->GetID(), charData->GetCharFullName() );
    psserver->GetEventManager()->Broadcast( newNameMsg.msg, NetBase::BC_EVERYONE );
    
 }
