//
//  XTRecolorationHelper.m
//  XTads
//
//  Created by Rune Berg on 13/07/2020.
//  Copyright © 2020 Rune Berg. All rights reserved.
//

#import "XTColorationHelper.h"
#import "XTHtmlColor.h"
#import "XTPrefs.h"
#import "XTAllocDeallocCounter.h"
#import "XTTextRecolorationTemplate.h"
#import "XTTextRecolorationHelper.h"
#import "XTTextView.h"


@interface XTColorationHelper ()

@property BOOL isForBanner;
@property BOOL isForGridBanner;

@property XTTextView *textView;
@property XTPrefs *prefs;
@property XTTextRecolorationTemplate *textRecolorationTemplate;
@property XTTextRecolorationHelper *textRecolorationHelper;

@property XTHtmlColor *lastUsedTextHtmlColor;
@property XTHtmlColor *lastUsedInputHtmlColor;
@property NSNumber *lastUsedColorsForBodyTag;

@end


@implementation XTColorationHelper

OVERRIDE_ALLOC_FOR_COUNTER
OVERRIDE_DEALLOC_FOR_COUNTER

@synthesize gridModeScreenColor = _gridModeScreenColor;

+ (instancetype)forTextView:(XTTextView *)textView
					isForT3:(BOOL)isForT3
				isForBanner:(BOOL)isForBanner
			isForGridBanner:(BOOL)isForGridBanner
{
	XTColorationHelper *res = [XTColorationHelper new];

	res.isForT3 = isForT3;
	res.isForBanner = isForBanner;
	res.isForGridBanner = isForGridBanner;

	res.textView = textView;
	res.textRecolorationTemplate = [XTTextRecolorationTemplate forTextStorage:textView.textStorage];
	res.textRecolorationHelper = [XTTextRecolorationHelper forTextStorage:textView.textStorage isForBanner:isForBanner];

	return res;
}

- (id)init
{
    self = [super init];
    if (self) {
		_isForT3 = NO;
		_isForBanner = NO;
		_isForGridBanner = NO;
		_htmlMode = NO;

		_textView = nil;
		_prefs = [XTPrefs prefs];
		_textRecolorationTemplate = nil;
		_textRecolorationHelper = nil;
		
		_lastUsedTextHtmlColor = nil;
		_lastUsedInputHtmlColor = nil;
		_lastUsedColorsForBodyTag = nil;

		_gridModeForegroundColor = nil;
		_gridModeBackgroundColor = nil;
		_gridModeScreenColor = nil;
	}
    return self;
}

- (void)setGridModeScreenColor:(XTHtmlColor *)color
{
	_gridModeScreenColor = color;
	[self refreshGridModeScreenColor];
}

- (XTHtmlColor *)gridModeScreenColor
{
	return _gridModeScreenColor;
}

//TODO !!! adaot: inline in only caller
- (void)refreshGridModeScreenColor
{
	self.textView.backgroundColor = [self getOutputBackgroundColorForTextView];
}


- (void)applyPrefsTextAndInputColors
{
	NSColor *textColor = [self getPrefsOutputTextColor];
	XTHtmlColor *textHtmlColor = [XTHtmlColor forNSColor:textColor];
	
	NSColor *inputColor = self.prefs.inputTextColor.value;
	XTHtmlColor *inputHtmlColor = [XTHtmlColor forNSColor:inputColor];
	
	[self applyTextHtmlColor:textHtmlColor inputHtmlColor:inputHtmlColor forBodyTag:NO forceApply:NO];
}

- (void)applyTextAndInputColorsForAllowGameToSetColors
{
	if ([self allowGameToSetColors]) {
		[self.textRecolorationTemplate recolorForAttribute:XT_OUTPUT_FORMATTER_ATTR_RECOLORABLE_TEXT callback:^(NSRange range, XTOutputTextColorResult *colorResult, XTColorSource colorSource) {
			[self.textRecolorationHelper restoreTextColorsSetByGameForRange:range colorResult:colorResult colorSource:colorSource];
		}];
		[self.textRecolorationTemplate recolorForAttribute:XT_OUTPUT_FORMATTER_ATTR_RECOLORABLE_TEXT_BACKGROUND callback:^(NSRange range, XTOutputTextColorResult *colorResult, XTColorSource colorSource) {
			[self.textRecolorationHelper restoreTextBackgroundColorsSetByGameForRange:range colorResult:colorResult colorSource:colorSource];
		}];
	} else {
		[self.textRecolorationTemplate recolorForAttribute:XT_OUTPUT_FORMATTER_ATTR_RECOLORABLE_TEXT callback:^(NSRange range, XTOutputTextColorResult *colorResult, XTColorSource colorSource) {
			[self.textRecolorationHelper removeTextColorsSetByGameForRange:range colorResult:colorResult colorSource:colorSource];
		}];
		[self.textRecolorationTemplate recolorForAttribute:XT_OUTPUT_FORMATTER_ATTR_RECOLORABLE_TEXT_BACKGROUND callback:^(NSRange range, XTOutputTextColorResult *colorResult, XTColorSource colorSource) {
			[self.textRecolorationHelper removeTextBackgroundColorsSetByGameForRange:range colorResult:colorResult colorSource:colorSource];
		}];
	}
}

//TODO !!! mv:
- (void)applyBodyTextAndInputColorsForceApply:(BOOL)forceApply
{
	//XT_DEF_SELNAME;

	XTHtmlColor *textHtmlColor = self.bodyTextColor;
	XTHtmlColor *inputHtmlColor = self.bodyInputColor;
	[self applyTextHtmlColor:textHtmlColor inputHtmlColor:inputHtmlColor forBodyTag:YES forceApply:forceApply];
}

- (void)applyBodyLinkColor
{
	[self.textRecolorationTemplate recolorForAttribute4:XT_OUTPUT_FORMATTER_ATTR_TEMPATTRSDICT callback:^(NSRange range, id attrValue) {
		
		NSDictionary *tempAttrDict = attrValue;
		NSMutableDictionary *newTempAttrDict = [NSMutableDictionary dictionaryWithDictionary:tempAttrDict];
		
		NSNumber *isPlainLinkObj = tempAttrDict[XT_OUTPUT_FORMATTER_ATTR_PLAIN_LINK];
		BOOL isPlainLink = isPlainLinkObj.boolValue;
		
		//TODO !!! adapt: recalc dict[NSUnderlineStyleAttributeName]
			// sep. method?
			// when? non-plain link without underline from fmt spec? XT_OUTPUT_FORMATTER_ATTR_UNDERLINE_FROM_PREFS
				
		NSColor *color = nil;
		XTOutputTextColorResult *colorResult = tempAttrDict[XT_OUTPUT_FORMATTER_ATTR_RECOLORABLE_TEXT]; //TODO !!! adapt: what is the point of this?!
		if (! isPlainLink) {
			colorResult = [self getLinkColorResult];
			if ([self allowGameToSetColors]) {
				color = colorResult.htmlColor.color;
			}
			if (color == nil) {
				color = self.prefs.linksTextColor.value;
			}
		} else {
			colorResult = [self getOutputTextColorFromBodyOrPrefs];
			color = colorResult.htmlColor.color;
		}
		newTempAttrDict[NSForegroundColorAttributeName] = color;
		newTempAttrDict[XT_OUTPUT_FORMATTER_ATTR_RECOLORABLE_TEXT] = colorResult;
		
		[self.textView.textStorage addAttribute:XT_OUTPUT_FORMATTER_ATTR_TEMPATTRSDICT value:newTempAttrDict range:range];
		[self.textView.layoutManager addTemporaryAttributes:newTempAttrDict forCharacterRange:range];
	}];
}

- (void)updateLinkColors
{
	[self.textRecolorationTemplate recolorForAttribute4:XT_OUTPUT_FORMATTER_ATTR_TEMPATTRSDICT callback:^(NSRange range, id attrValue) {
		
		NSDictionary *tempAttrDict = attrValue;
		NSMutableDictionary *newTempAttrDict = [NSMutableDictionary dictionaryWithDictionary:tempAttrDict];
		
		//TODO !!! adapt: recalc dict[NSUnderlineStyleAttributeName]
			// sep. method?
			// when? non-plain link without underline from fmt spec? XT_OUTPUT_FORMATTER_ATTR_UNDERLINE_FROM_PREFS
				
		NSColor *color = nil;
		XTOutputTextColorResult *colorResult = tempAttrDict[XT_OUTPUT_FORMATTER_ATTR_RECOLORABLE_TEXT];
		
		if ([self allowGameToSetColors]) {
			color = colorResult.htmlColor.color;
		}
		if (color == nil) {
			NSNumber *isPlainLinkObj = tempAttrDict[XT_OUTPUT_FORMATTER_ATTR_PLAIN_LINK];
			BOOL isPlainLink = isPlainLinkObj.boolValue;

			if (! isPlainLink) {
				color = self.prefs.linksTextColor.value;
			} else {
				color = [self.textView.textStorage attribute:NSForegroundColorAttributeName atIndex:range.location effectiveRange:nil];
			}
		}
		
		newTempAttrDict[NSForegroundColorAttributeName] = color;
		
		[self.textView.textStorage addAttribute:XT_OUTPUT_FORMATTER_ATTR_TEMPATTRSDICT value:newTempAttrDict range:range];
		[self.textView.layoutManager addTemporaryAttributes:newTempAttrDict forCharacterRange:range];
	}];
}

- (void)updateTableColors
{
	[self.textRecolorationTemplate recolorForAttribute4:NSParagraphStyleAttributeName callback:^(NSRange range, id attrValue) {
		
		[self.textRecolorationHelper updateTableColors:attrValue];
	}];
}

- (void)updateCursorColor:(XTFormattingSpecificationForHtmlTag *)formattingSpecForHtmlTag
{
	NSColor *textColor;
	if (! [self allowGameToSetColors]) {
		textColor = self.prefs.outputAreaTextColor.value;
	} else {
		XTOutputTextColorResult *textColorResult = [self getOutputTextColor:formattingSpecForHtmlTag];
		textColor = textColorResult.htmlColor.color;
	}
	if (textColor != nil) {
		[self.textView setInsertionPointColor:textColor];
	}
}

- (void)resetBodyColors
{
	self.bodyBackgroundColor = nil;
	self.textView.backgroundColor = [self getOutputBackgroundColorForTextView];
	self.bodyTextColor = nil;
	self.bodyInputColor = nil;
	self.bodyLinkColor = nil;
}

- (void)applyTextHtmlColor:(XTHtmlColor *)textHtmlColor
			inputHtmlColor:(XTHtmlColor *)inputHtmlColor
				forBodyTag:(BOOL)forBodyTag
				forceApply:(BOOL)forceApply
{
	if (! forceApply) {
		if (! [self shouldApplyTextHtmlColor:textHtmlColor inputHtmlColor:inputHtmlColor forBodyTag:forBodyTag]) {
			return;
		}
	}
	
	BOOL allowGameToSetColors = [self allowGameToSetColors];
	
	[self.textRecolorationTemplate recolorForAttribute:XT_OUTPUT_FORMATTER_ATTR_RECOLORABLE_TEXT callback:^(NSRange range, XTOutputTextColorResult *colorResult, XTColorSource colorSource) {
		[self.textRecolorationHelper applyTextHtmlColor:range
										colorResult:colorResult
										colorSource:colorSource
									  textHtmlColor:textHtmlColor
									 inputHtmlColor:inputHtmlColor
										 forBodyTag:forBodyTag
										 forceApply:forceApply
							   allowGameToSetColors:allowGameToSetColors];
	}];
	
	self.lastUsedTextHtmlColor = textHtmlColor;
	self.lastUsedInputHtmlColor = inputHtmlColor;
	self.lastUsedColorsForBodyTag = [NSNumber numberWithBool:forBodyTag];

	if ((! forBodyTag) || forceApply) {
		// prefs changed
		[self.textRecolorationTemplate recolorForAttribute:XT_OUTPUT_FORMATTER_ATTR_RECOLORABLE_TEXT_BACKGROUND callback:^(NSRange range, XTOutputTextColorResult *colorResult, XTColorSource colorSource) {
			[self.textRecolorationHelper applyTextHtmlBackgroundColor:range
													  colorResult:colorResult
													  colorSource:colorSource
													   forceApply:forceApply
											 allowGameToSetColors:allowGameToSetColors];
		}];
	}
}

- (BOOL)shouldApplyTextHtmlColor:(XTHtmlColor *)textHtmlColor inputHtmlColor:(XTHtmlColor *)inputHtmlColor forBodyTag:(BOOL)forBodyTag
{
	if (! forBodyTag) {
		// prefs changed
		return YES;
	}
	
	BOOL newTextHtmlColor = ! [XTHtmlColor safeCompare:textHtmlColor to:self.lastUsedTextHtmlColor];
	if (newTextHtmlColor) {
		return YES;
	}
	
	if (! self.isForBanner) {
		BOOL newInputHtmlColor = ! [XTHtmlColor safeCompare:inputHtmlColor to:self.lastUsedInputHtmlColor];
		if (newInputHtmlColor) {
			return YES;
		}
	}
	
	BOOL newForBodyTag = (self.lastUsedColorsForBodyTag == nil || self.lastUsedColorsForBodyTag.boolValue != forBodyTag);
	if (newForBodyTag) {
		return YES;
	}

	return NO;
}

- (NSColor *)getPrefsOutputTextColor
{
	NSColor *res;
	if (self.isForBanner) {
		res = self.prefs.statusLineTextColor.value;
	} else {
		res = self.prefs.outputAreaTextColor.value;
	}
	return res;
}

- (BOOL)allowGameToSetColors
{
	BOOL res;
	
	if ([self isForT2PlainTextBanner]) {
		res = NO;
	} else {
		NSNumber *valueObj = self.prefs.allowGamesToSetColors.value;
		res = (valueObj == nil || valueObj.boolValue);
	}
	
	return res;
}

- (XTOutputTextColorResult *)getLinkColorResult
{
	XTColorSource colorSource;
	XTHtmlColor *htmlColor;
	
	if (self.bodyLinkColor != nil) {
		colorSource = XT_COLOR_SOURCE_OUTPUT_FROM_BODY;
		htmlColor = [XTHtmlColor forAttributeValue:self.bodyLinkColor.attrValue];
	} else {
		colorSource = XT_COLOR_SOURCE_OUTPUT_FROM_PREFS;
		htmlColor = [XTHtmlColor forAttributeValueLink];
	}
	
	XTOutputTextColorResult *res = [XTOutputTextColorResult forHtmlColor:htmlColor colorSource:colorSource];
	
	return res;
}

//TODO !!! adapt: a mess - streamline
- (XTOutputTextColorResult *)getOutputTextColor:(XTFormattingSpecificationForHtmlTag *)formattingSpecForHtmlTag
{
	XTHtmlColor *htmlColor = nil;
	XTColorSource colorSource = XT_COLOR_SOURCE_OUTPUT_FROM_PREFS;
	BOOL bannerTextColorFromMainAreaPrefs = NO;
	
	if (self.isForGridBanner) {
		if (self.gridModeForegroundColor != nil) {
			if (self.gridModeForegroundColor.color != nil) {
				htmlColor = self.gridModeForegroundColor;
				colorSource = XT_COLOR_SOURCE_OUTPUT_FROM_FONT;
			} else {
				NSColor *color;
				if ([self allowGameToSetColors]) {
					color = self.prefs.outputAreaTextColor.value; // yup, that's what mjr terp and qtads do
				} else {
					color = [self getPrefsOutputTextColor];
				}
				htmlColor = [XTHtmlColor forNSColor:color];
				colorSource = XT_COLOR_SOURCE_OUTPUT_FROM_PREFS;
				bannerTextColorFromMainAreaPrefs = YES;
			}
		} else {
			htmlColor = [self getGridModeDefaultTextColor];
			colorSource = XT_COLOR_SOURCE_OUTPUT_FROM_FONT;
		}
	}
	
	if (htmlColor == nil) {
		XTFormattingSpecification *formattingSpec = formattingSpecForHtmlTag.formattingSpec;
		if (formattingSpec.fontColor != nil) {
			htmlColor = formattingSpec.fontColor;
			colorSource = XT_COLOR_SOURCE_OUTPUT_FROM_FONT;
			if (htmlColor.color == nil) {
				htmlColor = [XTHtmlColor forAttributeValue:@"black"];
			}
		}
	}
	
	if (htmlColor == nil) {
		XTOutputTextColorResult *tempRes = [self getOutputTextColorFromBodyOrPrefs];
		htmlColor = tempRes.htmlColor;
		colorSource = tempRes.colorSource;
		bannerTextColorFromMainAreaPrefs = tempRes.bannerTextColorFromMainAreaPrefs;
	}
	
	XTOutputTextColorResult *res = [XTOutputTextColorResult forHtmlColor:htmlColor
															 colorSource:colorSource
										bannerTextColorFromMainAreaPrefs:bannerTextColorFromMainAreaPrefs];
	return res;
}

- (XTOutputTextColorResult *)getOutputTextColorFromBodyOrPrefs
{
	XTHtmlColor *htmlColor = nil;
	XTColorSource colorSource = XT_COLOR_SOURCE_OUTPUT_FROM_PREFS;
	BOOL bannerTextColorFromMainAreaPrefs = NO;

	if (self.bodyTextColor != nil) {
		htmlColor = self.bodyTextColor;
		colorSource = XT_COLOR_SOURCE_OUTPUT_FROM_BODY;
	}
	if (htmlColor.color == nil) {
		if (self.isForBanner && ! [self isForT2PlainTextBanner] && ! self.isForT3) {
			htmlColor = [XTHtmlColor forAttributeValue:@"black"];
			colorSource = XT_COLOR_SOURCE_OUTPUT_FROM_BODY;
		} else {
			NSColor *color;
			if (self.isForBanner && self.isForT3) {
				if ([self allowGameToSetColors]) {
					color = self.prefs.outputAreaTextColor.value; // yup, that's what mjr terp and qtads do
				} else {
					color = [self getPrefsOutputTextColor];
				}
				bannerTextColorFromMainAreaPrefs = YES;
			} else {
				color = [self getPrefsOutputTextColor];
			}
			htmlColor = [XTHtmlColor forNSColor:color];
			colorSource = XT_COLOR_SOURCE_OUTPUT_FROM_PREFS;
		}
	}
	
	XTOutputTextColorResult *res = [XTOutputTextColorResult forHtmlColor:htmlColor
															 colorSource:colorSource
										bannerTextColorFromMainAreaPrefs:bannerTextColorFromMainAreaPrefs];
	return res;
}

- (XTOutputTextColorResult *)getOutputBackgroundColor:(XTFormattingSpecificationForHtmlTag *)formattingSpecForHtmlTag // for text
{
	XTHtmlColor *htmlColor = nil;
	XTColorSource colorSource = XT_COLOR_SOURCE_OUTPUT_FROM_PREFS;
	
	if (self.gridModeBackgroundColor != nil) {
		htmlColor = self.gridModeBackgroundColor;
		colorSource = XT_COLOR_SOURCE_BACKGROUND_FROM_FONT;
	} else {
		XTFormattingSpecification *formattingSpec = formattingSpecForHtmlTag.formattingSpec;
		if (formattingSpec.backgroundColor.color != nil) {
			htmlColor = formattingSpec.backgroundColor;
			colorSource = XT_COLOR_SOURCE_BACKGROUND_FROM_FONT;
		}
	}
	
	XTOutputTextColorResult *res = [XTOutputTextColorResult forHtmlColor:htmlColor colorSource:colorSource];
	return res;
}

- (void)updateLinksUnderline
{
	NSNumber *underlineStyleSingle = [NSNumber numberWithInt:NSUnderlineStyleSingle];
	NSNumber *underlineStyleNone = [NSNumber numberWithInt:NSUnderlineStyleNone];
	BOOL underlineLinks = self.prefs.linksUnderline.value.boolValue;
	
	[self.textRecolorationTemplate recolorForAttribute4:XT_OUTPUT_FORMATTER_ATTR_TEMPATTRSDICT callback:^(NSRange range, id attrValue) {
		
		NSDictionary *tempAttrDict = attrValue;
		
		NSNumber *underlineIsFromPrefsObj = tempAttrDict[XT_OUTPUT_FORMATTER_ATTR_UNDERLINE_FROM_PREFS];
		BOOL underlineIsFromPrefs = underlineIsFromPrefsObj.boolValue;

		if (underlineIsFromPrefs) {
			NSMutableDictionary *newTempAttrDict = [NSMutableDictionary dictionaryWithDictionary:tempAttrDict];

			if (underlineLinks) {
				newTempAttrDict[NSUnderlineStyleAttributeName] = underlineStyleSingle;
			} else {
				newTempAttrDict[NSUnderlineStyleAttributeName] = underlineStyleNone;
			}
							
			[self.textView.textStorage addAttribute:XT_OUTPUT_FORMATTER_ATTR_TEMPATTRSDICT value:newTempAttrDict range:range];
			[self.textView.layoutManager addTemporaryAttributes:newTempAttrDict forCharacterRange:range];
		}
	}];
}

//TODO !!! adapt: simplify logic!!!
- (NSColor *)getOutputBackgroundColorForTextView
{
	NSColor *res;
	BOOL allowGameToSetColors = [self allowGameToSetColors];
	
	if (self.isForGridBanner) {
		if (self.gridModeScreenColor != nil) {
			if (allowGameToSetColors) {
				res = self.gridModeScreenColor.color;
			} else {
				res = [self getPrefsOutputBackgroundColorForTextView];
			}
		} else {
			res = [self getPrefsOutputBackgroundColorForTextBannerTextView];
		}
		return res;
	}
	
	if (allowGameToSetColors && self.bodyBackgroundColor.color != nil) {
		res = self.bodyBackgroundColor.color;
	} else if (allowGameToSetColors && self.isForBanner /*isForTagBanner*/ && ! self.isForT3) {
		res = [self getPrefsOutputBackgroundColorForTextBannerTextView];
	} else {
		if (self.isForBanner && self.isForT3 && allowGameToSetColors) {
			res = self.prefs.outputAreaBackgroundColor.value; // yup, that's what mjr terp and qtads do
		} else {
			res = [self getPrefsOutputBackgroundColorForTextView];
		}
	}
	
	return res;
}

- (NSColor *)getPrefsOutputBackgroundColorForTextView
{
	NSColor *res;
	if (self.isForBanner) {
		res = self.prefs.statusLineBackgroundColor.value;
	} else {
		res = self.prefs.outputAreaBackgroundColor.value;
	}
	return res;
}

- (NSColor *)getPrefsOutputBackgroundColorForTextBannerTextView
{
	NSColor *res;
	if ([self allowGameToSetColors]) {
		res = self.prefs.outputAreaBackgroundColor.value; // yup, main area's screen color - this is what mjr's terp does
	} else {
		res = [self getPrefsOutputBackgroundColorForTextView];
	}
	return res;
}

- (XTHtmlColor *)getGridModeDefaultTextColor
{
	XTHtmlColor *res = [XTHtmlColor forAttributeValue:@"black"];
	return res;
}

- (BOOL)isForT2PlainTextBanner
{
	BOOL res = self.isForBanner && (! self.isForT3) && (! self.htmlMode);
	return res;
}

@end
