/*      $Id$

        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, 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., Inc., 51 Franklin Street, Fifth Floor, Boston,
        MA 02110-1301, USA.


        xfwm4    - (c) 2002-2011 Olivier Fourdan

 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <glib.h>
#include <libxfce4util/libxfce4util.h>

#include "screen.h"
#include "client.h"
#include "stacking.h"
#include "transients.h"

Client *
clientGetTransient (Client * c)
{
    g_return_val_if_fail (c != NULL, NULL);

    TRACE ("client \"%s\" (0x%lx)", c->name, c->window);

    if ((c->transient_for) && (c->transient_for != c->screen_info->xroot))
    {
        return myScreenGetClientFromWindow (c->screen_info, c->transient_for, SEARCH_WINDOW|SEARCH_FRAME);
    }
    return NULL;
}

gboolean
clientIsDirectTransient (Client * c)
{
    g_return_val_if_fail (c != NULL, FALSE);

    TRACE ("client \"%s\" (0x%lx)", c->name, c->window);

    return ((c->transient_for != c->screen_info->xroot) && (c->transient_for != None) && (c->transient_for != c->window));
}

gboolean
clientIsTransientForGroup (Client * c)
{
    g_return_val_if_fail (c != NULL, FALSE);

    TRACE ("client \"%s\" (0x%lx)", c->name, c->window);

    return ((c->transient_for == c->screen_info->xroot) && (c->group_leader != None) && (c->group_leader != c->window));
}

gboolean
clientIsTransient (Client * c)
{
    g_return_val_if_fail (c != NULL, FALSE);

    TRACE ("client \"%s\" (0x%lx)", c->name, c->window);

    return (clientIsDirectTransient(c) || clientIsTransientForGroup (c));
}

gboolean
clientIsModal (Client * c)
{
    g_return_val_if_fail (c != NULL, FALSE);

    TRACE ("client \"%s\" (0x%lx)", c->name, c->window);
    /*
       If the WM_TRANSIENT_FOR hint is set to another toplevel window, the dialog is modal for that window;
       if WM_TRANSIENT_FOR is not set or set to the root window the dialog is modal for its window group.
     */
    return (FLAG_TEST (c->flags, CLIENT_FLAG_STATE_MODAL) && (c->type & WINDOW_REGULAR_FOCUSABLE) &&
            clientIsTransient (c));
}

gboolean
clientIsModalForGroup (Client * c)
{
    g_return_val_if_fail (c != NULL, FALSE);

    TRACE ("client \"%s\" (0x%lx)", c->name, c->window);

    return (FLAG_TEST (c->flags, CLIENT_FLAG_STATE_MODAL) && (c->type & WINDOW_REGULAR_FOCUSABLE) &&
            !clientIsTransient(c) && (c->group_leader != None));
}

gboolean
clientIsTransientOrModalForGroup (Client * c)
{
    g_return_val_if_fail (c != NULL, FALSE);

    TRACE ("client \"%s\" (0x%lx)", c->name, c->window);

    return (clientIsTransientForGroup(c) || clientIsModalForGroup(c));
}

gboolean
clientIsTransientOrModal (Client * c)
{
    g_return_val_if_fail (c != NULL, FALSE);

    TRACE ("client \"%s\" (0x%lx)", c->name, c->window);

    return (clientIsTransient(c) || clientIsModal(c));
}

gboolean
clientSameGroup (Client * c1, Client * c2)
{
    g_return_val_if_fail (c1 != NULL, FALSE);
    g_return_val_if_fail (c2 != NULL, FALSE);

    TRACE ("client 1 \"%s\" (0x%lx), client 2 \"%s\" (0x%lx)",
           c1->name, c1->window, c2->name, c2->window);

    return ((c1 != c2) &&
            (((c1->group_leader != None) &&
              (c1->group_leader == c2->group_leader)) ||
             (c1->group_leader == c2->window) ||
             (c2->group_leader == c1->window)));
}

gboolean
clientSameLeader (Client * c1, Client * c2)
{
    g_return_val_if_fail (c1 != NULL, FALSE);
    g_return_val_if_fail (c2 != NULL, FALSE);

    TRACE ("client 1 \"%s\" (0x%lx), client 2 \"%s\" (0x%lx)",
           c1->name, c1->window, c2->name, c2->window);

    return ((c1 != c2) &&
            (((c1->client_leader != None) &&
              (c1->client_leader == c2->client_leader)) ||
             (c1->client_leader == c2->window) ||
             (c2->client_leader == c1->window)));
}

gboolean
clientSameName (Client * c1, Client * c2)
{
    g_return_val_if_fail (c1 != NULL, FALSE);
    g_return_val_if_fail (c2 != NULL, FALSE);

    TRACE ("client 1 \"%s\" (0x%lx), client 2 \"%s\" (0x%lx)",
           c1->name, c1->window, c2->name, c2->window);

    return ((c1 != c2) &&
            (c1->class.res_class != NULL) &&
            (c2->class.res_class != NULL) &&
            (strcmp (c1->class.res_name, c2->class.res_name) == 0));
}

gboolean
clientIsTransientFor (Client * c1, Client * c2)
{
    g_return_val_if_fail (c1 != NULL, FALSE);
    g_return_val_if_fail (c2 != NULL, FALSE);

    TRACE ("client 1 \"%s\" (0x%lx), client 2 \"%s\" (0x%lx)",
           c1->name, c1->window, c2->name, c2->window);

    if (c1->transient_for)
    {
        if (c1->transient_for != c1->screen_info->xroot)
        {
            return (c1->transient_for == c2->window);
        }
        /*
         * Transients for group shouldn't apply to other transients, or
         * it breaks stacking for some apps, noticeably mozilla "save as"
         * dialog...
         */
        else if (c2->transient_for == None)
        {
            return (clientSameGroup (c1, c2));
        }
    }
    return FALSE;
}

gboolean
clientIsModalFor (Client * c1, Client * c2)
{
    g_return_val_if_fail (c1 != NULL, FALSE);
    g_return_val_if_fail (c2 != NULL, FALSE);

    TRACE ("client 1 \"%s\" (0x%lx), client 2 \"%s\" (0x%lx)",
           c1->name, c1->window, c2->name, c2->window);

    if (FLAG_TEST (c1->flags, CLIENT_FLAG_STATE_MODAL) && (c1->type & WINDOW_REGULAR_FOCUSABLE) && (c1->serial >= c2->serial))
    {
        /*
         * We used to consider all windows of the same group here
         * (ie. "clientSameGroup (c1, c2)") but that seems too
         * wide so we restric modality to transient relationship
         * for now.
         */
        return (clientIsTransientFor (c1, c2));
    }
    return FALSE;
}

gboolean
clientIsTransientOrModalFor (Client * c1, Client * c2)
{
    g_return_val_if_fail (c1 != NULL, FALSE);
    g_return_val_if_fail (c2 != NULL, FALSE);

    TRACE ("client 1 \"%s\" (0x%lx), client 2 \"%s\" (0x%lx)",
           c1->name, c1->window, c2->name, c2->window);

    return (clientIsTransientFor(c1, c2) || clientIsModalFor(c1, c2));
}

gboolean
clientIsValidTransientOrModal (Client * c)
{
    g_return_val_if_fail (c != NULL, FALSE);

    TRACE ("client \"%s\" (0x%lx)", c->name, c->window);
    if (clientIsTransientOrModalForGroup (c))
    {
        ScreenInfo *screen_info = c->screen_info;
        GList *list;

        /* Look for a valid transient or modal for the same group */
        for (list = screen_info->windows_stack; list; list = g_list_next (list))
        {
            Client *c2 = (Client *) list->data;
            if (c2 != c)
            {
                if (clientIsTransientOrModalFor (c, c2))
                {
                    /* We found one, look no further */
                    return TRUE;
                }
            }
        }
    }
    else if (clientIsTransientOrModal (c))
    {
        return (clientGetTransient (c) != NULL);
    }

    return (FALSE);
}

gboolean
clientTransientOrModalHasAncestor (Client * c, guint ws)
{
    Client *c2;
    GList *list;
    ScreenInfo *screen_info;

    g_return_val_if_fail (c != NULL, FALSE);

    TRACE ("client \"%s\" (0x%lx), workspace %u", c->name, c->window, ws);

    if (!clientIsTransientOrModal (c))
    {
        return FALSE;
    }

    screen_info = c->screen_info;
    for (list = screen_info->windows_stack; list; list = g_list_next (list))
    {
        c2 = (Client *) list->data;
        if ((c2 != c)
            && !clientIsTransientOrModal (c2)
            && clientIsTransientOrModalFor (c, c2)
            && FLAG_TEST (c2->xfwm_flags, XFWM_FLAG_VISIBLE)
            && (c2->win_workspace == ws)
            && (((ws == screen_info->current_ws) && FLAG_TEST (c2->xfwm_flags, XFWM_FLAG_VISIBLE))
                || !FLAG_TEST (c2->flags, CLIENT_FLAG_ICONIFIED)))
        {
            return TRUE;
        }
    }
    return FALSE;

}

Client *
clientGetModalFor (Client * c)
{
    ScreenInfo *screen_info;
    Client *c2;
    GList *list;

    g_return_val_if_fail (c != NULL, NULL);
    TRACE ("client \"%s\" (0x%lx)", c->name, c->window);

    screen_info = c->screen_info;
    for (list = g_list_last(screen_info->windows_stack); list; list = g_list_previous (list))
    {
        c2 = (Client *) list->data;
        if (c2)
        {
            if ((c2 != c) && clientIsModalFor (c2, c))
            {
                return c2;
            }
        }
    }
    return NULL;
}

/* Find the deepest direct parent of that window */
Client *
clientGetTransientFor (Client * c)
{
    ScreenInfo *screen_info;
    Client *first_parent;
    GList *l1, *l2;
    GList *parents;

    g_return_val_if_fail (c != NULL, NULL);
    TRACE ("client \"%s\" (0x%lx)", c->name, c->window);

    first_parent = c;
    parents = g_list_append (NULL, c);

    screen_info = c->screen_info;
    for (l1 = g_list_last(screen_info->windows_stack); l1; l1 = g_list_previous (l1))
    {
        Client *c2 = (Client *) l1->data;
        if (c2 == c)
        {
            continue;
        }

        if (c->win_layer > c2->win_layer)
        {
            break;
        }

        if (clientIsDirectTransient (c) && clientIsTransientFor (c, c2))
        {
            parents = g_list_append (parents, c2);
            first_parent = c2;
        }
        else
        {
            for (l2 = parents; l2; l2 = g_list_next (l2))
            {
                Client *c3 = (Client *) l2->data;
                if ((c3 != c2) && clientIsDirectTransient (c3) && clientIsTransientFor (c3, c2))
                {
                    parents = g_list_append (parents, c2);
                    first_parent = c2;
                }
            }
        }
    }
    g_list_free (parents);

    return first_parent;
}

/* Build a GList of clients that have a transient relationship */
GList *
clientListTransient (Client * c)
{
    ScreenInfo *screen_info;
    Client *c2, *c3;
    GList *transients;
    GList *l1, *l2;

    g_return_val_if_fail (c != NULL, NULL);
    TRACE ("client \"%s\" (0x%lx)", c->name, c->window);

    transients = g_list_append (NULL, c);

    screen_info = c->screen_info;
    for (l1 = screen_info->windows_stack; l1; l1 = g_list_next (l1))
    {
        c2 = (Client *) l1->data;
        if (c2 != c)
        {
            if (clientIsTransientFor (c2, c))
            {
                transients = g_list_append (transients, c2);
            }
            else
            {
                for (l2 = transients; l2; l2 = g_list_next (l2))
                {
                    c3 = (Client *) l2->data;
                    if ((c3 != c2) && clientIsTransientFor (c2, c3))
                    {
                        transients = g_list_append (transients, c2);
                        break;
                    }
                }
            }
        }
    }
    return transients;
}

/* Build a GList of clients that have a transient or modal relationship */
GList *
clientListTransientOrModal (Client * c)
{
    ScreenInfo *screen_info;
    Client *c2, *c3;
    GList *transients;
    GList *l1, *l2;

    g_return_val_if_fail (c != NULL, NULL);
    TRACE ("client \"%s\" (0x%lx)", c->name, c->window);

    transients = g_list_append (NULL, c);

    screen_info = c->screen_info;
    for (l1 = screen_info->windows_stack; l1; l1 = g_list_next (l1))
    {
        c2 = (Client *) l1->data;
        if (c2 != c)
        {
            if (clientIsTransientOrModalFor (c2, c))
            {
                transients = g_list_append (transients, c2);
            }
            else
            {
                for (l2 = transients; l2; l2 = g_list_next (l2))
                {
                    c3 = (Client *) l2->data;
                    if ((c3 != c2) && clientIsTransientOrModalFor (c2, c3))
                    {
                        transients = g_list_append (transients, c2);
                        break;
                    }
                }
            }
        }
    }
    return transients;
}

/* Check if a window is not already listed in transients of a client.
   That's to avoid potential self transient relationship...
 */
gboolean
clientCheckTransientWindow (Client *c, Window w)
{
    GList *transients;
    GList *list;
    Client *c2;

    g_return_val_if_fail (c != NULL, FALSE);
    TRACE ("client \"%s\" (0x%lx)", c->name, c->window);

    transients = clientListTransient (c);
    for (list = transients; list; list = g_list_next (list))
    {
        c2 = (Client *) list->data;
        if (c2->window == w)
        {
            g_list_free (transients);
            return FALSE;
        }
    }
    g_list_free (transients);
    return TRUE;
}

gboolean
clientSameApplication (Client *c1, Client *c2)
{
    g_return_val_if_fail (c1 != NULL, FALSE);
    g_return_val_if_fail (c2 != NULL, FALSE);

    TRACE ("client 1 \"%s\" (0x%lx), client 2 \"%s\" (0x%lx)",
           c1->name, c1->window, c2->name, c2->window);

    return (clientIsTransientOrModalFor (c1, c2) ||
            clientIsTransientOrModalFor (c2, c1) ||
            clientSameGroup (c1, c2) ||
            clientSameLeader (c1, c2) ||
            clientSameName (c1, c2) ||
            (c1->pid != 0 && c1->pid == c2->pid &&
             c1->hostname && c2->hostname &&
             !g_ascii_strcasecmp (c1->hostname, c2->hostname)));
}