
#include "Border.h"
#include "Client.h"
#include "Manager.h"
#include "Rotated.h"

// These are degenerate initialisations, don't change them
int Border::m_tabWidth = -1;
XRotFontStruct *Border::m_tabFont = 0;
GC Border::m_drawGC = 0;

unsigned long Border::m_foregroundPixel;
unsigned long Border::m_backgroundPixel;
unsigned long Border::m_frameBackgroundPixel;
unsigned long Border::m_buttonBackgroundPixel;
unsigned long Border::m_borderPixel;

static int borderCounter = 0;


declareList(RectangleList, XRectangle);
implementList(RectangleList, XRectangle);

class BorderRectangleList : public RectangleList
{
public:
    BorderRectangleList() { }
    ~BorderRectangleList() { }

    void appendRect(int x, int y, int w, int h);
};

void BorderRectangleList::appendRect(int x, int y, int w, int h)
{
    XRectangle r;
    r.x = x; r.y = y; r.width = w; r.height = h;
    append(r);
}


Border::Border(Client *const c, Window child) :
    m_client(c), m_parent(0), m_tab(0),
    m_child(child), m_button(0), m_resize(0), m_label(0),
    m_prevW(-1), m_prevH(-1), m_tabHeight(-1)
{
    m_parent = root();

    if (m_tabFont == 0) {

	if (!(m_tabFont = XRotLoadFont(display(), CONFIG_NICE_FONT, 90.0)) &&
	    !(m_tabFont = XRotLoadFont(display(), CONFIG_NASTY_FONT, 90.0))) {
	    windowManager()->fatal
		("couldn't load default rotated font, bailing out");
	}

	m_tabWidth = m_tabFont->height + 4;
	if (m_tabWidth < TAB_TOP_HEIGHT * 2 + 8) {
	    m_tabWidth = TAB_TOP_HEIGHT * 2 + 8;
	}

	m_foregroundPixel = windowManager()->allocateColour
	    (CONFIG_TAB_FOREGROUND, "tab foreground");
	m_backgroundPixel = windowManager()->allocateColour
	    (CONFIG_TAB_BACKGROUND, "tab background");
	m_frameBackgroundPixel = windowManager()->allocateColour
	    (CONFIG_FRAME_BACKGROUND, "frame background");
	m_buttonBackgroundPixel = windowManager()->allocateColour
	    (CONFIG_BUTTON_BACKGROUND, "button background");
	m_borderPixel = windowManager()->allocateColour
	    (CONFIG_BORDERS, "border");

	XGCValues values;
	values.foreground = m_foregroundPixel;
	values.background = m_backgroundPixel;
	values.function = GXcopy;
	values.line_width = 0;
	values.subwindow_mode = IncludeInferiors;

	m_drawGC = XCreateGC(display(), root(),
			     GCForeground | GCBackground | GCFunction |
			     GCLineWidth | GCSubwindowMode,
			     &values);

	if (!m_drawGC) {
	    windowManager()->fatal("couldn't allocate border GC");
	}
    }

    ++borderCounter;
}


Border::~Border()
{
    if (m_parent != root()) {
	if (!m_parent) fprintf(stderr,"wm2: zero parent in Border::~Border\n");
	else {
	    XDestroyWindow(display(), m_tab);
	    XDestroyWindow(display(), m_button);
	    XDestroyWindow(display(), m_parent);

	    // bad window if its parent has already gone:
	    XDestroyWindow(display(), m_resize);
	}
    }

    if (m_label) free(m_label);

    if (--borderCounter == 0) {
	XFreeGC(display(), m_drawGC);
    }
}


void Border::fatal(char *s)
{
    windowManager()->fatal(s);
}


Display *Border::display()
{
    return m_client->display();
}


WindowManager *Border::windowManager()
{
    return m_client->windowManager();
}


Window Border::root()
{
    return m_client->root();
}


void Border::expose(XExposeEvent *e)
{
    if (e->window != m_tab) return;
    drawLabel();
}


void Border::drawLabel()
{
    if (m_label) {
	XClearWindow(display(), m_tab);
	XRotDrawString(display(), m_tabFont, m_tab, m_drawGC,
		       2 + m_tabFont->max_ascent, m_tabHeight - 1,
		       m_label, strlen(m_label));
    }
}


Boolean Border::isTransient(void)
{
    return m_client->isTransient();
}


Boolean Border::isFixedSize(void)
{
    return m_client->isFixedSize();
}


void Border::fixTabHeight(int maxHeight)
{
    m_tabHeight = 0x7fff;
    maxHeight -= m_tabWidth;	// for diagonal

    if (m_label) free(m_label);
    m_label = NewString(m_client->label());
    
    if (m_label) {
	m_tabHeight =
	    XRotTextWidth(m_tabFont, m_label, strlen(m_label)) + 6 + m_tabWidth;
    }

    if (m_tabHeight <= maxHeight) return;

    if (m_label) free(m_label);
    m_label = m_client->iconName() ?
	NewString(m_client->iconName()) : NewString("incognito");

    int len = strlen(m_label);
    m_tabHeight = XRotTextWidth(m_tabFont, m_label, len) + 6 + m_tabWidth;
    if (m_tabHeight <= maxHeight) return;

    char *newLabel = (char *)malloc(len + 3);

    do {
	strncpy(newLabel, m_label, len - 1);
	strcpy(newLabel + len - 1, "...");
	m_tabHeight = XRotTextWidth(m_tabFont, newLabel,
				    strlen(newLabel)) + 6 + m_tabWidth;
	--len;

    } while (m_tabHeight > maxHeight && len > 2);

    free(m_label);
    m_label = newLabel;

    if (m_tabHeight > maxHeight) m_tabHeight = maxHeight;
}


void Border::shapeTransientParent(int w, int h)
{
    XRectangle r;

    r.x = xIndent() - 1; r.y = yIndent() - 1;
    r.width = w + 2; r.height = h + 2;

    XShapeCombineRectangles
	(display(), m_parent, ShapeBounding, 0, 0, &r, 1, ShapeSet, YXBanded);

    r.x = xIndent(); r.y = yIndent();
    r.width = w; r.height = h;

    XShapeCombineRectangles
	(display(), m_parent, ShapeClip, 0, 0, &r, 1, ShapeSet, YXBanded);
}


void Border::setTransientFrameVisibility(Boolean visible, int w, int h)
{
    int i;
    BorderRectangleList rl;

    rl.appendRect(0, 0, w + 1, yIndent() - 1);
    for (i = 1; i < yIndent(); ++i) {
	rl.appendRect(w + 1, i - 1, i + 1, 1);
    }
    rl.appendRect(0, yIndent() - 1, xIndent() - 1, h - yIndent() + 2);
    for (i = 1; i < yIndent(); ++i) {
	rl.appendRect(i - 1, h, 1, i + 2);
    }

    XShapeCombineRectangles
	(display(), m_parent, ShapeBounding,
	 0, 0, rl.array(0, rl.count()), rl.count(),
	 visible ? ShapeUnion : ShapeSubtract, YXSorted);

    rl.remove_all();

    rl.appendRect(1, 1, w, yIndent() - 2);
    for (i = 2; i < yIndent(); ++i) {
	rl.appendRect(w + 1, i - 1, i, 1);
    }
    rl.appendRect(1, yIndent() - 1, xIndent() - 2, h - yIndent() + 1);
    for (i = 2; i < yIndent(); ++i) {
	rl.appendRect(i - 1, h, 1, i + 1);
    }

    XShapeCombineRectangles
	(display(), m_parent, ShapeClip,
	 0, 0, rl.array(0, rl.count()), rl.count(),
	 visible ? ShapeUnion : ShapeSubtract, YXSorted);
}   


void Border::shapeParent(int w, int h)
{
    int i;
    int mainRect;
    BorderRectangleList rl;

    if (isTransient()) {
	shapeTransientParent(w, h);
	return;
    }

    // Bounding rectangles -- clipping will be the same except for
    // child window border

    // top of tab
    rl.appendRect(0, 0, w + m_tabWidth + 1, TAB_TOP_HEIGHT + 2);

    // struts in tab, left...
    rl.appendRect(0, TAB_TOP_HEIGHT + 1,
		  TAB_TOP_HEIGHT + 2, m_tabWidth - TAB_TOP_HEIGHT*2 - 1);

    // ...and right
    rl.appendRect(m_tabWidth - TAB_TOP_HEIGHT, TAB_TOP_HEIGHT + 1,
		  TAB_TOP_HEIGHT + 2, m_tabWidth - TAB_TOP_HEIGHT*2 - 1);

    mainRect = rl.count();
    rl.appendRect(xIndent() - 1, yIndent() - 1, w + 2, h + 2);

    // main tab
    rl.appendRect(0, m_tabWidth - TAB_TOP_HEIGHT, m_tabWidth + 2,
		  m_tabHeight - m_tabWidth + TAB_TOP_HEIGHT);

    // diagonal
    for (i = 1; i < m_tabWidth - 1; ++i) {
	rl.appendRect(i, m_tabHeight + i - 1, m_tabWidth - i + 2, 1);
    }

    XShapeCombineRectangles
	(display(), m_parent, ShapeBounding,
	 0, 0, rl.array(0, rl.count()), rl.count(), ShapeSet, YXSorted);

    rl.item(mainRect).x ++;
    rl.item(mainRect).y ++;
    rl.item(mainRect).width -= 2;
    rl.item(mainRect).height -= 2;

    XShapeCombineRectangles
	(display(), m_parent, ShapeClip,
	 0, 0, rl.array(0, rl.count()), rl.count(), ShapeSet, YXSorted);
}


void Border::shapeTab(int w, int)
{
    int i;
    BorderRectangleList rl;

    if (isTransient()) {
	return;
    }

    // Bounding rectangles

    rl.appendRect(0, 0, w + m_tabWidth + 1, TAB_TOP_HEIGHT + 2);
    rl.appendRect(0, TAB_TOP_HEIGHT + 1, TAB_TOP_HEIGHT + 2,
		  m_tabWidth - TAB_TOP_HEIGHT*2 - 1);

    rl.appendRect(m_tabWidth - TAB_TOP_HEIGHT, TAB_TOP_HEIGHT + 1,
		  TAB_TOP_HEIGHT + 2, m_tabWidth - TAB_TOP_HEIGHT*2 - 1);

    rl.appendRect(0, m_tabWidth - TAB_TOP_HEIGHT, m_tabWidth + 2,
		  m_tabHeight - m_tabWidth + TAB_TOP_HEIGHT);

    for (i = 1; i < m_tabWidth - 1; ++i) {
	rl.appendRect(i, m_tabHeight + i - 1, m_tabWidth - i + 2, 1);
    }

    XShapeCombineRectangles
	(display(), m_tab, ShapeBounding,
	 0, 0, rl.array(0, rl.count()), rl.count(), ShapeSet, YXSorted);

    rl.remove_all();

    // Clipping rectangles

    rl.appendRect(1, 1, w + m_tabWidth - 1, TAB_TOP_HEIGHT);

    rl.appendRect(1, TAB_TOP_HEIGHT + 1, TAB_TOP_HEIGHT,
		  m_tabWidth + TAB_TOP_HEIGHT*2 - 1);

    rl.appendRect(m_tabWidth - TAB_TOP_HEIGHT + 1, TAB_TOP_HEIGHT + 1,
		  TAB_TOP_HEIGHT, m_tabWidth + TAB_TOP_HEIGHT*2 - 1);

    rl.appendRect(1, m_tabWidth - TAB_TOP_HEIGHT + 1, m_tabWidth,
		  m_tabHeight - m_tabWidth + TAB_TOP_HEIGHT - 1);

    for (i = 1; i < m_tabWidth - 2; ++i) {
	rl.appendRect(i + 1, m_tabHeight + i - 1, m_tabWidth - i, 1);
    }

    XShapeCombineRectangles
	(display(), m_tab, ShapeClip,
	 0, 0, rl.array(0, rl.count()), rl.count(), ShapeSet, YXSorted);
}


void Border::resizeTab(int h)
{
    int i;
    XRectangle r;
    BorderRectangleList rl;
    int shorter, longer, operation;

    if (isTransient()) {
	return;
    }

    int prevTabHeight = m_tabHeight;
    fixTabHeight(h);
    if (m_tabHeight == prevTabHeight) return;

    XWindowChanges wc;
    wc.height = m_tabHeight + 2 + m_tabWidth;
    XConfigureWindow(display(), m_tab, CWHeight, &wc);

    if (m_tabHeight > prevTabHeight) {

	shorter = prevTabHeight;
	longer = m_tabHeight;
	operation = ShapeUnion;

    } else {

	shorter = m_tabHeight;
	longer = prevTabHeight + m_tabWidth;
	operation = ShapeSubtract;
    }

    r.x = 0; r.y = shorter /*- 2*/;
    r.width = m_tabWidth + 2; r.height = longer - shorter;

    XShapeCombineRectangles(display(), m_parent, ShapeBounding,
			    0, 0, &r, 1, operation, YXBanded);

    XShapeCombineRectangles(display(), m_parent, ShapeClip,
			    0, 0, &r, 1, operation, YXBanded);

    XShapeCombineRectangles(display(), m_tab, ShapeBounding,
			    0, 0, &r, 1, operation, YXBanded);

    r.x ++; r.width -= 2;

    XShapeCombineRectangles(display(), m_tab, ShapeClip,
			    0, 0, &r, 1, operation, YXBanded);

    if (m_client->isActive()) {
	// restore a bit of the frame edge
	r.x = m_tabWidth + 1; r.y = shorter;
	r.width = FRAME_WIDTH - 1; r.height = longer - shorter;
	XShapeCombineRectangles(display(), m_parent, ShapeBounding,
				0, 0, &r, 1, ShapeUnion, YXBanded);
    }

    for (i = 1; i < m_tabWidth - 1; ++i) {
	rl.appendRect(i, m_tabHeight + i - 1, m_tabWidth - i + 2, 1);
    }
	
    XShapeCombineRectangles
	(display(), m_parent, ShapeBounding,
	 0, 0, rl.array(0, rl.count()), rl.count(), ShapeUnion, YXBanded);

    XShapeCombineRectangles
	(display(), m_parent, ShapeClip,
	 0, 0, rl.array(0, rl.count()), rl.count(), ShapeUnion, YXBanded);

    XShapeCombineRectangles
	(display(), m_tab, ShapeBounding,
	 0, 0, rl.array(0, rl.count()), rl.count(), ShapeUnion, YXBanded);

    if (rl.count() < 2) return;

    for (i = 0; i < rl.count() - 1; ++i) {
	rl.item(i).x ++; rl.item(i).width -= 2;
    }

    XShapeCombineRectangles
	(display(), m_tab, ShapeClip,
	 0, 0, rl.array(0, rl.count() - 1), rl.count() - 1,
	 ShapeUnion, YXBanded);
}


void Border::shapeResize()
{
    int i;
    BorderRectangleList rl;

    for (i = 0; i < FRAME_WIDTH*2; ++i) {
	rl.appendRect(FRAME_WIDTH*2 - i - 1, i, i + 1, 1);
    }

    XShapeCombineRectangles
	(display(), m_resize, ShapeBounding, 0, 0,
	 rl.array(0, rl.count()), rl.count(), ShapeSet, YXBanded);

    rl.remove_all();

    for (i = 1; i < FRAME_WIDTH*2; ++i) {
	rl.appendRect(FRAME_WIDTH*2 - i, i, i, 1);
    }

    XShapeCombineRectangles
	(display(), m_resize, ShapeClip, 0, 0,
	 rl.array(0, rl.count()), rl.count(), ShapeSet, YXBanded);

    rl.remove_all();

    for (i = 0; i < FRAME_WIDTH*2 - 3; ++i) {
	rl.appendRect(FRAME_WIDTH*2 - i - 1, i + 3, 1, 1);
    }

    XShapeCombineRectangles
	(display(), m_resize, ShapeClip, 0, 0,
	 rl.array(0, rl.count()), rl.count(), ShapeSubtract, YXBanded);

    windowManager()->installCursorOnWindow
	(WindowManager::DownrightCursor, m_resize);
}


void Border::setFrameVisibility(Boolean visible, int w, int h)
{
    BorderRectangleList rl;

    if (CONFIG_PROD_SHAPE) {
	shapeParent(w, h);
	shapeTab(w, h);
    }

    if (isTransient()) {
	setTransientFrameVisibility(visible, w, h);
	return;
    }

    // Bounding rectangles

    rl.appendRect(m_tabWidth + w + 1, 0, FRAME_WIDTH + 1, FRAME_WIDTH);

    rl.appendRect(m_tabWidth + 2, TAB_TOP_HEIGHT + 2, w,
		  FRAME_WIDTH - TAB_TOP_HEIGHT - 2);

    // for button
    int ww = m_tabWidth - TAB_TOP_HEIGHT*2 - 4;
    rl.appendRect((m_tabWidth + 2 - ww) / 2, (m_tabWidth+2 - ww) / 2, ww, ww);

    rl.appendRect(m_tabWidth + 2, FRAME_WIDTH,
		  FRAME_WIDTH - 2, m_tabHeight + m_tabWidth - FRAME_WIDTH - 2);

    // swap last two if sorted wrong
    if (rl.item(rl.count()-2).y > rl.item(rl.count()-1).y) {
	rl.append(rl.item(rl.count()-2));
	rl.remove(rl.count()-3);
    }

    int final = rl.count();
    rl.append(rl.item(final-1));
    rl.item(final).x -= 1;
    rl.item(final).y += rl.item(final).height;
    rl.item(final).width += 1;
    rl.item(final).height = h - rl.item(final).height + 2;

    XShapeCombineRectangles(display(), m_parent, ShapeBounding,
			    0, 0, rl.array(0, rl.count()), rl.count(),
			    visible ? ShapeUnion : ShapeSubtract, YXSorted);
    rl.remove_all();

    // Clip rectangles

    rl.appendRect(m_tabWidth + w + 1, 1, FRAME_WIDTH, FRAME_WIDTH - 1);

    rl.appendRect(m_tabWidth + 2, TAB_TOP_HEIGHT + 2, w,
		  FRAME_WIDTH - TAB_TOP_HEIGHT - 2);

    // for button
    ww = m_tabWidth - TAB_TOP_HEIGHT*2 - 6;
    rl.appendRect((m_tabWidth + 2 - ww) / 2, (m_tabWidth+2 - ww) / 2, ww, ww);

    rl.appendRect(m_tabWidth + 2, FRAME_WIDTH,
		  FRAME_WIDTH - 2, h - FRAME_WIDTH);

    // swap last two if sorted wrong
    if (rl.item(rl.count()-2).y > rl.item(rl.count()-1).y) {
	rl.append(rl.item(rl.count()-2));
	rl.remove(rl.count()-3);
    }

    rl.appendRect(m_tabWidth + 2, h, FRAME_WIDTH - 2, FRAME_WIDTH + 1);

    XShapeCombineRectangles(display(), m_parent, ShapeClip,
			    0, 0, rl.array(0, rl.count()), rl.count(),
			    visible ? ShapeUnion : ShapeSubtract, YXSorted);
    rl.remove_all();

    if (visible && !isFixedSize()) {
	XMapRaised(display(), m_resize);
    } else {
	XUnmapWindow(display(), m_resize);
    }
}


void Border::configure(int x, int y, int w, int h,
		       unsigned long mask, int detail,
		       Boolean force) // must reshape everything
{
    if (!m_parent || m_parent == root()) {

	// create windows, then shape them afterwards

	m_parent = XCreateSimpleWindow
	    (display(), root(), 1, 1, 1, 1, 0,
	     m_borderPixel, m_frameBackgroundPixel);

	m_tab = XCreateSimpleWindow
	    (display(), m_parent, 1, 1, 1, 1, 0,
	     m_borderPixel, m_backgroundPixel);

	m_button = XCreateSimpleWindow
	    (display(), m_parent, 1, 1, 1, 1, 0,
	     m_borderPixel, m_buttonBackgroundPixel);

	m_resize = XCreateWindow
	    (display(), m_child, 1, 1, FRAME_WIDTH*2, FRAME_WIDTH*2, 0,
	     CopyFromParent, InputOutput, CopyFromParent, 0L, 0);

	shapeResize();

	XSelectInput(display(), m_parent,
		     SubstructureRedirectMask | SubstructureNotifyMask |
		     ButtonPressMask | ButtonReleaseMask);

	if (!isTransient()) {
	    XSelectInput(display(), m_tab,
			 ExposureMask | ButtonPressMask | ButtonReleaseMask |
			 EnterWindowMask/* | LeaveWindowMask*/);
	}

	XSelectInput(display(), m_button,
		     ButtonPressMask | ButtonReleaseMask/* | LeaveWindowMask*/);
	XSelectInput(display(), m_resize, ButtonPressMask | ButtonReleaseMask);
	mask |= CWX | CWY | CWWidth | CWHeight | CWBorderWidth;
    }

    XWindowChanges wc;
    wc.x = x - xIndent();
    wc.y = y - yIndent();
    wc.width  = w + xIndent() + 1;
    wc.height = h + yIndent() + 1;
    wc.border_width = 0;
    wc.sibling = None;
    wc.stack_mode = detail;
    XConfigureWindow(display(), m_parent, mask, &wc);

    unsigned long rmask = 0L;
    if (mask & CWWidth)  rmask |= CWX;
    if (mask & CWHeight) rmask |= CWY;
    wc.x = w - FRAME_WIDTH*2;
    wc.y = h - FRAME_WIDTH*2;
    XConfigureWindow(display(), m_resize, rmask, &wc);
    
    if (force ||
	(m_prevW < 0 || m_prevH < 0) ||
	((mask & (CWWidth | CWHeight)) && (w != m_prevW || h != m_prevH))) {

	int prevTabHeight = m_tabHeight;
	if (isTransient()) m_tabHeight = 10; // arbitrary
	else fixTabHeight(h);

	shapeParent(w, h);
	setFrameVisibility(m_client->isActive(), w, h);

	if (force || w != m_prevW ||
	    prevTabHeight != m_tabHeight || m_prevW < 0 || m_prevH < 0) {

	    wc.x = 0;
	    wc.y = 0;
	    wc.width = w + xIndent();
	    wc.height = m_tabHeight + 2 + m_tabWidth;
	    XConfigureWindow(display(), m_tab, mask, &wc);
	    shapeTab(w, h);
	}

	m_prevW = w;
	m_prevH = h;

    } else {

	resizeTab(h);
    }

    wc.x = TAB_TOP_HEIGHT + 2;
    wc.y = wc.x;
    wc.width = wc.height = m_tabWidth - TAB_TOP_HEIGHT*2 - 4;
    XConfigureWindow(display(), m_button, mask, &wc);
}


void Border::moveTo(int x, int y)
{
    XWindowChanges wc;
    wc.x = x - xIndent();
    wc.y = y - yIndent();
    XConfigureWindow(display(), m_parent, CWX | CWY, &wc);
}


void Border::map()
{
    if (m_parent == root()) {
	fprintf(stderr, "wm2: bad parent in Border::map()\n");
    } else {
	XMapWindow(display(), m_parent);

	if (!isTransient()) {
	    XMapWindow(display(), m_tab);
	    XMapWindow(display(), m_button);
	    if (!isFixedSize()) XMapWindow(display(), m_resize);
	}
    }
}


void Border::mapRaised()
{
    if (m_parent == root()) {
	fprintf(stderr, "wm2: bad parent in Border::mapRaised()\n");
    } else {
	XMapRaised(display(), m_parent);

	if (!isTransient()) {
	    XMapWindow(display(), m_tab);
	    XMapRaised(display(), m_button);
	    if (!isFixedSize()) XMapRaised(display(), m_resize);
	}
    }
}


void Border::lower()
{
    XLowerWindow(display(), m_parent);
}


void Border::unmap()
{
    if (m_parent == root()) {
	fprintf(stderr, "wm2: bad parent in Border::unmap()\n");
    } else {
	XUnmapWindow(display(), m_parent);

	if (!isTransient()) {
	    XUnmapWindow(display(), m_tab);
	    XUnmapWindow(display(), m_button);
//	    XUnmapWindow(display(), m_resize); // no, will unmap with parent
	}
    }
}


void Border::decorate(Boolean active, int w, int h)
{
    setFrameVisibility(active, w, h);
}


void Border::reparent()
{
    XReparentWindow(display(), m_child, m_parent, xIndent(), yIndent());
}

