//
//  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 "XTTableDimensionsTracker.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 NSNumber *heightAsPercentage;
@property NSNumber *heightAsPoints;
@property BOOL heightStar;

@property CGFloat inheritedHeadIndent;
@property CGFloat inheritedTailIndent;

@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 heightAsPercentage = _heightAsPercentage;
@synthesize heightAsPoints = _heightAsPoints;
@synthesize heightStar = _heightStar;
@synthesize inheritedHeadIndent = _inheritedHeadIndent;
@synthesize inheritedTailIndent = _inheritedTailIndent;
@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;
}

- (void)setHeightAsPercentage:(NSNumber *)heightAsPercentage
{
	_heightAsPercentage = heightAsPercentage;
}

- (NSNumber *)heightAsPercentage
{
	return _heightAsPercentage;
}

- (void)setHeightAsPoints:(NSNumber *)heightAsPoints
{
	_heightAsPoints = heightAsPoints;
}

- (NSNumber *)heightAsPoints
{
	return _heightAsPoints;
}

- (void)setHeightStar:(BOOL)heightStar
{
	_heightStar = heightStar;
}

- (BOOL)heightStar
{
	return _heightStar;
}

- (void)setInheritedHeadIndent:(CGFloat)inheritedHeadIndent
{
	_inheritedHeadIndent = round(inheritedHeadIndent);
}

- (CGFloat)inheritedHeadIndent
{
	return _inheritedHeadIndent;
}

- (void)setInheritedTailIndent:(CGFloat)inheritedTailIndent
{
	_inheritedTailIndent = round(inheritedTailIndent);
}

- (CGFloat)inheritedTailIndent
{
	return _inheritedTailIndent;
}

- (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];
}

- (void)setVerticalAlignmentMode:(XTTextVerticalAlignMode)mode
{
	NSTextBlockVerticalAlignment alignment;
	
	switch (mode) {
		case XT_TEXT_VERTICAL_ALIGN_UNSPECIFIED:
		default:
			alignment = NSTextBlockMiddleAlignment;
			break;
		case XT_TEXT_VERTICAL_ALIGN_TOP:
			alignment = NSTextBlockTopAlignment;
			break;
		case XT_TEXT_VERTICAL_ALIGN_MIDDLE:
			alignment = NSTextBlockMiddleAlignment;
			break;
		case XT_TEXT_VERTICAL_ALIGN_BOTTOM:
			alignment = NSTextBlockBottomAlignment;
			break;
	}

	[self setVerticalAlignment:alignment];
}

- (void)noteHorizRulerWidthAsPoints:(CGFloat)widthAsPoints
{
	XTTableDimensionsTracker *tracker = [XTTableDimensionsTracker tracker];

	[tracker noteHorizRulerWidthAsPoints:self.tableId
						  textTableBlock:self
						   width:widthAsPoints];
}

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

//TODO !!! refactor: too long - split up
- (NSRect)rectForLayoutAtPoint:(NSPoint)startingPoint
						inRect:(NSRect)rect
				 textContainer:(NSTextContainer *)textContainer
				characterRange:(NSRange)charRange
{
	XT_DEF_SELNAME;
	
	//NSInteger startingRow = self.startingRow;
	//NSInteger startingColumn = self.startingColumn;
	
	//XT_WARN_2(@"row=%ld col=%ld", startingRow, startingColumn);
	//XT_WARN_2(@"col=%ld rect.size.width=%lf", self.startingColumn, rect.size.width);
	//XT_WARN_2(@"col=%ld rect.size.height=%lf", self.startingColumn, rect.size.height);

	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;
	XTTableDimensionsTracker *tracker = [XTTableDimensionsTracker 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];
	
	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 !!! CAPMIN for <caption>

	CGFloat tableLevelBoundsSize = [self totalTableLevelBoundsWidth];
	
	CGFloat usableWidth = rect.size.width;
	usableWidth -= self.inheritedHeadIndent;
	usableWidth += self.inheritedTailIndent; // because it's a negative number or 0
	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.string=\"%@\"", attrStringForRange.string);

	res.size.width = contentRectWidth;
	
	CGFloat originXAdjustedForTableLevelBounds = rect.origin.x + floor(tableLevelBoundsSize / 2.0);
	originXAdjustedForTableLevelBounds += self.inheritedHeadIndent;
	//CGFloat oldX = res.origin.x;
	res.origin.x = [tracker contentRectXForTable:self.tableId
								  textTableBlock:self
										 originX:originXAdjustedForTableLevelBounds
									 usableWidth:usableWidth];
	
	// Calc. cell height
	//----------------------------------------------------
	
	XTTextView *textView = (XTTextView *)textContainer.textView;
	CGFloat visibleHeight = [textView findVisibleHeightOfScrollView];
	NSSize contentRectSize = NSMakeSize(res.size.width, rect.size.height);

	[self recalcAllRowInfoForTextStorage:textStorage visibleHeight:visibleHeight];

	//TODO !!! adapt: needed now? use cached instead?
	CGFloat contentRectHeight = [self calcContentHeightForTextTableBlock:self
															  attrString:attrStringForRange
														   visibleHeight:visibleHeight
														 contentRectSize:contentRectSize];
	
	res.size.height = contentRectHeight;
	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 + 2.0;
		// 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);

	[self setValue:res.size.height type:NSTextBlockAbsoluteValueType forDimension:NSTextBlockHeight];
	//[self setValue:res.size.height type:NSTextBlockAbsoluteValueType forDimension:NSTextBlockMinimumHeight];

	/*
	XT_WARN_6(@"row=%ld col=%ld -> res.x=%lf res.y=%lf res.width=%lf res.height=%lf",
			  self.startingRow, self.startingColumn,
			  res.origin.x, res.origin.y,
			  res.size.width, res.size.height);
	*/
	//XT_WARN_3(@"row=%ld col=%ld -> res.height=%lf",
	//		  self.startingRow, self.startingColumn, res.size.height);
	
	return res;
}

- (CGFloat)calcContentHeightForTextTableBlock:(XTTextTableBlock *)textTableBlock
								   attrString:(NSAttributedString *)attrString
								visibleHeight:(CGFloat)visibleHeight
							  contentRectSize:(NSSize)contentRectSize
{
	XT_DEF_SELNAME;

	NSSize requiredContentRectSize;
	if ([self isStringForEmptyCell:attrString.string]) {
		requiredContentRectSize = NSMakeSize(1.0, 1.0);
	} else {
		requiredContentRectSize = [XTFontUtils requiredRectForText:attrString
													 forViewOfSize:contentRectSize
										suppressCenterAndRightTabs:YES];
	}
	
	CGFloat minContentRectHeight = requiredContentRectSize.height;
	NSUInteger numberOfNewlinesTrailingNewlines = [XTStringUtils numberOfTrailingNewlinesIn:attrString.string];
	if (numberOfNewlinesTrailingNewlines >= 2) {
		minContentRectHeight += 1.0; // This is required to avoid looping the OS layout engine
	}
	
	XTTableDimensionsTracker *tracker = [XTTableDimensionsTracker tracker];
	[tracker noteMinContentRectHeightForTable:textTableBlock.tableId
							  textTableBlock:textTableBlock
									   height:minContentRectHeight];

	CGFloat contentRectHeight = [tracker contentRectHeightForTable:textTableBlock.tableId textTableBlock:textTableBlock visibleHeight:visibleHeight];
	if (contentRectHeight < 0.0) {
		XT_WARN_3(@"row=%ld col=%ld : contentRectHeight %lf < 0.0", textTableBlock.startingRow, textTableBlock.startingColumn, contentRectHeight);
	}
		
	return contentRectHeight;
}

- (void)recalcAllRowInfoForTextStorage:(NSTextStorage *)textStorage
						 visibleHeight:(CGFloat)visibleHeight
{
	//XT_DEF_SELNAME
	//XT_WARN_0(@"");
	
	XTTableDimensionsTracker *tracker = [XTTableDimensionsTracker tracker];
	[tracker clearForRecalcOfTables];

	XTTextTable *table = (XTTextTable *)self.table;
	NSArray *textTableBlocks = table.textTableBlocks;
	
	//NSRange textStorageRange = NSMakeRange(0, textStorage.length);
	NSUInteger textStorageEndLoc = textStorage.length;

	for (XTTextTableBlock *block in textTableBlocks) {
		//if (block != self) {
		NSRange range = block.rangeForLatestContentRect;
		
		NSUInteger rangeEndLoc = range.location + range.length;
		if (rangeEndLoc > textStorageEndLoc) {
			continue;
		}
		
		CGFloat width;
		CGFloat minColumnWidth = [tracker minContentRectWidthForTable:table.tableId textTableBlock:self];
		if (block.latestContentRect != nil) {
			width = block.latestContentRect.rect.size.width;
			if (width < minColumnWidth) {
				width = minColumnWidth;
			}
		} else {
			//TODO !!! very exp
			width = 100000.0;
		}
		NSSize contentRectSize = NSMakeSize(width, 100000.0);
		NSAttributedString *attrString = [textStorage attributedSubstringFromRange:range];
		[self calcContentHeightForTextTableBlock:block
									  attrString:attrString
								   visibleHeight:visibleHeight
								 contentRectSize:contentRectSize];
		//}
	}
	//XT_WARN_0(@"exit");
}

- (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);

	XTTableDimensionsTracker *tracker = [XTTableDimensionsTracker tracker];

	NSRect res = [super boundsRectForContentRect:contentRect inRect:rect textContainer:textContainer characterRange:charRange];
	
	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);

	CGFloat totalBoundsHeight = [self totalBoundsHeight];
	//CGFloat origHeight = res.size.height;
	//TODO !!! exp rm: res.size.height = usedContentRect.size.height /*contentRect.size.height*/ + totalBoundsHeight;

	[tracker noteTotalBoundsForTable:self.tableId
					  textTableBlock:self
							   width:totalBoundsWidth /*WithCollapsingBorders*/
							  height:totalBoundsHeight];
	
	[tracker noteBoundsRectForTable:self.tableId
					 textTableBlock:self
							  width:res.size.width
							 height:res.size.height];

	/*
	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 &&
		self.latestContentRect.rect.origin.y == contentRect.origin.y &&
		self.latestContentRect.rect.size.height == contentRect.size.height) {
		//
		// 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:)
		//
		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);
		} 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);
		} 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;
}

- (CGFloat)totalBoundsHeight
{
	//XT_DEF_SELNAME;
	
	//TODO !!! consider value type?
	//TODO !!! consider collapsing borders
	CGFloat borderTop = [self widthForLayer:NSTextBlockBorder edge:NSRectEdgeMinY];
	CGFloat borderBottom = [self widthForLayer:NSTextBlockBorder edge:NSRectEdgeMaxY];
	CGFloat paddingTop = [self widthForLayer:NSTextBlockPadding edge:NSRectEdgeMinY];
	CGFloat paddingBottom = [self widthForLayer:NSTextBlockPadding edge:NSRectEdgeMaxY];
	CGFloat marginTop = [self widthForLayer:NSTextBlockMargin edge:NSRectEdgeMinY];
	CGFloat marginBottom = [self widthForLayer:NSTextBlockMargin edge:NSRectEdgeMaxY];
	
	CGFloat res = borderTop + borderBottom + paddingTop + paddingBottom + marginTop + marginBottom;

	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
