/*
 * Centralised functions for the Interactor trait.
 */

#include "putty.h"

Seat *interactor_borrow_seat(Interactor *itr)
{
    Seat *clientseat = interactor_get_seat(itr);
    if (!clientseat)
        return NULL;

    /* If the client has already had its Seat borrowed, then look
     * through the existing TempSeat to find the underlying one. */
    if (is_tempseat(clientseat))
        return tempseat_get_real(clientseat);

    /* Otherwise, make a new TempSeat and give that to the client. */
    Seat *tempseat = tempseat_new(clientseat);
    interactor_set_seat(itr, tempseat);
    return clientseat;
}

static Interactor *interactor_toplevel(Interactor *itr, unsigned *level_out)
{
    /*
     * Find the Interactor at the top of the chain, so that all the
     * Interactors in a stack can share that one's last-to-talk field.
     * Also, count how far we had to go to get to it, to put in the
     * message.
     */
    Interactor *itr_top = itr;
    unsigned level = 0;
    while (itr_top->parent) {
        itr_top = itr_top->parent;
        level++;
    }

    if (level_out)
        *level_out = level;
    return itr_top;
}

void interactor_return_seat(Interactor *itr)
{
    Seat *tempseat = interactor_get_seat(itr);
    if (!is_tempseat(tempseat))
        return;                        /* no-op */

    /*
     * We're about to hand this seat back to the parent Interactor to
     * do its own thing with. It will typically expect to start in the
     * same state as if the seat had never been borrowed, i.e. in the
     * starting trust state.
     *
     * However, this may be overridden by the tempseat_flush call.
     */
    Seat *realseat = tempseat_get_real(tempseat);
    seat_set_trust_status(realseat, true);

    tempseat_flush(tempseat);
    interactor_set_seat(itr, realseat);
    tempseat_free(tempseat);

    /*
     * If we have a parent Interactor, and anyone has ever called
     * interactor_announce, then all Interactors from now on will
     * announce themselves even if they have nothing to say.
     */
    Interactor *itr_top = interactor_toplevel(itr, NULL);
    if (itr_top->last_to_talk)
        interactor_announce(itr);
}

InteractionReadySeat interactor_announce(Interactor *itr)
{
    Seat *seat = interactor_get_seat(itr);
    assert(!is_tempseat(seat) &&
           "Shouldn't call announce when someone else is using our seat");

    InteractionReadySeat iseat;
    iseat.seat = seat;

    unsigned level;
    Interactor *itr_top = interactor_toplevel(itr, &level);

    /*
     * Generally, we should announce ourself if the previous
     * Interactor that said anything was not us. That includes if
     * there was no previous Interactor to talk (i.e. if we're the
     * first to say anything) - *except* that the primary Interactor
     * doesn't need to announce itself, if no proxy has intervened
     * before it.
     */
    bool need_announcement = (itr_top->last_to_talk != itr);
    if (!itr->parent && !itr_top->last_to_talk)
        need_announcement = false;

    if (need_announcement) {
        const char *prefix = "";
        if (itr_top->last_to_talk != NULL)
            seat_antispoof_msg(iseat, ""); /* leave a separating blank line */

        char *desc = interactor_description(itr);
        char *adjective = (level == 0 ? dupstr("primary") :
                           level == 1 ? dupstr("proxy") :
                           dupprintf("proxy^%u", level));
        char *msg = dupprintf("%sMaking %s %s", prefix, adjective, desc);
        sfree(adjective);
        sfree(desc);

        seat_antispoof_msg(iseat, msg);
        sfree(msg);

        itr_top->last_to_talk = itr;
    }

    return iseat;
}
