/*****************************************************************
 KWin - the KDE window manager
 This file is part of the KDE project.

Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org>
Copyright (C) 1997 to 2002 Cristian Tibirna <tibirna@kde.org>
Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org>

You can Freely distribute this program under the GNU General Public
License. See the file "COPYING" for the exact licensing terms.
******************************************************************/

#include "placement.h"

#include <qrect.h>
#include <assert.h>

#ifndef KCMRULES
#include "workspace.h"
#include "client.h"
#include "options.h"
#include "rules.h"
#endif

namespace KWinInternal
{

#ifndef KCMRULES

Placement::Placement(Workspace* w)
    {
    m_WorkspacePtr = w;

    //CT initialize the cascading info
    for( int i = 0; i < m_WorkspacePtr->numberOfDesktops(); i++) 
        {
        DesktopCascadingInfo inf;
        inf.pos = QPoint(0,0);
        inf.col = 0;
        inf.row = 0;
        cci.append(inf);
        }

    }

/*!
  Places the client \a c according to the workspace's layout policy
 */
void Placement::place(Client* c, QRect& area )
    {
    Policy policy = c->rules()->checkPlacement( Default );
    if( policy != Default )
        return place( c, policy, area );

    if( c->isUtility())
        placeUtility(c, area);
    else if( c->isDialog())
        placeDialog(c, area);
    else if( c->isSplash())
        placeOnMainWindow( c, area ); // on mainwindow, if any, otherwise centered
    else
        place(c, options->placement, area);
    }

void Placement::place(Client* c, Policy policy, QRect& area )
    {
    if( policy == Default )
        policy = options->placement;
    if( policy == NoPlacement )
        return;
    else if (policy == Random)
        placeAtRandom(c, area);
    else if (policy == Cascade)
        placeCascaded(c, area);
    else if (policy == Centered)
        placeCentered(c, area);
    else if (policy == ZeroCornered)
        placeZeroCornered(c, area);
    else if (policy == UnderMouse)
        placeUnderMouse(c, area);
    else if (policy == OnMainWindow)
        placeOnMainWindow(c, area);
    else
        placeSmart(c, area);
    }

/*!
  Place the client \a c according to a simply "random" placement algorithm.
 */
void Placement::placeAtRandom(Client* c, const QRect& area )
    {
    const int step  = 24;
    static int px = step;
    static int py = 2 * step;
    int tx,ty;

    const QRect maxRect = checkArea( c, area );

    if (px < maxRect.x())
        px = maxRect.x();
    if (py < maxRect.y())
        py = maxRect.y();

    px += step;
    py += 2*step;

    if (px > maxRect.width()/2)
        px =  maxRect.x() + step;
    if (py > maxRect.height()/2)
        py =  maxRect.y() + step;
    tx = px;
    ty = py;
    if (tx + c->width() > maxRect.right())
        {
        tx = maxRect.right() - c->width();
        if (tx < 0)
            tx = 0;
        px =  maxRect.x();
        }
    if (ty + c->height() > maxRect.bottom())
        {
        ty = maxRect.bottom() - c->height();
        if (ty < 0)
            ty = 0;
        py =  maxRect.y();
        }
    c->move(tx, ty);
    }

/*!
  Place the client \a c according to a really smart placement algorithm :-)
*/
void Placement::placeSmart(Client* c, const QRect& area )
    {
    /*
     * SmartPlacement by Cristian Tibirna (tibirna@kde.org)
     * adapted for kwm (16-19jan98) and for kwin (16Nov1999) using (with
     * permission) ideas from fvwm, authored by
     * Anthony Martin (amartin@engr.csulb.edu).
     * Xinerama supported added by Balaji Ramani (balaji@yablibli.com)
     * with ideas from xfce.
     */

    const int none = 0, h_wrong = -1, w_wrong = -2; // overlap types
    long int overlap, min_overlap = 0;
    int x_optimal, y_optimal;
    int possible;
    int desktop = c->desktop() == 0 || c->isOnAllDesktops() ? m_WorkspacePtr->currentDesktop() : c->desktop();

    int cxl, cxr, cyt, cyb;     //temp coords
    int  xl,  xr,  yt,  yb;     //temp coords
    int basket;                 //temp holder

    // get the maximum allowed windows space
    const QRect maxRect = checkArea( c, area );
    int x = maxRect.left(), y = maxRect.top();
    x_optimal = x; y_optimal = y;

    //client gabarit
    int ch = c->height() - 1;
    int cw = c->width()  - 1;

    bool first_pass = true; //CT lame flag. Don't like it. What else would do?

    //loop over possible positions
    do 
        {
        //test if enough room in x and y directions
        if (y + ch > maxRect.bottom() && ch < maxRect.height())
            overlap = h_wrong; // this throws the algorithm to an exit
        else if(x + cw > maxRect.right())
            overlap = w_wrong;
        else 
            {
            overlap = none; //initialize

            cxl = x; cxr = x + cw;
            cyt = y; cyb = y + ch;
            ClientList::ConstIterator l;
            for(l = m_WorkspacePtr->stackingOrder().begin(); l != m_WorkspacePtr->stackingOrder().end() ; ++l) 
                {
                if((*l)->isOnDesktop(desktop) &&
                   (*l)->isShown( false ) && (*l) != c) 
                    {

                    xl = (*l)->x();          yt = (*l)->y();
                    xr = xl + (*l)->width(); yb = yt + (*l)->height();

                    //if windows overlap, calc the overall overlapping
                    if((cxl < xr) && (cxr > xl) &&
                       (cyt < yb) && (cyb > yt)) 
                        {
                        xl = QMAX(cxl, xl); xr = QMIN(cxr, xr);
                        yt = QMAX(cyt, yt); yb = QMIN(cyb, yb);
                        if((*l)->keepAbove())
                            overlap += 16 * (xr - xl) * (yb - yt);
                        else if((*l)->keepBelow() && !(*l)->isDock()) // ignore KeepBelow windows
                            overlap += 0; // for placement (see Client::belongsToLayer() for Dock)
                        else
                            overlap += (xr - xl) * (yb - yt);
                        }
                    }
                }
            }

        //CT first time we get no overlap we stop.
        if (overlap == none) 
            {
            x_optimal = x;
            y_optimal = y;
            break;
            }

        if (first_pass) 
            {
            first_pass = false;
            min_overlap = overlap;
            }
        //CT save the best position and the minimum overlap up to now
        else if (overlap >= none && overlap < min_overlap) 
            {
            min_overlap = overlap;
            x_optimal = x;
            y_optimal = y;
            }

        // really need to loop? test if there's any overlap
        if (overlap > none) 
            {

            possible = maxRect.right();
            if (possible - cw > x) possible -= cw;

            // compare to the position of each client on the same desk
            ClientList::ConstIterator l;
            for(l = m_WorkspacePtr->stackingOrder().begin(); l != m_WorkspacePtr->stackingOrder().end() ; ++l) 
                {

                if ((*l)->isOnDesktop(desktop) &&
                     (*l)->isShown( false ) &&  (*l) != c) 
                    {

                    xl = (*l)->x();          yt = (*l)->y();
                    xr = xl + (*l)->width(); yb = yt + (*l)->height();

                    // if not enough room above or under the current tested client
                    // determine the first non-overlapped x position
                    if((y < yb) && (yt < ch + y)) 
                        {

                        if((xr > x) && (possible > xr)) possible = xr;

                        basket = xl - cw;
                        if((basket > x) && (possible > basket)) possible = basket;
                        }
                    }
                }
            x = possible;
            }

        // ... else ==> not enough x dimension (overlap was wrong on horizontal)
        else if (overlap == w_wrong) 
            {
            x = maxRect.left();
            possible = maxRect.bottom();

            if (possible - ch > y) possible -= ch;

            //test the position of each window on the desk
            ClientList::ConstIterator l;
            for(l = m_WorkspacePtr->stackingOrder().begin(); l != m_WorkspacePtr->stackingOrder().end() ; ++l) 
                {
                if((*l)->isOnDesktop(desktop) &&
                    (*l) != c   &&  c->isShown( false )) 
                    {

                    xl = (*l)->x();          yt = (*l)->y();
                    xr = xl + (*l)->width(); yb = yt + (*l)->height();

                    // if not enough room to the left or right of the current tested client
                    // determine the first non-overlapped y position
                    if((yb > y) && (possible > yb)) possible = yb;

                    basket = yt - ch;
                    if((basket > y) && (possible > basket)) possible = basket;
                    }
                }
            y = possible;
            }
        }
    while((overlap != none) && (overlap != h_wrong) && (y < maxRect.bottom()));

    if(ch>= maxRect.height())
        y_optimal=maxRect.top();

    // place the window
    c->move(x_optimal, y_optimal);

    }

/*!
  Place windows in a cascading order, remembering positions for each desktop
*/
void Placement::placeCascaded (Client* c, const QRect& area, bool re_init)
    {
    /* cascadePlacement by Cristian Tibirna (tibirna@kde.org) (30Jan98)
     */
    // work coords
    int xp, yp;

    //CT how do I get from the 'Client' class the size that NW squarish "handle"
    const int delta_x = 24;
    const int delta_y = 24;

    const int dn = c->desktop() == 0 || c->isOnAllDesktops() ? (m_WorkspacePtr->currentDesktop() - 1) : (c->desktop() - 1);

    // get the maximum allowed windows space and desk's origin
    QRect maxRect = checkArea( c, area );

    // initialize often used vars: width and height of c; we gain speed
    const int ch = c->height();
    const int cw = c->width();
    const int X = maxRect.left();
    const int Y = maxRect.top();
    const int H = maxRect.height();
    const int W = maxRect.width();

  //initialize if needed
    if (re_init || cci[dn].pos.x() < X || cci[dn].pos.y() < Y ) 
        {
        cci[dn].pos = QPoint(X, Y);
        cci[dn].col = cci[dn].row = 0;
        }


    xp = cci[dn].pos.x();
    yp = cci[dn].pos.y();

    //here to touch in case people vote for resize on placement
    if ((yp + ch) > H) yp = Y;

    if ((xp + cw) > W)
        if (!yp) 
        {
        placeSmart(c,area);
        return;
        }
    else xp = X;

  //if this isn't the first window
    if (cci[dn].pos.x() != X && cci[dn].pos.y() != Y) 
        {
        /* The following statements cause an internal compiler error with
         * egcs-2.91.66 on SuSE Linux 6.3. The equivalent forms compile fine.
         * 22-Dec-1999 CS
         *
         * if (xp != X && yp == Y) xp = delta_x * (++(cci[dn].col));
         * if (yp != Y && xp == X) yp = delta_y * (++(cci[dn].row));
         */
    if (xp != X && yp == Y)
            {
            ++(cci[dn].col);
            xp = delta_x * cci[dn].col;
            }
        if (yp != Y && xp == X)
            {
            ++(cci[dn].row);
            yp = delta_y * cci[dn].row;
            }

        // last resort: if still doesn't fit, smart place it
        if (((xp + cw) > W - X) || ((yp + ch) > H - Y)) 
            {
            placeSmart(c,area);
            return;
            }
        }

    // place the window
    c->move(QPoint(xp, yp));

    // new position
    cci[dn].pos = QPoint(xp + delta_x,  yp + delta_y);
    }

/*!
  Place windows centered, on top of all others
*/
void Placement::placeCentered (Client* c, const QRect& area )
    {

    // get the maximum allowed windows space and desk's origin
    const QRect maxRect = checkArea( c, area );

    const int xp = maxRect.left() + (maxRect.width() -  c->width())  / 2;
    const int yp = maxRect.top()  + (maxRect.height() - c->height()) / 2;

    // place the window
    c->move(QPoint(xp, yp));
    }

/*!
  Place windows in the (0,0) corner, on top of all others
*/
void Placement::placeZeroCornered(Client* c, const QRect& area )
    {
    // get the maximum allowed windows space and desk's origin
    const QRect maxRect = checkArea( c, area );

    // place the window
    c->move(QPoint(maxRect.left(), maxRect.top()));
    }

void Placement::placeUtility(Client* c, QRect& area )
    {
// TODO kwin should try to place utility windows next to their mainwindow,
// preferably at the right edge, and going down if there are more of them
// if there's not enough place outside the mainwindow, it should prefer
// top-right corner
    // use the default placement for now
    place( c, Default, area );
    }


void Placement::placeDialog(Client* c, QRect& area )
    {
    placeOnMainWindow( c, area );
    }

void Placement::placeUnderMouse(Client* c, QRect& area )
    {
    area = checkArea( c, area );
    QRect geom = c->geometry();
    geom.moveCenter( QCursor::pos());
    c->move( geom.topLeft());
    c->keepInArea( area ); // make sure it's kept inside workarea
    }

void Placement::placeOnMainWindow(Client* c, QRect& area )
    {
    area = checkArea( c, area );
    ClientList mainwindows = c->mainClients();
    Client* place_on = NULL;
    Client* place_on2 = NULL;
    int mains_count = 0;
    for( ClientList::ConstIterator it = mainwindows.begin();
         it != mainwindows.end();
         ++it )
        {
        if( (*it)->isSpecialWindow() && !(*it)->isOverride())
            continue; // don't consider toolbars etc when placing
        ++mains_count;
        place_on2 = *it;
        if( (*it)->isOnCurrentDesktop())
            {
            if( place_on == NULL )
                place_on = *it;
            else
                { // two or more on current desktop -> center
                placeCentered( c, area );
                return;
                }
            }
        }
    if( place_on == NULL )
        { // 'mains_count' is used because it doesn't include ignored mainwindows
        if( mains_count != 1 )
            {
            placeCentered( c, area );
            return;
            }
        place_on = place_on2; // use the only window filtered together with 'mains_count'
        }
    QRect geom = c->geometry();
    geom.moveCenter( place_on->geometry().center());
    c->move( geom.topLeft());
    // get area again, because the mainwindow may be on different xinerama screen
    area = checkArea( c, QRect()); 
    c->keepInArea( area ); // make sure it's kept inside workarea
    }

QRect Placement::checkArea( const Client* c, const QRect& area )
    {
    if( area.isNull())
        return m_WorkspacePtr->clientArea( PlacementArea, c->geometry().center(), c->desktop());
    return area;
    }

#endif


Placement::Policy Placement::policyFromString( const QString& policy, bool no_special )
    {
    if( policy == "NoPlacement" )
        return NoPlacement;
    else if( policy == "Default" && !no_special )
        return Default;
    else if( policy == "Random" )
        return Random;
    else if( policy == "Cascade" )
        return Cascade;
    else if( policy == "Centered" )
        return Centered;
    else if( policy == "ZeroCornered" )
        return ZeroCornered;
    else if( policy == "UnderMouse" && !no_special)
        return UnderMouse;
    else if( policy == "OnMainWindow" && !no_special)
        return OnMainWindow;
    else
        return Smart;
    }

const char* Placement::policyToString( Policy policy )
    {
    const char* const policies[] =
        { "NoPlacement", "Default", "Random", "Smart", "Cascade", "Centered",
            "ZeroCornered", "UnderMouse", "OnMainWindow" };
    assert( policy < int( sizeof( policies ) / sizeof( policies[ 0 ] )));
    return policies[ policy ];
    }


#ifndef KCMRULES

// ********************
// Workspace
// ********************

/*!
  Moves active window left until in bumps into another window or workarea edge.
 */
void Workspace::slotWindowPackLeft()
    {
    if( active_client && active_client->isMovable())
        active_client->move( packPositionLeft( active_client, active_client->geometry().left(), true ),
            active_client->y());
    }

void Workspace::slotWindowPackRight()
    {
    if( active_client && active_client->isMovable())
        active_client->move( 
            packPositionRight( active_client, active_client->geometry().right(), true )
            - active_client->width() + 1, active_client->y());
    }

void Workspace::slotWindowPackUp()
    {
    if( active_client && active_client->isMovable())
        active_client->move( active_client->x(),
            packPositionUp( active_client, active_client->geometry().top(), true ));
    }

void Workspace::slotWindowPackDown()
    {
    if( active_client && active_client->isMovable())
        active_client->move( active_client->x(),
            packPositionDown( active_client, active_client->geometry().bottom(), true ) - active_client->height() + 1 );
    }

void Workspace::slotWindowGrowHorizontal()
    {
    if( active_client )
        active_client->growHorizontal();
    }

void Client::growHorizontal()
    {
    if( !isResizable())
        return;
    QRect geom = geometry();
    geom.setRight( workspace()->packPositionRight( this, geom.right(), true ));
    QSize adjsize = adjustedSize( geom.size(), SizemodeFixedW );
    if( geometry().size() == adjsize && geom.size() != adjsize && xSizeHint.width_inc > 1 ) // take care of size increments
        {
        int newright = workspace()->packPositionRight( this, geom.right() + xSizeHint.width_inc - 1, true );
        // check that it hasn't grown outside of the area, due to size increments
        // TODO this may be wrong?
        if( workspace()->clientArea( MovementArea,
            QPoint(( x() + newright ) / 2, geometry().center().y()), desktop()).right() >= newright )
            geom.setRight( newright );
        }
    geom.setSize( adjustedSize( geom.size(), SizemodeFixedW ));
    setGeometry( geom );
    }

void Workspace::slotWindowShrinkHorizontal()
    {
    if( active_client )
        active_client->shrinkHorizontal();
    }

void Client::shrinkHorizontal()
    {
    if( !isResizable())
        return;
    QRect geom = geometry();
    geom.setRight( workspace()->packPositionLeft( this, geom.right(), false ));
    if( geom.width() <= 1 )
        return;
    geom.setSize( adjustedSize( geom.size(), SizemodeFixedW ));
    if( geom.width() > 20 )
        setGeometry( geom );
    }

void Workspace::slotWindowGrowVertical()
    {
    if( active_client )
        active_client->growVertical();
    }

void Client::growVertical()
    {
    if( !isResizable())
        return;
    QRect geom = geometry();
    geom.setBottom( workspace()->packPositionDown( this, geom.bottom(), true ));
    QSize adjsize = adjustedSize( geom.size(), SizemodeFixedH );
    if( geometry().size() == adjsize && geom.size() != adjsize && xSizeHint.height_inc > 1 ) // take care of size increments
        {
        int newbottom = workspace()->packPositionDown( this, geom.bottom() + xSizeHint.height_inc - 1, true );
        // check that it hasn't grown outside of the area, due to size increments
        if( workspace()->clientArea( MovementArea,
            QPoint( geometry().center().x(), ( y() + newbottom ) / 2 ), desktop()).bottom() >= newbottom )
            geom.setBottom( newbottom );
        }
    geom.setSize( adjustedSize( geom.size(), SizemodeFixedH ));
    setGeometry( geom );
    }


void Workspace::slotWindowShrinkVertical()
    {
    if( active_client )
        active_client->shrinkVertical();
    }

void Client::shrinkVertical()
    {
    if( !isResizable())
        return;
    QRect geom = geometry();
    geom.setBottom( workspace()->packPositionUp( this, geom.bottom(), false ));
    if( geom.height() <= 1 )
        return;
    geom.setSize( adjustedSize( geom.size(), SizemodeFixedH ));
    if( geom.height() > 20 )
        setGeometry( geom );
    }

int Workspace::packPositionLeft( const Client* cl, int oldx, bool left_edge ) const
    {
    int newx = clientArea( MovementArea, cl ).left();
    if( oldx <= newx ) // try another Xinerama screen
        newx = clientArea( MovementArea,
            QPoint( cl->geometry().left() - 1, cl->geometry().center().y()), cl->desktop()).left();
    if( oldx <= newx )
        return oldx;
    for( ClientList::ConstIterator it = clients.begin();
         it != clients.end();
         ++it)
        {
        if( !(*it)->isShown( false ) || !(*it)->isOnDesktop( active_client->desktop()))
            continue;
        int x = left_edge ? (*it)->geometry().right() + 1 : (*it)->geometry().left() - 1;
        if( x > newx && x < oldx
            && !( cl->geometry().top() > (*it)->geometry().bottom() // they overlap in Y direction
                || cl->geometry().bottom() < (*it)->geometry().top()))
            newx = x;
        }
    return newx;
    }

int Workspace::packPositionRight( const Client* cl, int oldx, bool right_edge ) const
    {
    int newx = clientArea( MovementArea, cl ).right();
    if( oldx >= newx ) // try another Xinerama screen
        newx = clientArea( MovementArea,
            QPoint( cl->geometry().right() + 1, cl->geometry().center().y()), cl->desktop()).right();
    if( oldx >= newx )
        return oldx;
    for( ClientList::ConstIterator it = clients.begin();
         it != clients.end();
         ++it)
        {
        if( !(*it)->isShown( false ) || !(*it)->isOnDesktop( cl->desktop()))
            continue;
        int x = right_edge ? (*it)->geometry().left() - 1 : (*it)->geometry().right() + 1;
        if( x < newx && x > oldx
            && !( cl->geometry().top() > (*it)->geometry().bottom()
                || cl->geometry().bottom() < (*it)->geometry().top()))
            newx = x;
        }
    return newx;
    }

int Workspace::packPositionUp( const Client* cl, int oldy, bool top_edge ) const
    {
    int newy = clientArea( MovementArea, cl ).top();
    if( oldy <= newy ) // try another Xinerama screen
        newy = clientArea( MovementArea,
            QPoint( cl->geometry().center().x(), cl->geometry().top() - 1 ), cl->desktop()).top();
    if( oldy <= newy )
        return oldy;
    for( ClientList::ConstIterator it = clients.begin();
         it != clients.end();
         ++it)
        {
        if( !(*it)->isShown( false ) || !(*it)->isOnDesktop( cl->desktop()))
            continue;
        int y = top_edge ? (*it)->geometry().bottom() + 1 : (*it)->geometry().top() - 1;
        if( y > newy && y < oldy
            && !( cl->geometry().left() > (*it)->geometry().right() // they overlap in X direction
                || cl->geometry().right() < (*it)->geometry().left()))
            newy = y;
        }
    return newy;
    }

int Workspace::packPositionDown( const Client* cl, int oldy, bool bottom_edge ) const
    {
    int newy = clientArea( MovementArea, cl ).bottom();
    if( oldy >= newy ) // try another Xinerama screen
        newy = clientArea( MovementArea,
            QPoint( cl->geometry().center().x(), cl->geometry().bottom() + 1 ), cl->desktop()).bottom();
    if( oldy >= newy )
        return oldy;
    for( ClientList::ConstIterator it = clients.begin();
         it != clients.end();
         ++it)
        {
        if( !(*it)->isShown( false ) || !(*it)->isOnDesktop( cl->desktop()))
            continue;
        int y = bottom_edge ? (*it)->geometry().top() - 1 : (*it)->geometry().bottom() + 1;
        if( y < newy && y > oldy
            && !( cl->geometry().left() > (*it)->geometry().right()
                || cl->geometry().right() < (*it)->geometry().left()))
            newy = y;
        }
    return newy;
    }

/*!
  Asks the internal positioning object to place a client
*/
void Workspace::place(Client* c, QRect& area)
    {
    initPositioning->place( c, area );
    }

void Workspace::placeSmart(Client* c, const QRect& area)
    {
    initPositioning->placeSmart( c, area );
    }

#endif

} // namespace
