//
// Client.hh for pekwm
// Copyright © 2003-2009 Claes Nästén <me{@}pekdon{.}net>
//
// client.hh for aewm++
// Copyright (C) 2002 Frank Hale <frankhale@yahoo.com>
// http://sapphire.sourceforge.net/
//
// This program is licensed under the GNU GPL.
// See the LICENSE file for more information.
//

#ifndef _CLIENT_HH_
#define _CLIENT_HH_

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

#include "pekwm.hh"
#include "Observer.hh"
#include "PTexturePlain.hh"
#include "PDecor.hh"

class PScreen;
class Strut;
class PWinObj;
class ClassHint;
class AutoProperty;
class Frame;

#include <string>

extern "C" {
#include <X11/Xutil.h>
}

class LayerObservation : public Observation {
public:
    LayerObservation(enum Layer _layer) : layer(_layer) { };
    virtual ~LayerObservation(void) { };
public:
    const enum Layer layer; /**< Layer client changed to. */
};

class Client : public PWinObj, public Observer
{
    // FIXME: This relationship should end as soon as possible, but I need to
    // figure out a good way of sharing. :)
    friend class Frame;

public: // Public Member Functions
    struct MwmHints {
        ulong flags;
        ulong functions;
        ulong decorations;
    };
    enum {
        MWM_HINTS_FUNCTIONS = (1L << 0),
        MWM_HINTS_DECORATIONS = (1L << 1),
        MWM_HINTS_NUM = 3
    };
    enum {
        MWM_FUNC_ALL = (1L << 0),
        MWM_FUNC_RESIZE = (1L << 1),
        MWM_FUNC_MOVE = (1L << 2),
        MWM_FUNC_ICONIFY = (1L << 3),
        MWM_FUNC_MAXIMIZE = (1L << 4),
        MWM_FUNC_CLOSE = (1L << 5)
    };
    enum {
        MWM_DECOR_ALL = (1L << 0),
        MWM_DECOR_BORDER = (1L << 1),
        MWM_DECOR_HANDLE = (1L << 2),
        MWM_DECOR_TITLE = (1L << 3),
        MWM_DECOR_MENU = (1L << 4),
        MWM_DECOR_ICONIFY = (1L << 5),
        MWM_DECOR_MAXIMIZE = (1L << 6)
    };

    Client(Window new_client, bool is_new = false);
    virtual ~Client(void);

    // START - PWinObj interface.
    virtual void mapWindow(void);
    virtual void unmapWindow(void);
    virtual void iconify(void);
    virtual void stick(void);

    virtual void move(int x, int y);
    virtual void resize(uint width, uint height);
    virtual void moveResize(int x, int y, uint width, uint height);

    virtual void setWorkspace(uint workspace);

    virtual void giveInputFocus(void);
    virtual void reparent(PWinObj *parent, int x, int y);

    virtual ActionEvent *handleButtonPress(XButtonEvent *ev) {
        if (_parent) { return _parent->handleButtonPress(ev); }
        return 0;
    }
    virtual ActionEvent *handleButtonRelease(XButtonEvent *ev) {
        if (_parent) { return _parent->handleButtonRelease(ev); }
        return 0;
    }

    virtual ActionEvent *handleMapRequest(XMapRequestEvent *ev);
    virtual ActionEvent *handleUnmapEvent(XUnmapEvent *ev);
    // END - PWinObj interface.

    // START - Observer interface.
    virtual void notify(Observable *observable, Observation *observation);
    // END - Observer interface.

    static Client *findClient(Window win);
    static Client *findClientFromWindow(Window win);
    static Client *findClientFromHint(const ClassHint *class_hint);
    static Client *findClientFromID(uint id);
    static void findFamilyFromWindow(std::list<Client*> &client_list, Window win);

    static void mapOrUnmapTransients(Window win, bool hide);

    // START - Iterators
    static uint client_size(void) { return _client_list.size(); }
    static std::list<Client*>::iterator client_begin(void) {
        return _client_list.begin();
    }
    static std::list<Client*>::iterator client_end(void) {
        return _client_list.end();
    }
    static std::list<Client*>::reverse_iterator client_rbegin(void) {
        return _client_list.rbegin();
    }
    static std::list<Client*>::reverse_iterator client_rend(void) {
        return _client_list.rend();
    }

    unsigned int transient_size(void) { return _transient_clients.size(); }
    std::list<Client*>::iterator transient_begin(void) {
        return _transient_clients.begin(); }
    std::list<Client*>::iterator transient_end(void) {
        return _transient_clients.end(); }
    // END - Iterators

    bool validate(void);

    inline uint getClientID(void) { return _id; }
    /**< Return title item for client name. */
    inline PDecor::TitleItem *getTitle(void) { return &_title; }
    /**< Return title item for client icon name. */
    inline PDecor::TitleItem *getIconName(void) { return &_icon_name; }

    inline const ClassHint* getClassHint(void) const { return _class_hint; }

    bool isTransient(void) const { return _transient_window != None; }
    Client *getTransientClient(void) const { return _transient; }
    Window getTransientClientWindow(void) const { return _transient_window; }
    void findAndRaiseIfTransient(void);

    inline XSizeHints* getXSizeHints(void) const { return _size; }

    bool isViewable(void);
    bool setPUPosition(void);

    inline bool hasTitlebar(void) const { return (_state.decor&DECOR_TITLEBAR); }
    inline bool hasBorder(void) const { return (_state.decor&DECOR_BORDER); }
    inline bool isShaped(void) const { return _shaped; }
    inline bool hasStrut(void) const { return (_strut); }
    Strut *getStrut(void) const { return _strut; }

    PTexture *getIcon(void) const { return _icon; }

    /** Return PID of client. */
    long getPid(void) const { return _pid; }
    /** Return true if client is remote. */
    bool isRemote(void) const { return _is_remote; }

    // State accessors
    inline bool isMaximizedVert(void) const { return _state.maximized_vert; }
    inline bool isMaximizedHorz(void) const { return _state.maximized_horz; }
    inline bool isShaded(void) const { return _state.shaded; }
    inline bool isFullscreen(void) const { return _state.fullscreen; }
    inline bool isPlaced(void) const { return _state.placed; }
    inline uint getInitialFrameOrder(void) const { return _state.initial_frame_order; }
    inline uint getSkip(void) const { return _state.skip; }
    inline bool isSkip(Skip skip) const { return (_state.skip&skip); }
    inline uint getDecorState(void) const { return _state.decor; }
    inline bool isCfgDeny(uint deny) { return (_state.cfg_deny&deny); }

    inline bool allowMove(void) const { return _actions.move; }
    inline bool allowResize(void) const { return _actions.resize; }
    inline bool allowMinimize(void) const { return _actions.minimize; }
    inline bool allowShade(void) const { return _actions.shade; }
    inline bool allowStick(void) const { return _actions.stick; }
    inline bool allowMaximizeHorz(void) const { return _actions.maximize_horz; }
    inline bool allowMaximizeVert(void) const { return _actions.maximize_vert; }
    inline bool allowFullscreen(void) const { return _actions.fullscreen; }
    inline bool allowChangeDesktop(void) const { return _actions.change_desktop; }
    inline bool allowClose(void) const { return _actions.close; }

    inline bool isAlive(void) const { return _alive; }
    inline bool isMarked(void) const { return _marked; }

    // We have this public so that we can reload button actions.
    void grabButtons(void);

    void setStateCfgDeny(StateAction sa, uint deny);
    inline void setStateMarked(StateAction sa) {
        if (ActionUtil::needToggle(sa, _marked)) {
            _marked = !_marked;
            if (_marked) {
                _title.infoAdd(PDecor::TitleItem::INFO_MARKED);
            } else {
                _title.infoRemove(PDecor::TitleItem::INFO_MARKED);
            }
            _title.updateVisible();
        }
    }

    // toggles
    void alwaysOnTop(bool top);
    void alwaysBelow(bool bottom);

    void setSkip(uint skip);

    inline void setStateSkip(StateAction sa, Skip skip) {
        if ((isSkip(skip) && (sa == STATE_SET)) || (! isSkip(skip) && (sa == STATE_UNSET))) {
            return;
        }
        _state.skip ^= skip;
    }

    void setStateDemandsAttention(StateAction sa, bool attention);

    inline void setTitlebar(bool titlebar) {
        if (titlebar) {
            _state.decor |= DECOR_TITLEBAR;
        } else {
            _state.decor &= ~DECOR_TITLEBAR;
        }
    }
    inline void setBorder(bool border) {
        if (border) {
            _state.decor |= DECOR_BORDER;
        } else {
            _state.decor &= ~DECOR_BORDER;
        }
    }

  /** Set shaped flag on Client. */
  inline void setShaped(bool shaped) {
    _shaped = shaped;
  }

    void close(void);
    void kill(void);

    // Event handlers below - Used by WindowManager
    void handleDestroyEvent(XDestroyWindowEvent *ev);
    void handleColormapChange(XColormapEvent *ev);

    inline bool setConfigureRequestLock(bool lock) {
        bool old_lock = _cfg_request_lock;
        _cfg_request_lock = lock;
        return old_lock;
    }

    void configureRequestSend(void);
    void sendTakeFocusMessage(void);

    bool getAspectSize(uint *r_w, uint *r_h, uint w, uint h);
    bool getIncSize(uint *r_w, uint *r_h, uint w, uint h, bool incr=false);

    bool getEwmhStates(NetWMStates &win_states);
    void updateEwmhStates(void);

    void getWMNormalHints(void);
    void getWMProtocols(void);
    void getTransientForHint(void);
    void getStrutHint(void);
    void readName(void);
    void readIconName(void);
    void removeStrutHint(void);
    
    long getPekwmFrameOrder(void);
    void setPekwmFrameOrder(long num);
    bool getPekwmFrameActive(void);
    void setPekwmFrameActive(bool active);

    static void setClientEnvironment(Client *client);
    AutoProperty* readAutoprops(uint type = 0);

private:
    bool getAndUpdateWindowAttributes(void);

    void findOrCreateFrame(AutoProperty *autoproperty);
    bool findTaggedFrame(void);
    bool findPreviousFrame(void);
    bool findAutoGroupFrame(AutoProperty *autoproperty);

    void setInitialState(void);
    void setMappedStateAndFocus(bool is_new, AutoProperty *autoproperty);

    bool titleApplyRule(std::wstring &wtitle);
    uint titleFindID(std::wstring &wtitle);

    void setWmState(ulong state);
    long getWmState(void);

    int sendXMessage(Window window, Atom atom, long mask,
                     long v1 = 0l, long v2 = 0l, long v3 = 0l,
                     long v4 = 0l, long v5 = 0l);

    MwmHints* getMwmHints(Window w);

    // these are used by frame
    inline void setMaximizedVert(bool m) { _state.maximized_vert = m; }
    inline void setMaximizedHorz(bool m) { _state.maximized_horz = m; }
    inline void setShade(bool s) { _state.shaded = s; }
    inline void setFullscreen(bool f) { _state.fullscreen = f; }
    inline void setFocusable(bool f) { _focusable = f; }

    // Grabs button with Caps,Num and so on
    void grabButton(int button, int mod, int mask, Window win, Cursor curs);

    void readHints(void);
    ulong readWmHints(void);
    void readClassRoleHints(void);
    void readEwmhHints(void);
    void readMwmHints(void);
    void readPekwmHints(void);
    void readIcon(void);
    void applyAutoprops(AutoProperty *ap);
    void applyActionAccessMask(uint mask, bool value);
    void readClientPid(void);
    void readClientRemote(void);

    static uint findClientID(void);
    static void returnClientID(uint id);

private: // Private Member Variables
    uint _id; //<! Unique ID of the Client.

    XSizeHints *_size;
    Colormap _cmap;

    Client *_transient; /**< Client for which this client is transient for */
    Window _transient_window;
    std::list<Client*> _transient_clients; /**< List of transient clients. */

    Strut *_strut;

    PDecor::TitleItem _title; /**< Name of the client. */
    PDecor::TitleItem _icon_name; /**< Name of the client when iconified. */
    PTextureImage *_icon;
    
    long _pid; /**< _NET_WM_PID of the client, only valid if is_remote is false. */
    bool _is_remote; /**< Boolean flag  */

    ClassHint *_class_hint;

    bool _alive, _marked;
    bool _send_focus_message, _send_close_message, _wm_hints_input;
    bool _cfg_request_lock;
    bool _shaped;
    bool _extended_net_name;

    class State {
    public:
        State(void)
            : maximized_vert(false), maximized_horz(false), shaded(false), fullscreen(false),
              placed(false), initial_frame_order(0),
              skip(0), decor(DECOR_TITLEBAR|DECOR_BORDER),
              cfg_deny(CFG_DENY_NO), demands_attention(true) { }
        ~State(void) { }

        bool maximized_vert, maximized_horz;
        bool shaded;
        bool fullscreen;

        // pekwm states
        bool placed;
        uint initial_frame_order; /**< Initial frame position */
        uint skip, decor, cfg_deny;
        bool demands_attention; /**< If true, the client requires attention from the user. */
    } _state;

    class Actions {
    public:
        Actions(void) : move(true), resize(true), minimize(true),
                shade(true), stick(true), maximize_horz(true),
                maximize_vert(true), fullscreen(true),
        change_desktop(true), close(true) { }
        ~Actions(void) { }

        bool move;
        bool resize;
        bool minimize; // iconify
        bool shade;
        bool stick;
        bool maximize_horz;
        bool maximize_vert;
        bool fullscreen;
        bool change_desktop; // workspace
        bool close;
    } _actions;

    static std::list<Client*> _client_list; //!< List of all Clients.
    static std::vector<uint> _clientid_list; //!< List of free Client IDs.
};

#endif // _CLIENT_HH_
