//
// PScreen.cc for pekwm
// Copyright © 2003-2009 Claes Nästén <me@pekdon.net>
//
// This program is licensed under the GNU GPL.
// See the LICENSE file for more information.
//

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

#include <string>
#include <iostream>
#include <cassert>
#include <cstring> // required for memset in FD_ZERO

#ifdef HAVE_LIMITS
#include <limits>
using std::numeric_limits;
#endif // HAVE_LIMITS

extern "C" {
#include <X11/Xlib.h>
#ifdef HAVE_SHAPE
#include <X11/Xutil.h>
#include <X11/extensions/shape.h>
#endif // HAVE_SHAPE
#ifdef HAVE_XRANDR
#include <X11/extensions/Xrandr.h>
#endif // HAVE_XRANDR
#include <X11/keysym.h> // For XK_ entries
#include <sys/select.h>

#ifdef DEBUG
bool xerrors_ignore = false;
#endif // DEBUG

unsigned int xerrors_count = 0;
}

#include "PScreen.hh"
// FIXME: Remove when strut handling is moved away from here.
#include "PWinObj.hh"
#include "ManagerWindows.hh"

using std::cerr;
using std::endl;
using std::vector;
using std::list;
using std::map;
using std::string;
using std::memset; // required for FD_ZERO

const uint PScreen::MODIFIER_TO_MASK[] = {
    ShiftMask, LockMask, ControlMask,
    Mod1Mask, Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask
};
const uint PScreen::MODIFIER_TO_MASK_NUM = sizeof(PScreen::MODIFIER_TO_MASK) / sizeof(PScreen::MODIFIER_TO_MASK[0]);

PScreen* PScreen::_instance = 0;


extern "C" {
    /**
      * XError handler, prints error.
      */
    static int
    handleXError(Display *dpy, XErrorEvent *ev)
    {
        ++xerrors_count;

#ifdef DEBUG
        if (xerrors_ignore) {
            return 0;
        }

        char error_buf[256];
        XGetErrorText(dpy, ev->error_code, error_buf, 256);
        cerr << "XError: " << error_buf << " id: " << ev->resourceid << endl;
#endif // DEBUG

        return 0;
    }
}

//! @brief PScreen::Visual constructor.
//! @param x_visual X Visual to wrap.
PScreen::PVisual::PVisual(Visual *x_visual) : _x_visual(x_visual),
        _r_shift(0), _r_prec(0),
        _g_shift(0), _g_prec(0),
        _b_shift(0), _b_prec(0)
{
    getShiftPrecFromMask(_x_visual->red_mask, _r_shift, _r_prec);
    getShiftPrecFromMask(_x_visual->green_mask, _g_shift, _g_prec);
    getShiftPrecFromMask(_x_visual->blue_mask, _b_shift, _b_prec);
}

//! @brief PScreen::Visual destructor.
PScreen::PVisual::~PVisual(void)
{
}

//! @brief Gets shift and prec from mask.
//! @param mask red,green,blue mask of Visual.
//! @param shift Set to the shift of mask.
//! @param prec Set to the prec of mask.
void
PScreen::PVisual::getShiftPrecFromMask(ulong mask, int &shift, int &prec)
{
    for (shift = 0; ! (mask&0x1); ++shift) {
        mask >>= 1;
    }
    
    for (prec = 0; (mask&0x1); ++prec) {
        mask >>= 1;
    }
}

//! @brief PScreen constructor
PScreen::PScreen(Display *dpy, bool honour_randr)
    : _honour_randr(honour_randr), _fd(-1),
      _screen(-1), _depth(-1),
      _root(None), _visual(0), _colormap(None),
      _modifier_map(0),
      _has_extension_shape(false), _event_shape(-1),
      _has_extension_xinerama(false),
      _has_extension_xrandr(false), _event_xrandr(-1),
      _server_grabs(0), _last_event_time(0), _last_click_id(None)
{
    if (_instance) {
        throw string("PScreen, trying to create multiple instances");
    }
    _instance = this;

    XSetErrorHandler(handleXError);

    _dpy = dpy;

    XGrabServer(_dpy);

    _fd = ConnectionNumber(dpy);
    _screen = DefaultScreen(_dpy);
    _root = RootWindow(_dpy, _screen);

    _depth = DefaultDepth(_dpy, _screen);
    _visual = new PScreen::PVisual(DefaultVisual(_dpy, _screen));
    _colormap = DefaultColormap(_dpy, _screen);
    _modifier_map = XGetModifierMapping(_dpy);

    _screen_gm.width = WidthOfScreen(ScreenOfDisplay(_dpy, _screen));
    _screen_gm.height = HeightOfScreen(ScreenOfDisplay(_dpy, _screen));

#ifdef HAVE_SHAPE
    {
        int dummy_error;
        _has_extension_shape = XShapeQueryExtension(_dpy, &_event_shape, &dummy_error);
    }
#endif // HAVE_SHAPE

#ifdef HAVE_XRANDR
    {
        int dummy_error;
        _has_extension_xrandr = XRRQueryExtension(_dpy, &_event_xrandr, &dummy_error);
    }
#endif // HAVE_XRANDR

    // Now screen geometry has been read and extensions have been
    // looked for, read head information.
    initHeads();

    // initialize array values
    for (uint i = 0; i < (BUTTON_NO - 1); ++i) {
        _last_click_time[i] = 0;
    }

    // Figure out what keys the Num and Scroll Locks are
    setLockKeys();

    XSync(_dpy, false);
    XUngrabServer(_dpy);
}

//! @brief PScreen destructor
PScreen::~PScreen(void) {
    delete _visual;

    if (_modifier_map) {
        XFreeModifiermap(_modifier_map);
    }

    _instance = 0;
}

/**
 * Figure out what keys the Num and Scroll Locks are
 */
void
PScreen::setLockKeys(void)
{
    _num_lock = getMaskFromKeycode(XKeysymToKeycode(_dpy, XK_Num_Lock));
    _scroll_lock = getMaskFromKeycode(XKeysymToKeycode(_dpy, XK_Scroll_Lock));
}

//! @brief Get next event using select to avoid signal blocking
//! @param ev Event to fill in.
//! @return true if event was fetched, else false.
bool
PScreen::getNextEvent(XEvent &ev)
{
    if (XPending(_dpy) > 0) {
        XNextEvent(_dpy, &ev);
        return true;
    }

    int ret;
    fd_set rfds;

    XFlush(_dpy);

    FD_ZERO(&rfds);
    FD_SET(_fd, &rfds);

    ret = select(_fd + 1, &rfds, 0, 0, 0);
    if (ret > 0) {
        XNextEvent(_dpy, &ev);
    }

    return ret > 0;
}

//! @brief Grabs the server, counting number of grabs
bool
PScreen::grabServer(void)
{
    if (_server_grabs == 0) {
        XGrabServer(_dpy);
    }

    ++_server_grabs;
    return (_server_grabs == 1); // was actually grabbed
}

//! @brief Ungrabs the server, counting number of grabs
bool
PScreen::ungrabServer(bool sync)
{
    if (_server_grabs > 0) {
        --_server_grabs;

        if (_server_grabs == 0) { // no more grabs left
            if (sync) {
                XSync(_dpy, false);
            }
            XUngrabServer(_dpy);
        }
    }
    return (_server_grabs == 0); // is actually ungrabbed
}

//! @brief Grabs the keyboard
bool
PScreen::grabKeyboard(Window win)
{
    if (XGrabKeyboard(_dpy, win, false, GrabModeAsync, GrabModeAsync,
                      CurrentTime) == GrabSuccess) {
        return true;
    }
#ifdef DEBUG
    cerr << __FILE__ << "@" << __LINE__ << ": "
         << "PScreen(" << this << ")::grabKeyboard(" << win << ")" << endl
         << " *** unable to grab keyboard." << endl;
#endif // DEBUG
    return false;
}

//! @brief Ungrabs the keyboard
bool
PScreen::ungrabKeyboard(void)
{
    XUngrabKeyboard(_dpy, CurrentTime);
    return true;
}

//! @brief Grabs the pointer
bool
PScreen::grabPointer(Window win, uint event_mask, Cursor cursor)
{
    if (XGrabPointer(_dpy, win, false, event_mask, GrabModeAsync, GrabModeAsync,
                     None, cursor, CurrentTime) == GrabSuccess) {
        return true;
    }
#ifdef DEBUG
    cerr << __FILE__ << "@" << __LINE__ << ": "
         << "PScreen(" << this << ")::grabPointer(" << win << ","
         << event_mask << "," << cursor << ")" << endl
         << " *** unable to grab pointer." << endl;
#endif // DEBUG
    return false;
}

//! @brief Ungrabs the pointer
bool
PScreen::ungrabPointer(void)
{
    XUngrabPointer(_dpy, CurrentTime);
    return true;
}

//! @brief Refetches the root-window size.
void
PScreen::updateGeometry(uint width, uint height)
{
#ifdef HAVE_XRANDR
  if (! _honour_randr || ! _has_extension_xrandr) {
    return;
  }

  // The screen has changed geometry in some way. To handle this the
  // head information is read once again, the root window is re sized
  // and strut information is updated.
  initHeads();

  _screen_gm.width = width;
  _screen_gm.height = height;
  PWinObj::getRootPWinObj()->resize(width, height);

  updateStrut();
#endif // HAVE_XRANDR
}

//! @brief Searches for the head closest to the coordinates x,y.
//! @return The nearest head.  Head numbers are indexed from 0.
uint
PScreen::getNearestHead(int x, int y)
{
  if(_heads.size() > 1) {
        // set distance to the highest uint value
#ifdef HAVE_LIMITS
        uint min_distance = numeric_limits<uint>::max();
#else //! HAVE_LIMITS
        uint min_distance = ~0;
#endif // HAVE_LIMITS
        uint nearest_head = 0;

        uint distance;
        int head_t, head_b, head_l, head_r;
        for(uint head = 0; head < _heads.size(); ++head) {
            head_t = _heads[head].y;
            head_b = _heads[head].y + _heads[head].height;
            head_l = _heads[head].x;
            head_r = _heads[head].x + _heads[head].width;

            if(x > head_r) {
                if(y < head_t) {
                    // above and right of the head
                    distance = calcDistance(x, y, head_r, head_t);
                } else if(y > head_b) {
                    // below and right of the head
                    distance = calcDistance(x, y, head_r, head_b);
                } else {
                    // right of the head
                    distance = calcDistance(x, head_r);
                }
            } else if(x < head_l) {
                if(y < head_t) {
                    // above and left of the head
                    distance = calcDistance(x, y, head_l, head_t);
                } else if(y > head_b) {
                    // below and left of the head
                    distance = calcDistance(x, y, head_l, head_b);
                } else {
                    // left of the head
                    distance = calcDistance(x, head_l);
                }
            } else {
                if(y < head_t) {
                    // above the head
                    distance = calcDistance(y, head_t);
                } else if(y > head_b) {
                    // below the head
                    distance = calcDistance(y, head_b);
                } else {
                    // on the head
                    return head;
                }
            }

#ifdef DEBUG
            cerr << __FILE__ << "@" << __LINE__ << ": PScreen::getNearestHead( " << x << "," << y << ") "
                 << "head boundaries " << head_t << "," << head_b << "," << head_l << "," << head_r << " "
                 << "distance " << distance << " min_distance " << min_distance << endl;
#endif // DEBUG

            if(distance < min_distance) {
                min_distance = distance;
                nearest_head = head;
            }
        }
        return nearest_head;
    } else {
        return 0;
    }
}

//! @brief Searches for the head that the pointer currently is on.
//! @return Active head number
uint
PScreen::getCurrHead(void)
{
    uint head = 0;

    if (_heads.size() > 1) {
        int x = 0, y = 0;
        getMousePosition(x, y);
        head = getNearestHead(x, y);
#ifdef DEBUG
        cerr << __FILE__ << "@" << __LINE__ << ": PScreen::getCurrHead() got head "
             << head << " from mouse position " << x << "," << y << endl;
#endif // DEBUG
    }

    return head;
}

//! @brief Fills head_info with info about head nr head
//! @param head Head number to examine
//! @param head_info Returning info about the head
//! @return true if xinerama is off or head exists.
bool
PScreen::getHeadInfo(uint head, Geometry &head_info)
{
    if (head  < _heads.size()) {
        head_info.x = _heads[head].x;
        head_info.y = _heads[head].y;
        head_info.width = _heads[head].width;
        head_info.height = _heads[head].height;
        return true;
    } else {
#ifdef DEBUG
        cerr << __FILE__ << "@" << __LINE__ << ": Head: " << head << " doesn't exist!" << endl;
#endif // DEBUG
        return false;
    }
}

/**
 * Same as getHeadInfo but returns Geometry instead of filling it in.
 */
Geometry
PScreen::getHeadGeometry(uint head)
{
  Geometry gm(_screen_gm);
  getHeadInfo(head, gm);
  return gm;
}

//! @brief Fill information about head and the strut.
void
PScreen::getHeadInfoWithEdge(uint num, Geometry &head)
{
    if (! getHeadInfo(num, head)) {
        return;
    }

    int strut_val;
    Strut strut(_heads[num].strut); // Convenience

    // Remove the strut area from the head info
    strut_val = (head.x == 0) ? std::max(_strut.left, strut.left) : strut.left;
    head.x += strut_val;
    head.width -= strut_val;  

    strut_val = ((head.x + head.width) == _screen_gm.width) ? std::max(_strut.right, strut.right) : strut.right;
    head.width -= strut_val;

    strut_val = (head.y == 0) ? std::max(_strut.top, strut.top) : strut.top;
    head.y += strut_val;
    head.height -= strut_val;

    strut_val = (head.y + head.height == _screen_gm.height) ? std::max(_strut.bottom, strut.bottom) : strut.bottom;
    head.height -= strut_val;
}

void
PScreen::getMousePosition(int &x, int &y)
{
    Window d_root, d_win;
    int win_x, win_y;
    uint mask;

    XQueryPointer(_dpy, _root, &d_root, &d_win, &x, &y, &win_x, &win_y, &mask);
}

uint
PScreen::getButtonFromState(uint state)
{
    uint button = 0;

    if (state&Button1Mask)
        button = BUTTON1;
    else if (state&Button2Mask)
        button = BUTTON2;
    else if (state&Button3Mask)
        button = BUTTON3;
    else if (state&Button4Mask)
        button = BUTTON4;
    else if (state&Button5Mask)
        button = BUTTON5;

    return button;
}

//! @brief Adds a strut to the strut list, updating max strut sizes
void
PScreen::addStrut(Strut *strut)
{
    assert(strut);
    _strut_list.push_back(strut);

    updateStrut();
}

//! @brief Removes a strut from the strut list
void
PScreen::removeStrut(Strut *strut)
{
    assert(strut);
    _strut_list.remove(strut);

    updateStrut();
}

//! @brief Updates strut max size.
void
PScreen::updateStrut(void)
{
    // Reset strut data.
    _strut.left = 0;
    _strut.right = 0;
    _strut.top = 0;
    _strut.bottom = 0;

    for (vector<Head>::iterator it(_heads.begin()); it != _heads.end(); ++it) {
      it->strut.left = 0;
      it->strut.right = 0;
      it->strut.top = 0;
      it->strut.bottom = 0;
    }

    Strut *strut;
    for(list<Strut*>::iterator it(_strut_list.begin()); it != _strut_list.end(); ++it) {
        if ((*it)->head < 0) {
            strut = &_strut;
        } else if (static_cast<uint>((*it)->head) < _heads.size()) {
            strut = &(_heads[(*it)->head].strut);
        } else {
            continue;
        }

        if (strut->left < (*it)->left) {
            strut->left = (*it)->left;
        }
        if (strut->right < (*it)->right) {
            strut->right = (*it)->right;
        }
        if (strut->top < (*it)->top) {
            strut->top = (*it)->top;
        }
        if (strut->bottom < (*it)->bottom) {
          strut->bottom = (*it)->bottom;
        }
    }

    // Update hints on the root window
    Geometry workarea(_strut.left, _strut.top,
                      _screen_gm.width - _strut.left - _strut.right, _screen_gm.height - _strut.top - _strut.bottom);

    static_cast<RootWO*>(PWinObj::getRootPWinObj())->setEwmhWorkarea(workarea);
}

//! @brief Initialize head information
void
PScreen::initHeads(void)
{
    _heads.clear();

    // Read head information, randr has priority over xinerama then
    // comes ordinary X11 information.

    initHeadsRandr();
    if (! _heads.size()) {
        initHeadsXinerama();

        if (! _heads.size()) {
            _heads.push_back(Head(0, 0, _screen_gm.width, _screen_gm.height));
        }
    }
}

//! @brief Initialize head information from Xinerama
void
PScreen::initHeadsXinerama(void)
{
#ifdef HAVE_XINERAMA
    // Check if there are heads already initialized from example Randr
    if (! XineramaIsActive(_dpy)) {
        return;
    }

    int num_heads = 0;
    XineramaScreenInfo *infos = XineramaQueryScreens(_dpy, &num_heads);

    for (int i = 0; i < num_heads; ++i) {
        _heads.push_back(Head(infos[i].x_org, infos[i].y_org, infos[i].width, infos[i].height));
    }

    XFree(infos);
#endif // HAVE_XINERAMA
}

//! @brief Initialize head information from RandR
void
PScreen::initHeadsRandr(void)
{
#ifdef HAVE_XRANDR
    if (! _honour_randr || ! _has_extension_xrandr) {
        return;
    }

    XRRScreenResources *resources = XRRGetScreenResources(_dpy, _root);
    if (! resources) {
        return;
    }

    for (int i = 0; i < resources->noutput; ++i) {
        XRROutputInfo *output = XRRGetOutputInfo(_dpy, resources, resources->outputs[i]);

        if (output->crtc) {
            XRRCrtcInfo *crtc = XRRGetCrtcInfo(_dpy, resources, output->crtc);

            _heads.push_back(Head(crtc->x, crtc->y, crtc->width, crtc->height));
#ifdef DEBUG
            cerr << __FILE__ << "@" << __LINE__ << ": PScreen::initHeadsRandr() added head "
                << crtc->x << "," << crtc->y << "," << crtc->width << "," << crtc->height << endl;
#endif // DEBUG

            XRRFreeCrtcInfo (crtc);
        }

        XRRFreeOutputInfo (output);
    }

    XRRFreeScreenResources (resources);
#endif // HAVE_XRANDR
}

/**
 * Lookup mask from keycode.
 *
 * @param keycode KeyCode to lookup.
 * @return Mask for keycode, 0 if something fails.
 */
uint
PScreen::getMaskFromKeycode(KeyCode keycode)
{
    // Make sure modifier mappings were looked up ok
    if (! _modifier_map || _modifier_map->max_keypermod < 1) {
        return 0;
    }

    // .h files states that modifiermap is an 8 * max_keypermod array.
    int max_info = _modifier_map->max_keypermod * 8;
    for (int i = 0; i < max_info; ++i) {
        if (_modifier_map->modifiermap[i] == keycode) {
            return MODIFIER_TO_MASK[i / _modifier_map->max_keypermod];
        }
    }

    return 0;
}

/**
 * Figure out what key you can press to generate mask
 *
 * @param mask Modifier mask to get keycode for.
 * @return KeyCode for mask, 0 if failing.
 */
KeyCode
PScreen::getKeycodeFromMask(uint mask)
{
    // Make sure modifier mappings were looked up ok
    if (! _modifier_map || _modifier_map->max_keypermod < 1) {
        return 0;
    }

    for (int i = 0; i < 8; ++i) {
        if (MODIFIER_TO_MASK[i] == mask) {
            // FIXME: Is iteration over the range required?
            return _modifier_map->modifiermap[i * _modifier_map->max_keypermod];
        }
    }

    return 0;
}

Display *PScreen::_dpy;
uint PScreen::_num_lock = 0;
uint PScreen::_scroll_lock = 0;