#include "xwin.hpp"
#include <linux/limits.h>
#ifdef USE_X


XImg::XImg(Display* display, Visual* visual, unsigned w, unsigned h)
    : buf((Img::rgb_t*)calloc(w*h, sizeof(Img::rgb_t))), img(new Img(w, h, buf, NULL)) {
    gctx = DefaultGC(display, DefaultScreen(display)); // NOLINT(clang-analyzer-core.NullDereference)
    xim = XCreateImage(display, visual, 24, ZPixmap, 0, (char*)buf, w, h, sizeof(Img::rgb_t)*8, w*sizeof(Img::rgb_t));
}


XImg::~XImg() {
    if (xim) {
        XDestroyImage(xim); // also frees buf
    } else {
        free(buf);
    }
    delete img;
}


Img* XImg::get() {
    return img;
}


bool XImg::draw(Display* display, Window window) {
    XPutImage(display, window, gctx, xim, 0, 0, 0, 0, img->w, img->h);  // TODO: MIT-SHM for performace reasons
    return true;
}


XWin::XWin(unsigned w, unsigned h) {
    display = XOpenDisplay(NULL);
    if (!display) {
        return;
    }

    visual = DefaultVisual(display, 0);
    if (visual->c_class != TrueColor) {
        XCloseDisplay(display);
        display = NULL;
    }

    window = XCreateSimpleWindow(display, RootWindow(display, 0), 0, 0, w, h, 1, 0, 0); // NOLINT(clang-analyzer-core.NullDereference)
    if (!window) {
        XCloseDisplay(display);
        display = NULL;
    }

    if (!XSelectInput(display, window, KeyPressMask) || !XMapWindow(display, window)) {
        XDestroyWindow(display, window);
        XCloseDisplay(display);
        display = NULL;
    }

    ximg = new XImg(display, visual, w, h);
}


XWin::~XWin() {
    if (!display) return;
    delete ximg;
    XDestroyWindow(display, window);
    XCloseDisplay(display);
}


Img* XWin::get() {
    return display? ximg->get(): NULL;
}


bool XWin::draw() {
    return (display && ximg->draw(display, window));
}


XWin::key_t XWin::get_key() {
    XEvent ev;
    #ifndef KEY_REPEAT
        while (XPending(display)) XNextEvent(display, &ev);
    #endif
    while (true) {
        XNextEvent(display, &ev);
        if (ev.type != KeyPress) continue;
        KeySym keysym = XLookupKeysym(&ev.xkey, 0);
        if (keysym == NoSymbol) return KEY_QUIT;
        XKeyEvent* kev = (XKeyEvent*)&ev;

        switch (keysym) {
            case XK_Left:
                return (kev->state & Mod1Mask)? KEY_PAN_LEFT: KEY_LEFT;
            case XK_Up:
                return (kev->state & Mod1Mask)? KEY_PAN_DOWN: KEY_FORWARD;
            case XK_Right:
                return (kev->state & Mod1Mask)? KEY_PAN_RIGHT: KEY_RIGHT;
            case XK_Down:
                return (kev->state & Mod1Mask)? KEY_PAN_UP: KEY_BACK;
            case XK_Page_Up:
                return KEY_UP;
            case XK_Page_Down:
                return KEY_DOWN;
            case XK_Escape:
                return KEY_QUIT;
            default:
                break; // ignore
        }
    }
    return KEY_QUIT; // not reached
}


#endif


OutImg::OutImg(unsigned ww, unsigned hh, unsigned samp) {
    if (samp) {
        if (samp <= 1 || ww % samp != 0 || hh % samp != 0 || samp > ww || samp > hh) {
            LOG("supersampling value %u invalid, skipping.", samp);
            samp = 0;
        }
    }
    supersample = samp;

#ifdef USE_X
    if (samp) {
        xwin = new XWin(ww / samp, hh / samp);
        if (xwin->get()) {
            out = new Img(ww, hh);
            out_sampled = xwin->get();
            return;
        } else {
            delete xwin;
            xwin = NULL;
        }
    } else {
        xwin = new XWin(ww, hh);
        if (xwin->get()) {
            out = xwin->get();
            out_sampled = NULL;
            return;
        } else {
            delete xwin;
            xwin = NULL;
        }
    }
    LOG("not using X window");
#endif

    if (samp) {
        out = new Img(ww, hh);
        out_sampled = new Img(ww / samp, hh / samp);
    } else {
        out = new Img(ww, hh);
        out_sampled = NULL;
    }
}


bool OutImg::has_x() const {
#ifdef USE_X
    return xwin != NULL;
#else
    return false;
#endif
}


OutImg::~OutImg() {
#ifdef USE_X
    if (xwin) {
        delete xwin;
        if (supersample) delete out;
        return;
    }
#endif
    delete out;
    if (supersample) delete out_sampled;
}


bool OutImg::put(const char* outdir) {
    if (supersample) {
        if (!out->supersample(supersample, out_sampled)) { // TODO: detach or in thread?
            return false;
        }
    }

#ifdef USE_X
    if (xwin && !xwin->draw()) {
        return false;
    }
#endif

    if (outdir && *outdir) {
        static unsigned frame_no = 0;
        static char outname[PATH_MAX+1];
        snprintf(outname, sizeof(outname), "%s/%u.ppm", outdir, frame_no++);
        if (!(supersample? out_sampled->to_ppm(outname): out->to_ppm(outname))) { // TODO: detach? (blocking i/o)
            return false;
        }
    }

    return true;
}


bool OutImg::get_input(coord_t msens, coord_t lsens, vertex_t& move, vertex_t& look) {
    msens = MAX(msens, 1.0);
    lsens = MAX(lsens, 1.0);
    move = vertex_t(0, 0, 0);
    look = vertex_t(0, 0, 0);
#ifdef USE_X
    if (!xwin) 	return false;
    switch (xwin->get_key()) {
        case XWin::KEY_UP:
            move.y = -msens; return true;
        case XWin::KEY_DOWN:
            move.y = msens;  return true;
        case XWin::KEY_LEFT:
            move.x = -msens; return true;
        case XWin::KEY_RIGHT:
            move.x = msens;  return true;
        case XWin::KEY_FORWARD:
            move.z = msens;  return true;
        case XWin::KEY_BACK:
            move.z = -msens; return true;
        case XWin::KEY_PAN_UP:
            look.y = lsens;  return true;
        case XWin::KEY_PAN_DOWN:
            look.y = -lsens; return true;
        case XWin::KEY_PAN_LEFT:
            look.x = -lsens; return true;
        case XWin::KEY_PAN_RIGHT:
            look.x = lsens;  return true;
        case XWin::KEY_QUIT:
        default:
            return false;
    }
    return true;
#else
    return false;
#endif
}