/*
    PPLayerControlButtonImagesManager.m

    Copyright 2013-2017 Josh Freeman
    http://www.twilightedge.com

    This file is part of PikoPixel for Mac OS X and 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/>.
*/

#import "PPLayerControlButtonImagesManager.h"

#import "PPLayerControlButtonImagesManager_Notifications.h"
#import "PPDocument.h"
#import "NSBitmapImageRep_PPUtilities.h"
#import "PPCompositeThumbnail.h"
#import "PPGeometry.h"
#import "PPBackgroundPattern.h"


#define kLayerControlButtonImageViewsNibName            @"LayerControlButtonImageViews"


@interface PPLayerControlButtonImagesManager (PrivateMethods)

- (void) addAsObserverForPPDocumentNotifications;
- (void) removeAsObserverForPPDocumentNotifications;
- (void) handlePPDocumentNotification_UpdatedMergedVisibleThumbnailImage:
                                                            (NSNotification *) notification;
- (void) handlePPDocumentNotification_UpdatedDrawingLayerThumbnailImage:
                                                                (NSNotification *) notification;
- (void) handlePPDocumentNotification_UpdatedBackgroundSettings:
                                                            (NSNotification *) notification;
- (void) handlePPDocumentNotification_ReloadedDocument: (NSNotification *) notification;

- (void) setupThumbnailDrawRects;
- (void) invalidateThumbnailsAndPostChangeNotifications;
- (void) setupThumbnailBackgroundBitmap;
- (void) updateEnabledLayersCompositeThumbnails;
- (void) updateDrawLayerCompositeThumbnails;

@end

@implementation PPLayerControlButtonImagesManager

+ sharedManager
{
    static PPLayerControlButtonImagesManager *sharedManager;

    if (!sharedManager)
    {
        sharedManager = [[self alloc] init];
    }

    return sharedManager;
}

- init
{
    self = [super init];

    if (!self)
        goto ERROR;

    if (![NSBundle loadNibNamed: kLayerControlButtonImageViewsNibName owner: self])
    {
        goto ERROR;
    }

    _thumbnailFramesize = [_displayModeDrawLayerThumbnailView frame].size;

    _thumbnailBackgroundBitmap =
                    [[NSBitmapImageRep ppImageBitmapOfSize: _thumbnailFramesize] retain];

    _drawLayerThumbnailBitmap =
                    [[NSBitmapImageRep ppImageBitmapOfSize: _thumbnailFramesize] retain];

    _enabledLayersThumbnailBitmap =
                    [[NSBitmapImageRep ppImageBitmapOfSize: _thumbnailFramesize] retain];

    if (!_thumbnailBackgroundBitmap
        || !_drawLayerThumbnailBitmap
        || !_enabledLayersThumbnailBitmap)
    {
        goto ERROR;
    }

    _displayModeDrawLayerCompositeThumbnail =
        [[PPCompositeThumbnail compositeThumbnailFromView: _displayModeDrawLayerView
                                    thumbnailOrigin:
                                            [_displayModeDrawLayerThumbnailView frame].origin]
                                retain];

    _displayModeEnabledLayersCompositeThumbnail =
        [[PPCompositeThumbnail compositeThumbnailFromView: _displayModeEnabledLayersView
                                    thumbnailOrigin:
                                        [_displayModeEnabledLayersThumbnailView frame].origin]
                                retain];

    _operationTargetDrawLayerCompositeThumbnail =
        [[PPCompositeThumbnail compositeThumbnailFromView: _operationTargetDrawLayerView
                                    thumbnailOrigin:
                                        [_operationTargetDrawLayerThumbnailView frame].origin]
                                retain];

    _operationTargetEnabledLayersCompositeThumbnail =
        [[PPCompositeThumbnail compositeThumbnailFromView: _operationTargetEnabledLayersView
                                thumbnailOrigin:
                                    [_operationTargetEnabledLayersThumbnailView frame].origin]
                            retain];

    if (!_displayModeDrawLayerCompositeThumbnail
        || !_displayModeEnabledLayersCompositeThumbnail
        || !_operationTargetDrawLayerCompositeThumbnail
        || !_operationTargetEnabledLayersCompositeThumbnail)
    {
        goto ERROR;
    }

    return self;

ERROR:
    [self release];

    return nil;
}

- (void) dealloc
{
    [self setPPDocument: nil];

    [_thumbnailBackgroundBitmap release];
    [_drawLayerThumbnailBitmap release];
    [_enabledLayersThumbnailBitmap release];

    [_displayModeDrawLayerCompositeThumbnail release];
    [_displayModeEnabledLayersCompositeThumbnail release];
    [_operationTargetDrawLayerCompositeThumbnail release];
    [_operationTargetEnabledLayersCompositeThumbnail release];

    [super dealloc];
}

- (void) setPPDocument: (PPDocument *) ppDocument
{
    if (_ppDocument == ppDocument)
    {
        return;
    }

    if (_ppDocument)
    {
        [self removeAsObserverForPPDocumentNotifications];
    }

    [_ppDocument release];
    _ppDocument = [ppDocument retain];

    if (_ppDocument)
    {
        [self setupThumbnailDrawRects];

        [self invalidateThumbnailsAndPostChangeNotifications];

        [self addAsObserverForPPDocumentNotifications];
    }
}

- (NSImage *) buttonImageForDisplayMode: (PPLayerDisplayMode) displayMode
{
    if (_thumbnailBackgroundBitmapIsDirty)
    {
        [self setupThumbnailBackgroundBitmap];
    }

    if (displayMode == kPPLayerDisplayMode_DrawingLayerOnly)
    {
        if (_drawLayerThumbnailsAreDirty)
        {
            [self updateDrawLayerCompositeThumbnails];
        }

        return [_displayModeDrawLayerCompositeThumbnail compositeImage];
    }
    else
    {
        if (_enabledLayersThumbnailsAreDirty)
        {
            [self updateEnabledLayersCompositeThumbnails];
        }

        return [_displayModeEnabledLayersCompositeThumbnail compositeImage];
    }
}

- (NSImage *) buttonImageForOperationTarget: (PPLayerOperationTarget) operationTarget
{
    if (_thumbnailBackgroundBitmapIsDirty)
    {
        [self setupThumbnailBackgroundBitmap];
    }

    if (operationTarget == kPPLayerOperationTarget_DrawingLayerOnly)
    {
        if (_drawLayerThumbnailsAreDirty)
        {
            [self updateDrawLayerCompositeThumbnails];
        }

        return [_operationTargetDrawLayerCompositeThumbnail compositeImage];
    }
    else
    {
        if (_enabledLayersThumbnailsAreDirty)
        {
            [self updateEnabledLayersCompositeThumbnails];
        }

        return [_operationTargetEnabledLayersCompositeThumbnail compositeImage];
    }
}

#pragma mark PPDocument notifications

- (void) addAsObserverForPPDocumentNotifications
{
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];

    if (!_ppDocument)
        return;

    [notificationCenter addObserver: self
                        selector:
                            @selector(
                            handlePPDocumentNotification_UpdatedMergedVisibleThumbnailImage:)
                        name: PPDocumentNotification_UpdatedMergedVisibleThumbnailImage
                        object: _ppDocument];

    [notificationCenter addObserver: self
                        selector:
                            @selector(
                                handlePPDocumentNotification_UpdatedDrawingLayerThumbnailImage:)
                        name: PPDocumentNotification_UpdatedDrawingLayerThumbnailImage
                        object: _ppDocument];

    [notificationCenter addObserver: self
                        selector:
                            @selector(handlePPDocumentNotification_UpdatedBackgroundSettings:)
                        name: PPDocumentNotification_UpdatedBackgroundSettings
                        object: _ppDocument];


    [notificationCenter addObserver: self
                        selector: @selector(handlePPDocumentNotification_ReloadedDocument:)
                        name: PPDocumentNotification_ReloadedDocument
                        object: _ppDocument];

}

- (void) removeAsObserverForPPDocumentNotifications
{
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];

    [notificationCenter removeObserver: self
                        name: PPDocumentNotification_UpdatedMergedVisibleThumbnailImage
                        object: _ppDocument];

    [notificationCenter removeObserver: self
                        name: PPDocumentNotification_UpdatedDrawingLayerThumbnailImage
                        object: _ppDocument];

    [notificationCenter removeObserver: self
                        name: PPDocumentNotification_UpdatedBackgroundSettings
                        object: _ppDocument];

    [notificationCenter removeObserver: self
                        name: PPDocumentNotification_ReloadedDocument
                        object: _ppDocument];
}

- (void) handlePPDocumentNotification_UpdatedMergedVisibleThumbnailImage:
                                                            (NSNotification *) notification
{
    _enabledLayersThumbnailsAreDirty = YES;

    [self postNotification_ChangedEnabledLayersImages];
}

- (void) handlePPDocumentNotification_UpdatedDrawingLayerThumbnailImage:
                                                                (NSNotification *) notification
{
    _drawLayerThumbnailsAreDirty = YES;

    [self postNotification_ChangedDrawLayerImages];
}

- (void) handlePPDocumentNotification_UpdatedBackgroundSettings:
                                                            (NSNotification *) notification
{
    [self invalidateThumbnailsAndPostChangeNotifications];
}

- (void) handlePPDocumentNotification_ReloadedDocument: (NSNotification *) notification
{
    [self setupThumbnailDrawRects];

    [self invalidateThumbnailsAndPostChangeNotifications];
}

#pragma mark Private methods

- (void) setupThumbnailDrawRects
{
    _thumbnailDrawSourceBounds = PPGeometry_OriginRectOfSize([_ppDocument canvasSize]);

    _thumbnailDrawDestinationBounds =
        PPGeometry_ScaledBoundsForFrameOfSizeToFitFrameOfSize(_thumbnailDrawSourceBounds.size,
                                                                _thumbnailFramesize);
}

- (void) invalidateThumbnailsAndPostChangeNotifications
{
    _thumbnailBackgroundBitmapIsDirty = YES;
    _enabledLayersThumbnailsAreDirty = YES;
    _drawLayerThumbnailsAreDirty = YES;

    [self postNotification_ChangedEnabledLayersImages];
    [self postNotification_ChangedDrawLayerImages];
}

- (void) setupThumbnailBackgroundBitmap
{
    PPBackgroundPattern *documentBackgroundPattern, *thumbnailBackgroundPattern;
    float patternScalingFactor;
    NSColor *thumbnailBackgroundPatternColor;

    if (!_ppDocument || NSIsEmptyRect(_thumbnailDrawSourceBounds))
    {
        goto ERROR;
    }

    documentBackgroundPattern = [_ppDocument backgroundPattern];

    patternScalingFactor = kScalingFactorForThumbnailBackgroundPatternSize
                                * _thumbnailDrawDestinationBounds.size.width
                                / _thumbnailDrawSourceBounds.size.width;

    if (patternScalingFactor > 1.0f)
    {
        patternScalingFactor = 1.0f;
    }

    thumbnailBackgroundPattern =
            [documentBackgroundPattern backgroundPatternScaledByFactor: patternScalingFactor];

    thumbnailBackgroundPatternColor = [thumbnailBackgroundPattern patternFillColor];

    if (!thumbnailBackgroundPatternColor)
        goto ERROR;

    [_thumbnailBackgroundBitmap ppClearBitmap];

    [_thumbnailBackgroundBitmap ppSetAsCurrentGraphicsContext];

    [thumbnailBackgroundPatternColor set];

    NSRectFill(_thumbnailDrawDestinationBounds);

    [_thumbnailBackgroundBitmap ppRestoreGraphicsContext];

    _thumbnailBackgroundBitmapIsDirty = NO;

    return;

ERROR:
    return;
}

- (void) updateEnabledLayersCompositeThumbnails
{
    NSImageInterpolation imageInterpolation;

    imageInterpolation =
        (_thumbnailDrawDestinationBounds.size.width >= _thumbnailDrawSourceBounds.size.width) ?
            NSImageInterpolationNone : NSImageInterpolationLow;

    [_enabledLayersThumbnailBitmap ppCopyFromBitmap: _thumbnailBackgroundBitmap
                                    toPoint: NSZeroPoint];

    [_enabledLayersThumbnailBitmap ppSetAsCurrentGraphicsContext];

    [[NSGraphicsContext currentContext] setImageInterpolation: imageInterpolation];

    [[_ppDocument mergedVisibleLayersThumbnailImage]
                                                drawInRect: _thumbnailDrawDestinationBounds
                                                fromRect: _thumbnailDrawSourceBounds
                                                operation: NSCompositeSourceOver
                                                fraction: 1.0f];

    [_enabledLayersThumbnailBitmap ppRestoreGraphicsContext];

    [_displayModeEnabledLayersCompositeThumbnail
                                            setThumbnailBitmap: _enabledLayersThumbnailBitmap];

    [_operationTargetEnabledLayersCompositeThumbnail
                                            setThumbnailBitmap: _enabledLayersThumbnailBitmap];

    _enabledLayersThumbnailsAreDirty = NO;
}

- (void) updateDrawLayerCompositeThumbnails
{
    NSImageInterpolation imageInterpolation;

    imageInterpolation =
        (_thumbnailDrawDestinationBounds.size.width >= _thumbnailDrawSourceBounds.size.width) ?
            NSImageInterpolationNone : NSImageInterpolationLow;

    [_drawLayerThumbnailBitmap ppCopyFromBitmap: _thumbnailBackgroundBitmap
                                toPoint: NSZeroPoint];

    [_drawLayerThumbnailBitmap ppSetAsCurrentGraphicsContext];

    [[NSGraphicsContext currentContext] setImageInterpolation: imageInterpolation];

    [[_ppDocument drawingLayerThumbnailImage] drawInRect: _thumbnailDrawDestinationBounds
                                                fromRect: _thumbnailDrawSourceBounds
                                                operation: NSCompositeSourceOver
                                                fraction: 1.0f];

    [_drawLayerThumbnailBitmap ppRestoreGraphicsContext];

    [_displayModeDrawLayerCompositeThumbnail setThumbnailBitmap: _drawLayerThumbnailBitmap];

    [_operationTargetDrawLayerCompositeThumbnail
                                            setThumbnailBitmap: _drawLayerThumbnailBitmap];

    _drawLayerThumbnailsAreDirty = NO;
}

@end
