//
//  XTTextTableBlock.m
//  XTads
//
//  Created by Rune Berg on 03/09/2018.
//  Copyright © 2018 Rune Berg. All rights reserved.
//

#import "XTTextTableBlock.h"
#import "XTTextTable.h"
#import "XTFontUtils.h"
#import "XTTableColumnWidthTracker.h"
#import "XTStringUtils.h"
#import "XTLogger.h"
#import "XTTimer.h"
#import "XTRequiredRectForTextCache.h"
#import "XTRect.h"
#import "XTOutputFormatter.h"
#import "XTTabStopUtils.h"
#import "XTTextView.h"
#import "XTAllocDeallocCounter.h"


@interface XTTextTableBlock ()

@property NSNumber *widthAsPercentage;
@property NSNumber *widthAsPoints;

@property XTRect *latestContentRect;
@property NSRange rangeForLatestContentRect;

@property XTHtmlColor *backgroundHtmlColor;

@end


@implementation XTTextTableBlock

static XTLogger* logger;

static NSUInteger countRectForLayoutAtPoint = 0;
static double totalTimeInRectForLayoutAtPoint1 = 0.0;
static double totalTimeInRectForLayoutAtPoint2 = 0.0;
static double totalTimeInRectForLayoutAtPoint3 = 0.0;
static double totalTimeInRectForLayoutAtPoint4 = 0.0;
static double totalTimeInRectForLayoutAtPoint5 = 0.0;

@synthesize widthAsPercentage = _widthAsPercentage;
@synthesize widthAsPoints = _widthAsPoints;
@synthesize backgroundHtmlColor = _backgroundHtmlColor;

+ (void)initialize
{
	logger = [XTLogger loggerForClass:[XTTextTableBlock class]];
}

OVERRIDE_ALLOC_FOR_COUNTER
OVERRIDE_DEALLOC_FOR_COUNTER

- (NSNumber *)widthAsPercentage
{
	return _widthAsPercentage;
}

- (void)setWidthAsPercentage:(NSNumber *)widthAsPercentage
{
	_widthAsPercentage = widthAsPercentage;
}

- (NSNumber *)widthAsPoints
{
	return _widthAsPoints;
}

- (void)setWidthAsPoints:(NSNumber *)widthAsPoints
{
	_widthAsPoints = widthAsPoints;
}

- (XTHtmlColor *)backgroundHtmlColor
{
	return _backgroundHtmlColor;
}

- (void)setBackgroundHtmlColor:(XTHtmlColor *)htmlColor
{
	_backgroundHtmlColor = htmlColor;
	[self updateBackgroundColor];
}

- (void)updateBackgroundColor
{
	NSColor *color = nil;
	if ([self allowGameToSetColors]) {
		color = self.backgroundHtmlColor.color;
	}
	[self setBackgroundColor:color];
}

- (XTRect *)getlatestContentRect
{
	return self.latestContentRect;
}

//TODO !!! too long - split up
- (NSRect)rectForLayoutAtPoint:(NSPoint)startingPoint
						inRect:(NSRect)rect
				 textContainer:(NSTextContainer *)textContainer
				characterRange:(NSRange)charRange
{
	NSInteger startingRow = self.startingRow;
	NSInteger startingColumn = self.startingColumn;
	
	XT_DEF_SELNAME;
	//XT_WARN_2(@"row=%ld col=%ld", startingRow, startingColumn); //TDOO !!! rm
	
	XTTimer *timer = [XTTimer fromNow];
	countRectForLayoutAtPoint += 1;
	
	[self recalcCellMargins];

	NSRect res = [super rectForLayoutAtPoint:startingPoint inRect:rect textContainer:textContainer characterRange:charRange];
		// TODO !!! Only needed for origin.y - can we simplify/avoid?
	//XT_WARN_3(@"charRange=(%lu, %lu) res.origin.y=%lf", charRange.location, charRange.length, res.origin.y);
	totalTimeInRectForLayoutAtPoint1 += [timer timeElapsed];

	NSTextStorage *textStorage = textContainer.layoutManager.textStorage;
	XTTableColumnWidthTracker *tracker = [XTTableColumnWidthTracker tracker];

	NSAttributedString *attrStringInCell = [textStorage attributedSubstringFromRange:charRange];
	//XT_WARN_1(@"attrStringInCell=\"%@\"", attrStringInCell.string);

	// Calc. max/ideal content rect width and remember it:
	//----------------------------------------------------
	
	NSSize viewSize;
	if ((self.latestContentRect != nil) && NSEqualRanges(charRange, self.rangeForLatestContentRect)) {
		viewSize = self.latestContentRect.rect.size;
	} else {
		viewSize = NSMakeSize(10000.0, 10000.0);
	}
	NSSize sizeOfRange = [XTFontUtils sizeOfTextInTextStorage:textStorage
														range:charRange
													 viewSize:viewSize];
	
	//if (sizeOfRange.width == 10000.0) {
	//	NSAttributedString *subString = [textStorage attributedSubstringFromRange:charRange];
	//}
	
	totalTimeInRectForLayoutAtPoint2 += [timer timeElapsed];

	CGFloat maxContentRectWidth;
	if (sizeOfRange.width == 0.0) {
		maxContentRectWidth = 1.0;
	} else {
		maxContentRectWidth = sizeOfRange.width;
	}
	[tracker noteMaxContentRectWidthForTable:self.tableId
							  textTableBlock:self
									   width:maxContentRectWidth];
	
	//XT_WARN_1(@"maxContentRectWidth=%lf", maxContentRectWidth);

	// Calc. minimum content rect width and remember it:
	//--------------------------------------------------

	CGFloat minContentRectWidth;
	if (sizeOfRange.width == 0.0) {
		minContentRectWidth = 1.0;
	} else {
		minContentRectWidth = [XTFontUtils widthOfLongestIndivisibleWordInTextStorage:textStorage
																				range:charRange
																	numCharsInLongest:nil];
	}
	[tracker noteMinContentRectWidthForTable:self.tableId
							  textTableBlock:self
									   width:minContentRectWidth];
	totalTimeInRectForLayoutAtPoint3 += [timer timeElapsed];
	
	//XT_WARN_1(@"minContentRectWidth=%lf", minContentRectWidth);

	// Calc. the actual content rect width
	//----------------------------------------------------
	
	//TODO !!! "when <table> has computed value" case
	
	//TODO !!! CAPMIN for <caption>

	CGFloat tableLevelBoundsSize = [self totalTableLevelBoundsWidth];
	
	//TODO !!! adjust usableWidth for % / pts here?

	CGFloat usableWidth = rect.size.width;
	usableWidth -= tableLevelBoundsSize;
	CGFloat contentRectWidth = [tracker contentRectWidthForTable:self.tableId textTableBlock:self usableWidth:usableWidth];
	if (contentRectWidth < 0.0) {
		XT_WARN_3(@"row=%ld col=%ld : contentRectWidth %lf < 0.0", self.startingRow, self.startingColumn, contentRectWidth);
	}
	totalTimeInRectForLayoutAtPoint4 += [timer timeElapsed];

	//XT_WARN_1(@"contentRectWidth=%lf", contentRectWidth);

	// Calc. the x position
	//----------------------------------------------------

	NSAttributedString *attrStringForRange = [textStorage attributedSubstringFromRange:charRange];
	//XT_WARN_1(@"attrStringForRange=\"%@\"", attrStringForRange);

	res.size.width = contentRectWidth;
	
	CGFloat originXAdjustedForTableLevelBounds = rect.origin.x + floor(tableLevelBoundsSize / 2.0);
	//CGFloat oldX = res.origin.x;
	res.origin.x = [tracker contentRectXForTable:self.tableId
								  textTableBlock:self
										 originX:originXAdjustedForTableLevelBounds
									 usableWidth:usableWidth];
	
	// Calc. cell height
	//----------------------------------------------------
	
	NSSize requiredContentRectSize;
	if ([self isStringForEmptyCell:attrStringForRange.string]) {
		requiredContentRectSize = NSMakeSize(1.0, 1.0);
	} else {
		NSSize contentRectSize = NSMakeSize(res.size.width, rect.size.height);
		requiredContentRectSize = [XTFontUtils requiredRectForText:attrStringForRange
													 forViewOfSize:contentRectSize
										suppressCenterAndRightTabs:YES];
	}
	CGFloat requiredContentRectHeight = requiredContentRectSize.height;
	NSUInteger numberOfNewlinesTrailingNewlines = [XTStringUtils numberOfTrailingNewlinesIn:attrStringForRange.string];
	if (numberOfNewlinesTrailingNewlines >= 2) {
		requiredContentRectHeight += 1.0; // This is required to avoid looping the OS layout engine
	}
	res.size.height = requiredContentRectHeight;
	totalTimeInRectForLayoutAtPoint5 += [timer timeElapsed];
	
	// Log and finish up
	//----------------------------------------------------
	
	/*
	XT_WARN_12(@"row=%ld col=%ld (start x=%ld y=%ld) (in rect x=%ld y=%ld w=%ld h=%ld) -> (x=%ld y=%ld w=%ld h=%ld)",
			  self.startingRow, self.startingColumn,
			  (NSInteger)startingPoint.x, (NSInteger)startingPoint.y,
			  (NSInteger)rect.origin.x, (NSInteger)rect.origin.y,
			  (NSInteger)rect.size.width, (NSInteger)rect.size.height,
			  (NSInteger)res.origin.x, (NSInteger)res.origin.y,
			  (NSInteger)res.size.width, (NSInteger)res.size.height);
	 */
	/*
	XT_WARN_8(@"row=%ld col=%ld (start x=%ld) (in rect x=%ld w=%ld) -> res=(x=%ld w=%ld) instance %@",
			   self.startingRow, self.startingColumn,
			   (NSInteger)startingPoint.x,
			   (NSInteger)rect.origin.x,
			   (NSInteger)rect.size.width,
			   (NSInteger)res.origin.x,
			   (NSInteger)res.size.width,
			   self);
	*/
	/*
	XT_WARN_6(@"row=%ld col=%ld (in rect x=%ld w=%ld) -> res=(x=%ld w=%ld)",
			   self.startingRow, self.startingColumn,
			   (NSInteger)rect.origin.x,
			   (NSInteger)rect.size.width,
			   (NSInteger)res.origin.x,
			   (NSInteger)res.size.width);
	 */
	if (res.size.width < 0.0) {
		XT_WARN_3(@"row=%ld col=%ld : res.width %lf < 0.0", self.startingRow, self.startingColumn, res.size.width);
	} else {
		//XT_WARN_2(@"row=%ld col=%ld OK", self.startingRow, self.startingColumn);
	}
	CGFloat totalBoundsWidth = [self totalBoundsWidth];
	CGFloat limit = rect.size.width + totalBoundsWidth;
		// adjust by totalBoundsWidth because columnInfo.totalBoundsWidth might not have been set yet
	if (res.origin.x + res.size.width > limit) {
		XT_WARN_5(@"row=%ld col=%ld res.x %lf + res.width %lf > limit %lf", self.startingRow, self.startingColumn, res.origin.x, res.size.width, limit);
	} else {
		//XT_WARN_5(@"row=%ld col=%ld res.x %lf + res.width %lf <= limit %lf", self.startingRow, self.startingColumn, res.origin.x, res.size.width, limit);
		//XT_WARN_6(@"row=%ld col=%ld res=(x=%lf width=%lf y=%lf height=%lf)",
		//		  self.startingRow, self.startingColumn, res.origin.x, res.size.width, res.origin.y, res.size.height);
	}
	
	/*
	XTRequiredRectForTextCache *reqRectForTextCache = [XTRequiredRectForTextCache singletonInstance];
	XT_WARN_9(@"count=%ld totalTime1=%lf totalTime2=%lf totalTime3=%lf totalTime4=%lf totalTime5=%lf totalTime=%lf -- totalTimeInRequiredRectForText=%lf cache-stats=%@",
			  countRectForLayoutAtPoint,
			  totalTimeInRectForLayoutAtPoint1,
			  totalTimeInRectForLayoutAtPoint2 - totalTimeInRectForLayoutAtPoint1,
			  totalTimeInRectForLayoutAtPoint3 - totalTimeInRectForLayoutAtPoint2,
			  totalTimeInRectForLayoutAtPoint4 - totalTimeInRectForLayoutAtPoint3,
			  totalTimeInRectForLayoutAtPoint5 - totalTimeInRectForLayoutAtPoint4,
			  totalTimeInRectForLayoutAtPoint5,
			  //[XTFontUtils getTotalTimeInRequiredRectForTextStorage],
			  [XTFontUtils getTotalTimeInRequiredRectForText],
			  [reqRectForTextCache statsString]
			  );
	 */

	if ((self.latestContentRect == nil) || ! CGRectEqualToRect(self.latestContentRect.rect, res)) {
		XTTextView *textView = (XTTextView *)textContainer.textView;
		textView.tableCellChangedContentRect = YES;
	}
	self.latestContentRect = [XTRect with:res];
	self.rangeForLatestContentRect = NSMakeRange(charRange.location, charRange.length);
	//XT_WARN_2(@"self.latestContentRect set to (width=%lf) for instance %@", self, res.size.width);
	
	//XT_WARN_4(@"row=%ld col=%ld -> res.width=%lf res.height=%lf", self.startingRow, self.startingColumn, res.size.width, res.size.height);
	return res;
}

- (NSRect)boundsRectForContentRect:(NSRect)contentRect
							inRect:(NSRect)rect
					 textContainer:(NSTextContainer *)textContainer
					characterRange:(NSRange)charRange
{
	//XTTimer *timer = [XTTimer fromNow];
	//countBoundsRectForContentRect += 1;
	XT_DEF_SELNAME;
	/*
	XT_WARN_8(@"contentRect=(x=%ld y=%ld w=%ld h=%ld) rect=(x=%ld y=%ld w=%ld, h=%ld)",
			  (NSInteger)contentRect.origin.x, (NSInteger)contentRect.origin.y,
			  (NSInteger)contentRect.size.width, (NSInteger)contentRect.size.height,
			  (NSInteger)rect.origin.x, (NSInteger)rect.origin.y,
			  (NSInteger)rect.size.width, (NSInteger)rect.size.height);
	XT_WARN_4(@"contentRect=(x=%ld w=%ld) rect=(x=%ld w=%ld)",
			  (NSInteger)contentRect.origin.x,
			  (NSInteger)contentRect.size.width,
			  (NSInteger)rect.origin.x,
			  (NSInteger)rect.size.width);
	 XT_WARN_2(@"contentRect=(x=%ld w=%ld)",
	 (NSInteger)contentRect.origin.x,
	 (NSInteger)contentRect.size.width);
	 */
	//XT_WARN_2(@"row=%ld col=%ld", self.startingRow, self.startingColumn);

	XTTableColumnWidthTracker *tracker = [XTTableColumnWidthTracker tracker];

	NSRect res = [super boundsRectForContentRect:contentRect inRect:rect textContainer:textContainer characterRange:charRange];
	//NSRect resOrig = res;
	
	CGFloat totalBoundsWidth = [self totalBoundsWidth];
	CGFloat origWidth = res.size.width;
	res.size.width = contentRect.size.width + totalBoundsWidth;
	//XT_WARN_6(@"row=%ld col=%ld res.width %lf -> contentRect.width %lf + totalBoundsWidth %lf = %lf",
	//		  self.startingRow, self.startingColumn, origWidth, contentRect.size.width, totalBoundsWidth, res.size.width);
	
	[tracker noteTotalBoundsWidthForTable:self.tableId
						   textTableBlock:self
									width:totalBoundsWidth /*WithCollapsingBorders*/];
	
	[tracker noteBoundsRectWidthForTable:self.tableId
						  textTableBlock:self
								   width:res.size.width];

	/*
	XT_WARN_6(@"row=%ld col=%ld res=(x=%lf width=%lf y=%lf height=%lf)",
			  self.startingRow, self.startingColumn,
			  res.origin.x, res.size.width, res.origin.y, res.size.height);
	/*/
	/*
	XT_WARN_2(@"res=(x=%ld w=%ld)",
			  (NSInteger)res.origin.x,
			  (NSInteger)res.size.width);
	*/
	//totalTimeInBoundsRectForContentRect += [timer timeElapsed];
	//XT_WARN_2(@"count=%ld totalTime=%lf", countBoundsRectForContentRect, totalTimeInBoundsRectForContentRect);
	
	if (self.latestContentRect != nil &&
		self.latestContentRect.rect.origin.x == contentRect.origin.x &&
		self.latestContentRect.rect.size.width == contentRect.size.width) {
		//
		// contentRect param is same as last return value from rectForLayoutAtPoint,
		// so we expect res to have "normal" values.
		// (For some reason, boundsRectForContentRect: doesn't always get same contentRect as returned by rectForLayoutAtPoint:)
		//
		//TODO !!! test _18
		if (res.origin.x < 0.0) {
			XT_WARN_3(@"row=%ld col=%ld res.x %lf < 0.0", self.startingRow, self.startingColumn, res.origin.x);
			int brkpt = 1;
		} else {
			//XT_WARN_3(@"row=%ld col=%ld res.x %lf >= 0.0", self.startingRow, self.startingColumn, res.origin.x);
		}
		CGFloat limit = rect.size.width + totalBoundsWidth;
			// because rect.size.width might have been calc'd before columnInfo.totalBoundsWidth was updated
			// (See similar affordance in rectForLayoutAtPoint)
		if (res.size.width > limit) {
			XT_WARN_5(@"row=%ld col=%ld res.width %lf > limit %lf, totalBoundsWidth=%lf", self.startingRow, self.startingColumn, res.size.width, limit, totalBoundsWidth);
			int brkpt = 1;
		} else {
			//XT_WARN_5(@"row=%ld col=%ld res.width %lf <= limit %lf, totalBoundsWidth=%lf", self.startingRow, self.startingColumn, res.size.width,limit, totalBoundsWidth);
		}
	}

	return res;
}

- (CGFloat)totalBoundsWidth
{
	//XT_DEF_SELNAME;
	
	//TODO !!! consider value type?
	//TODO !!! consider collapsing borders
	CGFloat borderLeft = [self widthForLayer:NSTextBlockBorder edge:NSRectEdgeMinX];
	CGFloat borderRight = [self widthForLayer:NSTextBlockBorder edge:NSRectEdgeMaxX];
	CGFloat paddingLeft = [self widthForLayer:NSTextBlockPadding edge:NSRectEdgeMinX];
	CGFloat paddingRight = [self widthForLayer:NSTextBlockPadding edge:NSRectEdgeMaxX];
	CGFloat marginLeft = [self widthForLayer:NSTextBlockMargin edge:NSRectEdgeMinX];
	CGFloat marginRight = [self widthForLayer:NSTextBlockMargin edge:NSRectEdgeMaxX];
	
	CGFloat res = borderLeft + borderRight + paddingLeft + paddingRight + marginLeft + marginRight;
	//XT_WARN_3(@"row col %ld %ld --> %lf", self.startingRow, self.startingColumn, res);
	
	/*XT_WARN_8(@"row=%ld col=%ld : borderLeft=%lf borderRight=%lf paddingLeft=%lf paddingRight=%lf marginLeft=%lf marginRight=%lf",
			  self.startingRow, self.startingColumn,
			  borderLeft, borderRight,
			  paddingLeft, paddingRight,
			  marginLeft, marginRight);*/

	return res;
}

- (BOOL)isStringForEmptyCell:(NSString *)string
{
	BOOL res = NO;
	if ((string == nil) || (string.length == 0) || [string isEqualToString:@"\n"]) {
		res = YES;
	}
	return res;
}

- (CGFloat)totalBoundsWidthOnLeftHandSide
{
	//TODO !!! consider value type?
	//TODO !!! consider collapsing borders
	CGFloat borderLeft = [self widthForLayer:NSTextBlockBorder edge:NSRectEdgeMinX];
	CGFloat paddingLeft = [self widthForLayer:NSTextBlockPadding edge:NSRectEdgeMinX];
	CGFloat marginLeft = [self widthForLayer:NSTextBlockMargin edge:NSRectEdgeMinX];
	
	CGFloat res = borderLeft + paddingLeft + marginLeft;
	return res;
}

- (CGFloat)totalTableLevelBoundsWidth
{
	//TODO !!! consider collapsing borders

	XTTextTable *table = (XTTextTable *)self.table;
	CGFloat res = table.borderSize * 2.0;
		//TODO !!! margin etc.
	return res;
}

- (CGFloat)getCellSpacing
{
	//TODO !!! use from <td> if def'd?
	XTTextTable *table = (XTTextTable *)self.table;
	CGFloat res = table.cellSpacing;
	return res;
}

- (void)recalcCellMargins
{
	//XT_DEF_SELNAME;

	CGFloat cellSpacing = [self getCellSpacing];

	if (cellSpacing >= 1.0) {
		
		NSInteger tableColumnIndex = self.startingColumn;
		NSInteger tableRowIndex = self.startingRow;
		NSInteger numberOfColumns = self.table.numberOfColumns;
		NSInteger numberOfRows = ((XTTextTable *)self.table).numberOfRows;
		BOOL isFirstColumn = (tableColumnIndex == 0);
		BOOL isLastColumn = (numberOfColumns - 1 == tableColumnIndex);
		BOOL isFirstRow = (tableRowIndex == 0);
		BOOL isLastRow = (numberOfRows - 1 == tableRowIndex);
		
		//TODO !!! need ceil/floor for these? :
		CGFloat marginLeft = (isFirstColumn ? cellSpacing : cellSpacing / 2.0);
		CGFloat marginRight = (isLastColumn ? cellSpacing : cellSpacing / 2.0);
		[self setWidth:marginLeft type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSRectEdgeMinX];
		[self setWidth:marginRight type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSRectEdgeMaxX];
		
		CGFloat marginTop = (isFirstRow ? cellSpacing : cellSpacing / 2.0);
		CGFloat marginBottom = (isLastRow ? cellSpacing : cellSpacing / 2.0);
		[self setWidth:marginTop type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSRectEdgeMinY];
		[self setWidth:marginBottom type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSRectEdgeMaxY];
		
		//XT_WARN_0(@"");
	}
}

- (BOOL)allowGameToSetColors
{
	XTPrefs *prefs = [XTPrefs prefs];
	NSNumber *valueObj = prefs.allowGamesToSetColors.value;
	BOOL res = (valueObj == nil || valueObj.boolValue);
	return res;
}

@end
