/*
    PPGNUstepGlue_WindowStyleOffsets.m

    Copyright 2014-2018 Josh Freeman
    http://www.twilightedge.com

    This file is part of PikoPixel for GNUstep.
    PikoPixel is a graphical application for drawing & editing pixel-art images.

    PikoPixel is free software: you can redistribute it and/or modify it under
    the terms of the GNU Affero General Public License as published by the
    Free Software Foundation, either version 3 of the License, or (at your
    option) any later version approved for PikoPixel by its copyright holder (or
    an authorized proxy).

    PikoPixel is distributed in the hope that it will be useful, but WITHOUT ANY
    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
    details.

    You should have received a copy of the GNU Affero General Public License
    along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

// - Workarounds for window style offsets issues on Compiz & KWin WMs (cropped window content),
// and Openbox & Window Maker WMs (drawing artifacts after maximizing or switching
// resizeable/nonresizable)
//
// - Force GNUstep to ignore any cached style offset values stored in the root window, instead
// check offsets manually (in case the window manager changed since the values were cached)

#ifdef GNUSTEP

#import <Cocoa/Cocoa.h>
#import "NSObject_PPUtilities.h"
#import "PPAppBootUtilities.h"
#import "PPGNUstepGlueUtilities.h"

// Compiz defines
#define kUserDefaultsKey_FallbackWindowStyleOffsets_Compiz  \
                    @"GSGlue_FallbackWindowStyleOffsets_Compiz"

#define kDefaultStyleOffsetValue_Compiz_Top                 28
#define kDefaultStyleOffsetValue_Compiz_SidesAndBottom      0

// KWin defines
#define kUserDefaultsKey_FallbackWindowStyleOffsets_KWin    \
                    @"GSGlue_FallbackWindowStyleOffsets_KWin"

#define kDefaultStyleOffsetValue_KWin_Top                   29
#define kDefaultStyleOffsetValue_KWin_SidesAndBottom        4


typedef unsigned long int PPXWindow; // local definition of libX11's Window type


static NSString *gUserDefaultsKey_FallbackWindowStyleOffsets =
                                        kUserDefaultsKey_FallbackWindowStyleOffsets_Compiz;

static float gFallbackStyleOffset_Top = kDefaultStyleOffsetValue_Compiz_Top,
                gFallbackStyleOffset_Bottom = kDefaultStyleOffsetValue_Compiz_SidesAndBottom,
                gFallbackStyleOffset_Left = kDefaultStyleOffsetValue_Compiz_SidesAndBottom,
                gFallbackStyleOffset_Right = kDefaultStyleOffsetValue_Compiz_SidesAndBottom;


@interface NSUserDefaults (PPGNUstepGlue_WindowStyleOffsetsUtilities)

- (void) ppGSGlue_IgnoreRootWindowStyleOffsets;

+ (void) ppGSGlue_SetupFallbackStyleOffsetsFromDefaults;

+ (void) ppGSGlue_SaveFallbackStyleOffsetsToDefaults;

@end


@implementation NSObject (PPGNUstepGlue_WindowStyleOffsets)

// Compiz or KWin WMs

+ (void) ppGSGlue_WindowStyleOffsets_CompizKWin_InstallPatches
{
    [NSClassFromString(@"XGServer")
        ppSwizzleInstanceMethodWithSelector: @selector(styleoffsets::::::)
        forInstanceMethodWithSelector: @selector(ppGSPatch_CompizKWin_Styleoffsets::::::)];
}

+ (void) ppGSGlue_WindowStyleOffsets_CompizKWin_Install
{
    [NSUserDefaults ppGSGlue_SetupFallbackStyleOffsetsFromDefaults];

    [self ppGSGlue_WindowStyleOffsets_CompizKWin_InstallPatches];
}

// Openbox or Window Maker WMs

+ (void) ppGSGlue_WindowStyleOffsets_OpenboxWMaker_InstallPatches
{
    [NSClassFromString(@"XGServer")
        ppSwizzleInstanceMethodWithSelector: @selector(styleoffsets::::::)
        forInstanceMethodWithSelector: @selector(ppGSPatch_OpenboxWMaker_Styleoffsets::::::)];
}

// All WMs

+ (void) ppGSGlue_WindowStyleOffsets_Install
{
    if (PPGSGlueUtils_WindowManagerMatchesTypeMask(kPPGSWindowManagerTypeMask_Compiz
                                                    | kPPGSWindowManagerTypeMask_KWin))
    {
        [self ppGSGlue_WindowStyleOffsets_CompizKWin_Install];
    }
    else if (PPGSGlueUtils_WindowManagerMatchesTypeMask(kPPGSWindowManagerTypeMask_Openbox
                                                    | kPPGSWindowManagerTypeMask_WindowMaker))
    {
        [self ppGSGlue_WindowStyleOffsets_OpenboxWMaker_InstallPatches];
    }
}

+ (void) load
{
    macroPerformNSObjectSelectorAfterAppLoads(ppGSGlue_WindowStyleOffsets_Install);

    PPGSGlueUtils_PerformNSUserDefaultsSelectorBeforeGSBackendLoads(
                                            @selector(ppGSGlue_IgnoreRootWindowStyleOffsets));
}

// PATCH: -[XGServer styleoffsets::::::] (Compiz or KWin)
// When running on Compiz or KWin window managers, the styleoffsets:::::: method can return
// garbage values for some styles (and which style values return garbage may change each time
// the app runs); Workaround patch checks whether the offsets returned by the original
// implementation appear invalid (t <= 0) - if so, it replaces the returned values with valid
// fallback values (using the first valid values found; saved in user defaults for future runs
// where the app hasn't yet received valid values).

- (void) ppGSPatch_CompizKWin_Styleoffsets: (float *) l : (float *) r : (float *) t
            : (float *) b : (unsigned int) style : (PPXWindow) win
{
    [self ppGSPatch_CompizKWin_Styleoffsets: l : r : t : b : style : win];

    if (style & NSTitledWindowMask)
    {
        static bool didSaveValidFallbackOffsetsToDefaults = NO;
        bool styleOffsetsAreInvalid;

        styleOffsetsAreInvalid = (*t <= 0) ? YES : NO;

        if (styleOffsetsAreInvalid)
        {
            *l = gFallbackStyleOffset_Left;
            *r = gFallbackStyleOffset_Right;
            *t = gFallbackStyleOffset_Top;
            *b = gFallbackStyleOffset_Bottom;
        }
        else if (!didSaveValidFallbackOffsetsToDefaults)
        {
            if ((*l != gFallbackStyleOffset_Left)
                || (*r != gFallbackStyleOffset_Right)
                || (*t != gFallbackStyleOffset_Top)
                || (*b != gFallbackStyleOffset_Bottom))
            {
                gFallbackStyleOffset_Left = *l;
                gFallbackStyleOffset_Right = *r;
                gFallbackStyleOffset_Top = *t;
                gFallbackStyleOffset_Bottom = *b;

                [NSUserDefaults ppGSGlue_SaveFallbackStyleOffsetsToDefaults];
            }

            didSaveValidFallbackOffsetsToDefaults = YES;
        }
    }
}

// PATCH: -[XGServer styleoffsets::::::] (Openbox or Window Maker)
// When running on Openbox or Window Maker window managers, maximizing a titled window or
// switching a titled window between resizable & non-resizable causes drawing artifacts due to
// incorrect style offsets - this is because Openbox's & WMaker's window decorations are
// different sizes for different window states, and when the decoration sizes change on-the-fly,
// they no longer line up with GNUstep's drawing/graphics state (which seems to use cached
// offset values);
// Patch sets the win parameter to zero (if the window style is titled) before calling the
// original styleoffsets:::::: implementation - this forces it to return cached offset values
// (which should match the window's initial state & GNUstep's) instead of querying the window
// directly for its current offsets (which may no longer match GNUstep's state).

- (void) ppGSPatch_OpenboxWMaker_Styleoffsets: (float *) l : (float *) r : (float *) t
            : (float *) b : (unsigned int) style : (PPXWindow) win
{
    if (style & NSTitledWindowMask)
    {
        win = 0;
    }

    [self ppGSPatch_OpenboxWMaker_Styleoffsets: l : r : t : b : style : win];
}

@end

@implementation NSUserDefaults (PPGNUstepGlue_WindowStyleOffsets)

- (void) ppGSGlue_IgnoreRootWindowStyleOffsets
{
    NSDictionary *defaultsDict =
                            [NSDictionary dictionaryWithObject: [NSNumber numberWithBool: YES]
                                            forKey: @"GSIgnoreRootOffsets"];

    if (!defaultsDict)
        goto ERROR;

    [self registerDefaults: defaultsDict];

    return;

ERROR:
    return;
}

+ (void) ppGSGlue_SetupFallbackStyleOffsetsFromDefaults
{
    NSUserDefaults *userDefaults;
    NSArray *fallbackOffsets;

    if (PPGSGlueUtils_WindowManagerMatchesTypeMask(kPPGSWindowManagerTypeMask_KWin))
    {
        gUserDefaultsKey_FallbackWindowStyleOffsets =
                                            kUserDefaultsKey_FallbackWindowStyleOffsets_KWin;

        gFallbackStyleOffset_Left = kDefaultStyleOffsetValue_KWin_SidesAndBottom;
        gFallbackStyleOffset_Right = kDefaultStyleOffsetValue_KWin_SidesAndBottom;
        gFallbackStyleOffset_Top = kDefaultStyleOffsetValue_KWin_Top;
        gFallbackStyleOffset_Bottom = kDefaultStyleOffsetValue_KWin_SidesAndBottom;
    }
    else    // !KWin WM - use Compiz default values
    {
        gUserDefaultsKey_FallbackWindowStyleOffsets =
                                            kUserDefaultsKey_FallbackWindowStyleOffsets_Compiz;

        gFallbackStyleOffset_Left = kDefaultStyleOffsetValue_Compiz_SidesAndBottom;
        gFallbackStyleOffset_Right = kDefaultStyleOffsetValue_Compiz_SidesAndBottom;
        gFallbackStyleOffset_Top = kDefaultStyleOffsetValue_Compiz_Top;
        gFallbackStyleOffset_Bottom = kDefaultStyleOffsetValue_Compiz_SidesAndBottom;
    }

    userDefaults = [NSUserDefaults standardUserDefaults];

    fallbackOffsets = [NSArray arrayWithObjects:
                                    [NSNumber numberWithFloat: gFallbackStyleOffset_Left],
                                    [NSNumber numberWithFloat: gFallbackStyleOffset_Right],
                                    [NSNumber numberWithFloat: gFallbackStyleOffset_Top],
                                    [NSNumber numberWithFloat: gFallbackStyleOffset_Bottom],
                                    nil];

    if (fallbackOffsets)
    {
        [userDefaults registerDefaults:
                        [NSDictionary dictionaryWithObject: fallbackOffsets
                                        forKey: gUserDefaultsKey_FallbackWindowStyleOffsets]];
    }

    fallbackOffsets = [userDefaults objectForKey: gUserDefaultsKey_FallbackWindowStyleOffsets];

    if (![fallbackOffsets isKindOfClass: [NSArray class]]
        || ([fallbackOffsets count] < 4))
    {
        goto ERROR;
    }

    gFallbackStyleOffset_Left = [[fallbackOffsets objectAtIndex: 0] floatValue];
    gFallbackStyleOffset_Right = [[fallbackOffsets objectAtIndex: 1] floatValue];
    gFallbackStyleOffset_Top = [[fallbackOffsets objectAtIndex: 2] floatValue];
    gFallbackStyleOffset_Bottom = [[fallbackOffsets objectAtIndex: 3] floatValue];

    return;

ERROR:
    return;
}

+ (void) ppGSGlue_SaveFallbackStyleOffsetsToDefaults
{
    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    NSArray *fallbackOffsets =
                        [NSArray arrayWithObjects:
                                    [NSNumber numberWithFloat: gFallbackStyleOffset_Left],
                                    [NSNumber numberWithFloat: gFallbackStyleOffset_Right],
                                    [NSNumber numberWithFloat: gFallbackStyleOffset_Top],
                                    [NSNumber numberWithFloat: gFallbackStyleOffset_Bottom],
                                    nil];

    if (!fallbackOffsets)
        goto ERROR;

    [userDefaults setObject: fallbackOffsets
                    forKey: gUserDefaultsKey_FallbackWindowStyleOffsets];

    return;

ERROR:
    return;
}

@end

#endif  // GNUSTEP

