//
//  XTTableInfo.m
//  XTads
//
//  Created by Rune Berg on 30/10/2018.
//  Copyright © 2018 Rune Berg. All rights reserved.
//

#import "XTTableInfo.h"
#import "XTTableColumnInfo.h"
#import "XTTableRowInfo.h"
#import "XTTextTable.h"
#import "XTTextTableBlock.h"
#import "XTTextTableBlockCaption.h"
#import "XTLogger.h"
#import "XTAllocDeallocCounter.h"


@interface XTTableInfo ()

@property NSMutableDictionary<NSNumber*, XTTableColumnInfo*> *columnInfoByColumn;
@property NSMutableDictionary<NSNumber*, XTTableRowInfo*> *rowInfoByRow;
@property CGFloat columnWidthRemainder;
@property CGFloat minCaptionContentRectWidth;

@end


@implementation XTTableInfo

static XTLogger *logger;

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

OVERRIDE_ALLOC_FOR_COUNTER
OVERRIDE_DEALLOC_FOR_COUNTER

- (instancetype)init
{
	self = [super init];
	if (self) {
		_columnInfoByColumn = [NSMutableDictionary dictionaryWithCapacity:10];
		_rowInfoByRow = [NSMutableDictionary dictionaryWithCapacity:10];
		_columnWidthRemainder = 0.0;
		_minCaptionContentRectWidth = 0.0;
	}
	return self;
}

- (void)noteMaxContentRectWidth:(CGFloat)width
					  forColumn:(NSInteger)column
{
	XTTableColumnInfo *columnInfo = [self getOrCreateColumnInfo:column];

	NSNumber *maxWidthObj = columnInfo.maxContentRectWidth;
	if (maxWidthObj == nil || width > maxWidthObj.doubleValue) {
		//XT_DEF_SELNAME;
		//XT_WARN_2(@"new maxWidth %lf for col. %d", width, column);
		columnInfo.maxContentRectWidth = [NSNumber numberWithDouble:width];
	}
}

- (void)noteMinContentRectWidth:(CGFloat)width
					  forColumn:(NSInteger)column
						 forRow:(NSInteger)row
{
	//XT_DEF_SELNAME;

	XTTableColumnInfo *columnInfo = [self getOrCreateColumnInfo:column];
	
	NSNumber *minWidthObj = columnInfo.minContentRectWidth;
	if (minWidthObj == nil || width > minWidthObj.doubleValue) { // it's the "max min" value we're tracking here
		//XT_WARN_2(@"new minWidth %lf for col. %d", width, column);
		columnInfo.minContentRectWidth = [NSNumber numberWithDouble:width];
	}
	
	if (minWidthObj != nil && width != minWidthObj.doubleValue) {
		XTTableRowInfo *rowInfo = [self getOrCreateRowInfo:row];
		rowInfo.minContentRectHeight = nil;
		//XT_WARN_3(@"reset rowInfo for row %lu (width %lf != minWidthObj %lf)", row, width, minWidthObj.doubleValue);
	}
}

- (CGFloat)minContentRectWidthForColumn:(NSInteger)column
{
	XTTableColumnInfo *columnInfo = [self getColumnInfo:column];
	CGFloat res = [columnInfo getComputedMinContentRectWidth];
	return res;
}

- (void)noteMinContentRectHeight:(CGFloat)height
						  forRow:(NSInteger)row
{
	XTTableRowInfo *rowInfo = [self getOrCreateRowInfo:row];
	
	NSNumber *minHeightObj = rowInfo.minContentRectHeight;
	if (minHeightObj == nil || height > minHeightObj.doubleValue) { // it's the "max min" value we're tracking here
		rowInfo.minContentRectHeight = [NSNumber numberWithDouble:height];
	}
}

- (void)noteTotalBoundsForTextTableBlock:(XTTextTableBlock *)textTableBlock
								   width:(CGFloat)width
								  height:(CGFloat)height
{
	//XT_DEF_SELNAME;
	
	NSInteger column = textTableBlock.startingColumn;
	XTTableColumnInfo *columnInfo = [self getOrCreateColumnInfo:column];
	columnInfo.totalBoundsWidth = [NSNumber numberWithDouble:width];
	
	NSInteger row = textTableBlock.startingRow;
	XTTableRowInfo *rowInfo = [self getOrCreateRowInfo:row];
	rowInfo.totalBoundsHeight = [NSNumber numberWithDouble:height];

	//XT_WARN_2(@"col %ld : %ld", column, (NSInteger)width);
}

- (void)noteBoundsRectForTextTableBlock:(XTTextTableBlock *)textTableBlock
								  width:(CGFloat)width
								 height:(CGFloat)height
{
	//XT_DEF_SELNAME;
	
	NSInteger column = textTableBlock.startingColumn;
	XTTableColumnInfo *columnInfo = [self getOrCreateColumnInfo:column];
	columnInfo.boundsRectWidth = [NSNumber numberWithDouble:width];
	
	NSInteger row = textTableBlock.startingRow;
	XTTableRowInfo *rowInfo = [self getOrCreateRowInfo:row];
	rowInfo.boundsRectHeight = [NSNumber numberWithDouble:height];

	//XT_WARN_2(@"col %ld : %ld", column, (NSInteger)width);
}

- (void)noteHorizRulerWidthAsPoints:(CGFloat)width
						  forColumn:(NSInteger)column
{
	XTTableColumnInfo *columnInfo = [self getOrCreateColumnInfo:column];

	NSNumber *maxWidthObj = columnInfo.maxHorizRulerWidthAsPoints;
	if (maxWidthObj == nil || width > maxWidthObj.doubleValue) {
		columnInfo.maxHorizRulerWidthAsPoints = [NSNumber numberWithDouble:width];
	}
}

- (void)noteMinCaptionContentRectWidth:(CGFloat)width
{
	self.minCaptionContentRectWidth = width;
}

- (CGFloat)contectRectWidthForTextTableBlock:(XTTextTableBlock *)textTableBlock
								 usableWidth:(CGFloat)usableWidth
{
	//XT_DEF_SELNAME;

	NSInteger column = textTableBlock.startingColumn;
	NSInteger row = textTableBlock.startingRow;
	//XT_WARN_3(@"col=%ld row=%ld usableWidth=%lf", column, row, usableWidth);

	XTTextTable *table = (XTTextTable *)textTableBlock.table;
	
	CGFloat totalContentWidthOfTable = [self calcTotalContentWidthOfTable2:table usableWidth:usableWidth];
	if (totalContentWidthOfTable < self.minCaptionContentRectWidth) {
		totalContentWidthOfTable = self.minCaptionContentRectWidth;
	}
	
	// Recalc all columns widths, so that x pos calcs can be done right:
	NSUInteger columnCount = self.columnInfoByColumn.count;
	CGFloat contectRectWidth;
	for (NSUInteger iterColumn = 0; iterColumn < columnCount; iterColumn++) {
		CGFloat tempContectRectWidth = [self contectRectWidthForTextTable2:table
																	   row:row
																	column:iterColumn
												  totalContentWidthOfTable:totalContentWidthOfTable
															   usableWidth:usableWidth];
		if (iterColumn == column) {
			contectRectWidth = tempContectRectWidth;
		}
	}

	return contectRectWidth;
}

- (CGFloat)contectRectWidthForTextTableBlockCaption:(XTTextTableBlockCaption *)textTableBlock
									  usableWidth:(CGFloat)usableWidth
{
	XTTextTable *table = (XTTextTable *)textTableBlock.table;

	CGFloat totalContentWidthOfTable = [self calcTotalContentWidthOfTable2:table usableWidth:usableWidth];
	
	CGFloat totalWidthOfInnerBounds = [self totalWidthOfInnerBounds];
	
	CGFloat res = totalContentWidthOfTable + totalWidthOfInnerBounds;
	return res;
}

/*
 Figuring the table's height. Once the column widths are chosen, the renderer performs word-wrapping on the contents of each cell to fit the contents to the final size of the cell's column. This process determines the minimum height of a cell, which is the height required to hold the word-wrapped contents of the cell at its final width. The minimum height of a row is the largest of the minimum heights of the row's cells.

 If a cell or a row has a HEIGHT attribute, then we'll use the larger of the minimum height and the explicit HEIGHT attribute value.

 If the <TABLE> tag has a HEIGHT attribute, and the height is larger than the sum of the minimum heights of the rows, then the table's height is the explicitly given HEIGHT; otherwise, the table's height is the sum of the minimum row heights. If the explicit HEIGHT is larger, then we distribute the extra vertical space (the difference between the explicit HEIGHT and the actual minimum height of the rows) among the rows as follows:

	 If there are any rows (<TR> tags) with "HEIGHT=*" settings, then we distribute the extra space evenly among the "HEIGHT=*" rows.
	 Otherwise, we distribute the space to the rows in proportion to their minimum heights. (For example, if there are two rows, and the first row's minimum height is twice that of the second row, then we distribute 2/3 of the extra vertical space to the first row and 1/3 to the second row.)

 If a cell has a ROWSPAN greater than 1, we distribute its height among the rows it spans in proportion to the heights of the rows as they would be if we considered only the cells with ROWSPAN=1. The detailed algorithm is almost exactly the same as the one described above for multi-column (COLSPAN) cells.
 */
- (CGFloat)contectRectHeightForTextTableBlock:(XTTextTableBlock *)textTableBlock
								visibleHeight:(CGFloat)visibleHeight

{
	XT_DEF_SELNAME;

	NSInteger row = textTableBlock.startingRow;
	//XT_WARN_2(@"row=%ld visibleHeight=%lf", row, visibleHeight);

	XTTextTable *table = (XTTextTable *)textTableBlock.table;
	CGFloat specdTableHeight = [self getSpecdTableHeightFor:textTableBlock visibleHeight:visibleHeight];

	NSMutableArray *allTextTableBlocks = table.textTableBlocks;

	//TODO !!! refactor: make sep method:
	NSMutableDictionary<NSNumber *, NSNumber *> *maxRowHeightAsPercentageByRow = [NSMutableDictionary dictionary];
	NSMutableSet<NSNumber *> *rowsWithHeightStar = [NSMutableSet set];
	for (XTTextTableBlock *textTableBlock in allTextTableBlocks) {
		NSInteger row = textTableBlock.startingRow;
		NSNumber *rowObj = [NSNumber numberWithInteger:row];
		CGFloat heightAsPercentage = textTableBlock.heightAsPercentage.doubleValue;
		if (heightAsPercentage > maxRowHeightAsPercentageByRow[rowObj].doubleValue) {
			maxRowHeightAsPercentageByRow[rowObj] = [NSNumber numberWithDouble:heightAsPercentage];
		}
		if (textTableBlock.heightStar) {
			[rowsWithHeightStar addObject:rowObj];
		}
	}
	CGFloat sumOfRowHeightsAsPercentage = 0.0;
	for (NSNumber *num in maxRowHeightAsPercentageByRow.allValues) {
		sumOfRowHeightsAsPercentage += num.doubleValue;
	}
	CGFloat heightsAsPercentageScaleFactor = 1.0;
	if (sumOfRowHeightsAsPercentage > 100.0) {
		heightsAsPercentageScaleFactor = 100.0 / sumOfRowHeightsAsPercentage;
	}

	NSMutableDictionary<NSNumber *, NSNumber *> *minHeightByRow = [NSMutableDictionary dictionary];
	for (XTTextTableBlock *textTableBlock in allTextTableBlocks) {
		NSInteger row = textTableBlock.startingRow;
		NSNumber *rowObj = [NSNumber numberWithInteger:row];
		XTTableRowInfo *rowInfo = self.rowInfoByRow[rowObj];
		CGFloat minHeight = rowInfo.minContentRectHeight.doubleValue;
		if (textTableBlock.heightAsPoints.doubleValue > minHeight) {
			minHeight = textTableBlock.heightAsPoints.doubleValue;
		}
		if (minHeight > minHeightByRow[rowObj].doubleValue) {
			minHeightByRow[rowObj] = [NSNumber numberWithDouble:minHeight];
		}
		if (textTableBlock.heightAsPercentage.doubleValue > 0.0) {
			CGFloat pct = textTableBlock.heightAsPercentage.doubleValue;
			CGFloat totalBpundsHeight = [self totalHeightOfBounds];
			CGFloat heightToDistr = specdTableHeight - totalBpundsHeight;
			CGFloat tempMinHeight = heightToDistr * (pct / 100.0) * heightsAsPercentageScaleFactor;
			tempMinHeight = floor(tempMinHeight);
			if (tempMinHeight > minHeightByRow[rowObj].doubleValue) {
				minHeightByRow[rowObj] = [NSNumber numberWithDouble:tempMinHeight];
			}
		}
	}
	
	CGFloat sumOfMinRowHeights = 0.0;
	for (NSNumber *value in minHeightByRow.allValues) {
		sumOfMinRowHeights += value.doubleValue;
	}

	//NSInteger row = textTableBlock.startingRow;
	NSNumber *rowObj = [NSNumber numberWithInteger:row];
	NSNumber *resObj = minHeightByRow[rowObj];
	CGFloat res = resObj.doubleValue;

	CGFloat extraHeightToDistribute = 0.0;
	if (specdTableHeight > sumOfMinRowHeights) {
		CGFloat totalBounds = [self totalHeightOfBounds];
		extraHeightToDistribute = specdTableHeight - sumOfMinRowHeights - totalBounds;
		if (extraHeightToDistribute < 0.0) {
			extraHeightToDistribute = 0.0;
		}
	}
	//XT_WARN_1(@"extraHeightToDistribute=%lf", extraHeightToDistribute);

	if (res > 1.0) {
		if (extraHeightToDistribute > 0.0) {
			if (rowsWithHeightStar.count >= 1) {
				if (textTableBlock.heightStar) {
					CGFloat fraction = 1.0 / ((CGFloat)rowsWithHeightStar.count);
					CGFloat toAdd = floor(extraHeightToDistribute * fraction);
					res += toAdd;
				}
			} else {
				CGFloat fraction = res / sumOfMinRowHeights;
				CGFloat toAdd = floor(extraHeightToDistribute * fraction);
				res += toAdd;
			}
		}
	}
	//XT_WARN_2(@"row=%ld res=%lf", row, res);
	
	return res;
}

//TODO !!! mv:
- (CGFloat)getSpecdTableHeightFor:(XTTextTableBlock *)textTableBlock
					visibleHeight:(CGFloat)visibleHeight
{
	XTTextTable *textTable = (XTTextTable *)textTableBlock.table;
	CGFloat res = 0;
	if (textTable.heightAsPoints != nil) {
		res = textTable.heightAsPoints.doubleValue;
		if (res > visibleHeight) {
			res = visibleHeight;
		}
	} else if (textTable.heightAsPercentage != nil) {
		NSUInteger heightAsPercentage = textTable.heightAsPercentage.unsignedIntegerValue;
		if (heightAsPercentage > 100) {
			heightAsPercentage = 100;
		}
		res = visibleHeight * ((CGFloat)heightAsPercentage / 100.0);
	}
	return res;
}

/*
 Figuring the table's width. The first step in laying out a table is determining the overall width of the table. The rules for choosing the width are:

 Above all else, the table must be at least as wide as the sum of the minimum widths of all of its columns. This means that a table must be large enough to accomodate all of its columns without any clipping or overlapping. If any other rule would choose a table size below this minimum, we always override that decision and use this minimum table size instead.

 If the <TABLE> tag specifies a WIDTH attribute, then we use that value (except that the minimum table size above overrides this if the WIDTH value is below the minimum).

 In the absence of a WIDTH attribute, the largest we'll make the table is the full available space in the display window (except, as always, that we'll never go below the minimum table size). We set this upper limit so that the table fits in the window without any horizontal scrolling, since horizontal scrolling would make the table much harder to read. (It might seem that we're just replacing horizontal scrolling with vertical scrolling, but in many cases a table won't become tall enough to need vertical scrolling even after being forced to fit the window's width. Even when a table is very tall, vertical scrolling is preferable to horizontal for most users, since text games tend to display enough sequential information that vertical scrolling occurs anyway.)

 If any column has an explicit pixel width specified, we'll try to make the table big enough to accomodate that pixel width.

 For any column that doesn't have any explicit width specified, we'll try to make the table big enough to accomodate the maximum width of the column.

 If any column has a percentage width specified, we'll try to make the table big enough that, at the column's maximum width, the column would have the requested percentage of the total table width. (For example, if a column specifies a width of 10%, we try to make sure that the table is big enough that the column would only take up 10% of the total table size if the column were at its maximum size.)

 If some columns have percentage widths and some don't, we try to make the table big enough that the non-percentage columns would only take up the left-over percentage at their maximum sizes. (For example, if we have three columns in the table, and the first two specify widths of 20% and 50% respectively, we try to make sure that the table is big enough that the third column, at its maximum width, would take up only 30% of the total table width.)
 */
/*
 *   Calculate the table width based on the size constraints of the
 *   columns.
 
 *   width_min and width_max are the minimum and maximum sizes calculated for the contents, and
 *   win_width is the available space in the window.
 */
// based on CHtmlTagTABLE::calc_table_width method from HTML TADS - most of the comments are from that method
- (CGFloat)calcTotalContentWidthOfTable2:(XTTextTable *)table usableWidth:(CGFloat)usableWidth
{
	//XT_DEF_SELNAME;
	//XT_WARN_1(@"=== entry usableWidth=%lf", usableWidth);
	
	CGFloat sumOfMinimumCellWidths = [self totalMinColumnWidthExcludingBounds];
	CGFloat totalWidthOfBounds = [self totalWidthOfBounds];
	CGFloat usableWidthMinusBounds = usableWidth - totalWidthOfBounds;
	
	if (table.widthAsPoints != nil) {
		CGFloat widthTotal = (CGFloat)table.widthAsPoints.integerValue;
		if (sumOfMinimumCellWidths + totalWidthOfBounds > widthTotal) {
			widthTotal = sumOfMinimumCellWidths + totalWidthOfBounds;
		}
		if (widthTotal > usableWidth) {
			widthTotal = usableWidth;
		}

		CGFloat res = widthTotal - totalWidthOfBounds;
		res = floor(res);
		//XT_WARN_1(@"widthAsPoints --> ", res);
		return res;
	}
		
	if (table.widthAsPercentage != nil) {
		//TODO !!! can we handle % case as simply as pts case? if so, make this block a sep meth
		CGFloat widthAsPercentage = (CGFloat)table.widthAsPercentage.integerValue;
		CGFloat usableWidthScaledToTableWidthAsPercentage = (usableWidth * widthAsPercentage) / 100.0;
		
		CGFloat widthTotal = usableWidthScaledToTableWidthAsPercentage;
		if (sumOfMinimumCellWidths + totalWidthOfBounds > widthTotal) {
			widthTotal = sumOfMinimumCellWidths + totalWidthOfBounds;
		}
		if (widthTotal > usableWidth) {
			widthTotal = usableWidth;
		}
		CGFloat res = widthTotal - totalWidthOfBounds;
		res = floor(res);
		//XT_WARN_3(@"sumOfMinimumCellWidths=%lf totalWidthOfBounds=%lf --> %lf", sumOfMinimumCellWidths, totalWidthOfBounds, res);
		return res;
	}

    long tot_pct = 0;
    long non_pct_max = 0;
    long content_max;

    /*
     *   If our minimum width doesn't fit the available window width, then
     *   simply use our minimum width; this will require horizontal
     *   scrolling, but this is the smallest we can go.
	 *   *** NB! XTads *never* does horiz scrolling ***
     */
	if (sumOfMinimumCellWidths + totalWidthOfBounds >= usableWidth) {
		return usableWidthMinusBounds;
	}

    /*
     *   calculate the maximum width of the content part (excluding
     *   decorations)
     */
	CGFloat sumOfMaximumCellWidths = [self totalMaxColumnWidthExcludingBounds];
	if (sumOfMaximumCellWidths < sumOfMinimumCellWidths) {
		sumOfMaximumCellWidths = sumOfMinimumCellWidths;
	}
	long width_max = sumOfMaximumCellWidths;
	//XT_WARN_1(@"width_max init'd to %lu", width_max);
	content_max = sumOfMaximumCellWidths;
	NSMutableDictionary<NSNumber *, XTTextTableBlock *>*textTableBlocksDefiningColumnWidthsByIndex = [self getTextTableBlocksDefiningColumnWidths:table];
	NSUInteger columnCount = self.columnInfoByColumn.count;

    /*
     *   Our minimum size fits the window, so we can expand beyond our
     *   minimum content size.  In the simplest case, we can just check to
     *   see if the maximum fits the window, and top out there if so.  If the
     *   maximum doesn't fit, top out at the window size instead; this will
     *   squeeze the table into a smaller space than it would fill if left to
     *   its own devices, but will minimize horizontal scrolling, which is
     *   more important than maximizing the width of the table.
     *
     *   If we have any percentage columns, we need to do some extra work.
     *   For each percentage-based column, we need to check the minimum table
     *   size that would be necessary for the column to have its desired
     *   percentage at its maximum content size.  In addition, we must check
     *   how much space would be needed so that the columns *without*
     *   percentage widths would take the percentage of space left over after
     *   accounting for all of the percentage columns.
     *
     *   Run through each column and check for percentage columns.
     */
	for (NSUInteger columnIdx = 0; columnIdx < columnCount; columnIdx++) {
		//XT_WARN_1(@"-- begin columnIdx %lu", columnIdx);
		XTTableColumnInfo *columnInfo = [self getColumnInfo:columnIdx];
		NSNumber *columnIdxObj = [NSNumber numberWithUnsignedInteger:columnIdx];
        long cur_pct;

        /* get this column descriptor */
		XTTextTableBlock *textTableBlock = textTableBlocksDefiningColumnWidthsByIndex[columnIdxObj];

        /* if it has a percentage width, figure its needs */
		if ((cur_pct = textTableBlock.widthAsPercentage.integerValue) != 0)
        {
            /*
             *   if this column would take us over 100%, limit it so that the
             *   total doesn't exceed 100%
             */
            if (tot_pct + cur_pct > 100)
            {
                /* limit the sum of all requests to 100% */
                cur_pct = 100 - tot_pct;

                /*
                 *   if this leaves us with 0% or less, we've overcommitted;
                 *   simply use the maximum available window width
                 */
                if (cur_pct <= 0)
					return usableWidthMinusBounds;
            }

            /* add this percentage into the total */
            tot_pct += cur_pct;
			//XT_WARN_2(@"%% tot_pct += cur_pct %ld -> %ld", cur_pct, tot_pct);

            /*
             *   Calculate how big the table has to be to accommodate our
             *   percentage request.  Don't bother if we're asking for 100%,
             *   as this will either overcommit the table (if there are any
             *   other non-empty columns) or provide no information (if we're
             *   the only column); if it overcommits the table, we'll catch
             *   this naturally in the rest of the algorithm, so no need for
             *   a special check here.
             */
            if (cur_pct < 100)
            {
				//XT_WARN_1(@"%% cur_pct %ld < 100 {", cur_pct);
                long implied_max;

                /*
                 *   calculate the implied size of the table; round up, since
                 *   we'll round down when calculating percentage during
                 *   column width layout
                 */
				implied_max = ([columnInfo getComputedMaxContentRectWidth] * 100 + (cur_pct - 1))
                              / cur_pct;
				//XT_WARN_1(@"%% implied_max = %ld", implied_max);

                /*
                 *   if this is greater than the maximum table size, increase
                 *   the required maximum table size to accommodate
                 */
                if (implied_max > content_max)
                {
                    /* increase the table size */
					//XT_WARN_2(@"implied_max %ld > content_max %ld {", implied_max, content_max);
                    width_max += implied_max - content_max;
					//XT_WARN_1(@"width_max updated to %lu 1", width_max);
					//XT_WARN_3(@"%% width_max += implied_max %ld - content_max %ld -> width_max", implied_max, content_max, width_max);
                    content_max = implied_max;
					//XT_WARN_1(@"%% content_max = implied_max %ld", content_max);
					//XT_WARN_0(@"}");
                }
				//XT_WARN_0(@"}");
            }
        }
        else
        {
            /*
             *   this column doesn't have a percentage width, so add its
             *   maximum width to the sum of the non-percentage column
             *   widths; we'll use this later to make sure the part of the
             *   table without percentage widths adds up to the leftover
             *   percentage after counting all of the percentage-width
             *   columns
             */
			non_pct_max += [columnInfo getComputedMaxContentRectWidth];
			//XT_WARN_2(@"non_pct_max += columnInfo.maxContentRectWidth %ld -> %ld", columnInfo.maxContentRectWidth.unsignedIntegerValue, non_pct_max);
        }
		//XT_WARN_1(@"-- end columnIdx %lu", columnIdx);
    }

    /*
     *   If the total of the non-percentage columns is non-zero, and we've
     *   committed all of the space with percentage columns already, then we
     *   can't possibly satisfy the constraints, so simply make the table as
     *   wide as the available window space.
     */
	if (non_pct_max != 0 && tot_pct >= 100) {
		return usableWidthMinusBounds;
	}

    /*
     *   If the total of the non-percentage columns is between 0 and 100,
     *   calculate the table width that we'd need to accommodate the
     *   non-percentage columns at the left-over percentage.  This will
     *   ensure that we have space for all of the non-percentage columns at
     *   their maximum sizes, and can still have all of the percentage
     *   columns at their correct percentages.
     */
    if (non_pct_max != 0 && tot_pct != 0)
    {
		//XT_WARN_2(@"non_pct_max %kd != 0 && tot_pct %ld != 0 {", non_pct_max, tot_pct);
        long implied_max;

        /*
         *   calculate the implied table width so that the non-percentage
         *   columns take up exactly the left-over percentage
         */
        implied_max = (non_pct_max*100 + (100 - tot_pct - 1))
                      / (100 - tot_pct);
		//XT_WARN_1(@"implied_max = %ld", implied_max);

        /*
         *   if this is greater than the maximum table size, increase the
         *   required maximum table size
         */
        if (implied_max > content_max)
        {
			//XT_WARN_2(@"implied_max %ld > content_max %ld {", implied_max, content_max);
            /* increase the table size */
            width_max += implied_max - content_max;
			//XT_WARN_1(@"width_max updated to %lu 2", width_max);
			//XT_WARN_3(@"width_max += implied_max %ld - content_max %ld -> %ld", implied_max, content_max, width_max);
            content_max = implied_max;
			//XT_WARN_1(@"content_max = implied_max %ld", implied_max);
			//XT_WARN_0(@"}");
        }
		//XT_WARN_0(@"}");
    }

    /*
     *   If our maximum size fits the window, top out at the maximum size.
     *   Otherwise, limit our size to the available window space.
     */
	if (width_max < usableWidthMinusBounds) {
		//XT_WARN_3(@"width_max %ld < sumOfMinimumCellWidths %lf --> width_max %ld", width_max, sumOfMinimumCellWidths, width_max);
        return width_max;
	} else {
		//XT_WARN_3(@"width_max %ld >= sumOfMinimumCellWidths %lf --> sumOfMinimumCellWidths %lf", width_max, sumOfMinimumCellWidths, sumOfMinimumCellWidths);
		return usableWidthMinusBounds;
	}
}

// See http://www.tads.org/t3doc/doc/htmltads/tables.htm (HTML TADS Table Layout Rules)
//TODO !!! refactor - way too long
- (CGFloat)contectRectWidthForTextTable2:(XTTextTable *)table
									 row:(NSInteger)row
								  column:(NSInteger)column
				totalContentWidthOfTable:(CGFloat)totalContentWidthOfTable
							 usableWidth:(CGFloat)usableWidth
{
	XT_DEF_SELNAME;
	
	/*TODO !!! rm
	 Determining the column widths. Once we know the overall width of the table, we figure the widths of the individual columns. Since we choose the overall table width before determining the column widths, our job at this point is to determine how the table width is divided among the columns. We use the following rules, in order of priority:

		 The first overriding requirement is that the sum of all of the column widths equals the table width. That is, a table's columns must exactly fill the table's overall width. Columns cannot overlap.
		 The second overriding requirement is that a column can never be smaller than its minimum width. (Note that these first two column constraints could contradict one another if it weren't for the table constraint that says that a table can never be narrow than the sum of the minimum widths of its columns. That table constraint guarantees that we'll always be able to satisfy both of these column constraints.)
	 
		 If any columns specify percentage widths, we try to accomodate the requests. If this pushes us over the table size, then we proportionally reduce the widths of the percentage columns until they fit the table size (but we never reduce a column below its minimum width, of course).
	 
		 If there's any more space after satisfying percentage width requests, we try to satisfy pixel width requests. If there's enough space to satisfy all pixel width requests, we set each pixel-width column to its exact requested width; otherwise, we distribute the remaining space among the pixel-width columns in proportion to the requested widths.
	 
		 If there's still any extra space after satisfying percentage and column width requests, we distribute the extra space among the columns. If there are any columns without any width requests at all, we distribute all of the extra space to those columns; otherwise, we distribute the space to all columns. We distribute space as follows:
			 We first distribute space to eligible columns that are below their maximum column widths. We distribute this space proportionally to the difference between maximum and minimum widths for these columns. (This odd basis was chosen because it causes the columns to expand out to their maximum widths evenly as the table width increases.)
			 Once all eligible columns are at their maximum widths, we distribute any remaining space to the eligible columns in proportion to their maximum widths. (This keeps the amount of whitespace in each column balanced with the amount of content in that column.)

	 Note that percentage width requests have higher priority than pixel widths. This means we'll satisfy any percentage requests (as closely as possible) before sastifying any pixel width requests; so, if there's not room to satisfy both kinds of requests, any pixel-width columns will be set to their minimum size to make as much room as possible for percentage-width columns.

	 When the table is so wide that there's extra space to distribute beyond explicit WIDTH requests, note that we preferentially distribute the space to unconstrained columns (columns without any WIDTH requests). We prefer to distribute space to unconstrained columns because this allows columns with explicit WIDTH requests to stay at the exact size requested.
	 */
	
	//XT_WARN_3(@"*** ENTRY column=%ld totalContentWidthOfTable=%lf usableWidth=%lf", column, totalContentWidthOfTable, usableWidth);
	
	NSMutableDictionary<NSNumber *, XTTextTableBlock *>*textTableBlocksDefiningColumnWidthsByIndex = [self getTextTableBlocksDefiningColumnWidths:table];
	NSMutableDictionary<NSNumber *, NSNumber *>*columnWidthByIndex = [NSMutableDictionary dictionaryWithCapacity:textTableBlocksDefiningColumnWidthsByIndex.count + 10];
	CGFloat totalContentWidthUsed = 0.0;
	
	// Handle #width=...%
	//--------------------
	
	NSUInteger sumOfWidthPercentages = 0;
	for (XTTextTableBlock *textTableBlock in textTableBlocksDefiningColumnWidthsByIndex.allValues) {
		if (textTableBlock.widthAsPercentage != nil) {
			sumOfWidthPercentages += textTableBlock.widthAsPercentage.unsignedIntegerValue;
		}
	}
	CGFloat percentageScaledownFactor = 1.0;
	if (sumOfWidthPercentages > 100) {
		percentageScaledownFactor = 100.0 / (CGFloat)sumOfWidthPercentages;
	}
	//XT_WARN_2(@"sumOfWidthPercentages=%lu percentageScaledownFactor=%lf", sumOfWidthPercentages, percentageScaledownFactor);
	NSUInteger sumOfWidthPercentages2 = 0;
	NSArray *sortedKeys = [textTableBlocksDefiningColumnWidthsByIndex.allKeys sortedArrayUsingSelector: @selector(compare:)];
	for (NSNumber *idxObj in sortedKeys) {
		XTTextTableBlock *textTableBlock = textTableBlocksDefiningColumnWidthsByIndex[idxObj];
		if (textTableBlock.widthAsPercentage != nil) {
			NSUInteger widthPercentage = textTableBlock.widthAsPercentage.integerValue;
			sumOfWidthPercentages2 += widthPercentage;
			if (sumOfWidthPercentages2 > 100) {
				NSUInteger excess = sumOfWidthPercentages2 - 100;
				sumOfWidthPercentages2 -= excess;
				widthPercentage -= excess;
			}
			CGFloat columnWidth = totalContentWidthOfTable * ((widthPercentage /** percentageScaledownFactor*/) / 100.00);
			columnWidth = floor(columnWidth);
			
			XTTableColumnInfo *columnInfo = self.columnInfoByColumn[idxObj];
			CGFloat minWidth = [columnInfo getComputedMinContentRectWidth];
			if (columnWidth < minWidth) {
				//XT_WARN_3(@"%% columnWidth=%lf < minWidth=%lf -- using minWidth=%lf", columnWidth, minWidth, minWidth);
				columnWidth = minWidth;
			}
				
			//XT_WARN_2(@"%% [%lu] = %lf", idxObj.unsignedIntegerValue, columnWidth);
			columnWidthByIndex[idxObj] = [NSNumber numberWithDouble:columnWidth];

			totalContentWidthUsed += columnWidth;
		}
	}

	// Handle #width=... (abs/pixel width)
	//-------------------------------------

	//If there's any more space after satisfying percentage width requests, we try to satisfy pixel width requests. If there's enough space to satisfy all pixel width requests, we set each pixel-width column to its exact requested width; otherwise, we distribute the remaining space among the pixel-width columns in proportion to the requested widths.

	CGFloat contentWidthToDistribute = totalContentWidthOfTable - totalContentWidthUsed;

	NSUInteger sumOfWidthPoints = 0;
	for (XTTextTableBlock *textTableBlock in textTableBlocksDefiningColumnWidthsByIndex.allValues) {
		if (textTableBlock.widthAsPoints != nil) {
			sumOfWidthPoints += textTableBlock.widthAsPoints.unsignedIntegerValue;
		}
	}

	// Distr for min widths, increasing totalContentWidthUsed:
	CGFloat compensateForMinWidthsFactor = 0.66;
	BOOL compensateForMinWidths = (sumOfWidthPoints > contentWidthToDistribute);
	//XT_WARN_4(@"abs [%lu] contentWidthToDistribute before mins = %lf, sumOfWidthPoints=%lu, compensateForMinWidths=%d", column, contentWidthToDistribute, sumOfWidthPoints, compensateForMinWidths);
	if (compensateForMinWidths) {
		for (NSNumber *idxObj in textTableBlocksDefiningColumnWidthsByIndex.allKeys) {
			XTTextTableBlock *textTableBlock = textTableBlocksDefiningColumnWidthsByIndex[idxObj];
			if (textTableBlock.widthAsPoints != nil) {
				XTTableColumnInfo *columnInfo = self.columnInfoByColumn[idxObj];
				CGFloat minWidth = [columnInfo getComputedMinContentRectWidth];
				contentWidthToDistribute -= floor(minWidth * compensateForMinWidthsFactor);
			}
		}
		compensateForMinWidths = (sumOfWidthPoints > contentWidthToDistribute);
		//XT_WARN_2(@"abs [%lu] recalcd compensateForMinWidths=%d", column, compensateForMinWidths);
	}
	//XT_WARN_2(@"abs [%lu] contentWidthToDistribute after mins = %lf ", column, contentWidthToDistribute);

	CGFloat pointsScaledownFactor = 1.0;
	if (compensateForMinWidths) {
		pointsScaledownFactor = contentWidthToDistribute / (CGFloat)sumOfWidthPoints;
	}
	//XT_WARN_4(@"abs [%lu] sumOfWidthPoints=%lu contentWidthToDistribute=%lf pointsScaledownFactor=%lf", column, sumOfWidthPoints, contentWidthToDistribute, pointsScaledownFactor);
	for (NSNumber *idxObj in textTableBlocksDefiningColumnWidthsByIndex.allKeys) {
		XTTextTableBlock *textTableBlock = textTableBlocksDefiningColumnWidthsByIndex[idxObj];
		if (textTableBlock.widthAsPoints != nil) {
			XTTableColumnInfo *columnInfo = self.columnInfoByColumn[idxObj];
			CGFloat minWidth = [columnInfo getComputedMinContentRectWidth];
			CGFloat columnWidth = 0.0;
			if (compensateForMinWidths) {
				columnWidth += floor(minWidth * compensateForMinWidthsFactor);
				//XT_WARN_3(@"abs [%lu] columnWidth += minWidth %lf -> %lf", idxObj.unsignedIntegerValue, minWidth, columnWidth);
			}
			CGFloat toAdd = ((CGFloat)textTableBlock.widthAsPoints.unsignedIntegerValue) /* * factor*/ * pointsScaledownFactor;
			toAdd = floor(toAdd);
			columnWidth += toAdd;
			//XT_WARN_4(@"abs [%lu] raw columnWidth=%lf (minWidth=%lf + toAdd=%lf)", idxObj.unsignedIntegerValue, columnWidth, minWidth, toAdd);
			
			if (columnWidth < minWidth) {
				//XT_WARN_4(@"abs [%lu] columnWidth=%lf < minWidth=%lf -- using minWidth=%lf", idxObj.unsignedIntegerValue, columnWidth, minWidth, minWidth);
				columnWidth = minWidth;
			}

			columnWidthByIndex[idxObj] = [NSNumber numberWithDouble:columnWidth];
			//XT_WARN_2(@"abs [%lu] = %lf", idxObj.unsignedIntegerValue, columnWidth);

			totalContentWidthUsed += columnWidth;
		}
	}
	
	contentWidthToDistribute = totalContentWidthOfTable - totalContentWidthUsed;

	// Handle #width not specified
	//-------------------------------------

	//XT_WARN_2(@"[%ld] START #width not specified: contentWidthToDistribute = %lf", column, contentWidthToDistribute);

	for (NSNumber *idxObj in self.columnInfoByColumn.allKeys) {
		XTTextTableBlock *textTableBlock = textTableBlocksDefiningColumnWidthsByIndex[idxObj];
		if (textTableBlock == nil) {
			XTTableColumnInfo *columnInfo = self.columnInfoByColumn[idxObj];
			CGFloat columnWidth = [columnInfo getComputedMinContentRectWidth];
			columnWidthByIndex[idxObj] = [NSNumber numberWithDouble:columnWidth];
			totalContentWidthUsed += columnWidth;
			//XT_WARN_3(@"[%lu] #width not specified [%lu]: columnWidth = %lf", column, idxObj.unsignedIntegerValue, columnWidth);
		}
	}

	contentWidthToDistribute = totalContentWidthOfTable - totalContentWidthUsed;

	// Handle excess table width
	//---------------------------

	contentWidthToDistribute = totalContentWidthOfTable - totalContentWidthUsed;
	//XT_WARN_2(@"[%ld] START excess table width: contentWidthToDistribute = %lf", column, contentWidthToDistribute);
	CGFloat newContentWidthToDistribute = contentWidthToDistribute;

	//If there are any columns without any width requests at all, we distribute all of the extra space to those columns;
	
	BOOL haveColumnsWithoutWidthAttr = (textTableBlocksDefiningColumnWidthsByIndex.count == 0);
	if (! haveColumnsWithoutWidthAttr) {
		for (NSNumber *idxObj in self.columnInfoByColumn.allKeys) {
			XTTextTableBlock *textTableBlock = textTableBlocksDefiningColumnWidthsByIndex[idxObj];
			if (textTableBlock == nil) {
				haveColumnsWithoutWidthAttr = YES;
				break;
			}
		}
	}
	
	// otherwise, we distribute the space to all columns.
	// We distribute space as follows:
	
	// We first distribute space to eligible columns that are below their maximum column widths. We distribute this space proportionally to the difference
	// between maximum and minimum widths for these columns. (This odd basis was chosen because it causes the columns to expand out to their maximum widths evenly as the table width increases.)
	
	CGFloat weightAbsColumns = 1.0;
	if (contentWidthToDistribute >= 1.0) {
		BOOL havePercentageColumns = NO;
		BOOL haveAbsColumns = NO;
		for (NSNumber *idxObj in self.columnInfoByColumn.allKeys) {
			XTTextTableBlock *textTableBlock = textTableBlocksDefiningColumnWidthsByIndex[idxObj];
			if (textTableBlock.widthAsPercentage != nil) {
				havePercentageColumns = YES;
			}
			if (textTableBlock.widthAsPoints != nil) {
				haveAbsColumns = YES;
			}
			if (havePercentageColumns && haveAbsColumns) {
				break;
			}
		}
		if (havePercentageColumns && haveAbsColumns) {
			weightAbsColumns = 0.25;
		}
	}
	
	BOOL iterate = YES;
	//XT_WARN_2(@"[%ld] START colWidth < maxColWidth : contentWidthToDistribute = %lf", column, contentWidthToDistribute);
	while (iterate) {
		iterate = NO;
		if (contentWidthToDistribute >= 1.0) {

			CGFloat sumOfDiffMaxWidthMinColumnWidth = 0.0;
			for (NSNumber *idxObj in self.columnInfoByColumn.allKeys) {
				XTTableColumnInfo *columnInfo = self.columnInfoByColumn[idxObj];
				CGFloat colWidth = columnWidthByIndex[idxObj].doubleValue;
				CGFloat maxColWidth = [columnInfo getComputedMaxContentRectWidth];
				XTTextTableBlock *textTableBlock = textTableBlocksDefiningColumnWidthsByIndex[idxObj];
				BOOL columnIsEligible = [self columnIsEligibleForColWidth:colWidth
															  maxColWidth:maxColWidth
												  columnsWithoutWidthAttr:haveColumnsWithoutWidthAttr
														   textTableBlock:textTableBlock];
				if (columnIsEligible) {
					CGFloat diff = [columnInfo getComputedMaxContentRectWidth] - [columnInfo getComputedMinContentRectWidth];
					sumOfDiffMaxWidthMinColumnWidth += diff;
				}
			}

			if (sumOfDiffMaxWidthMinColumnWidth > 0.0) {
				//XT_WARN_1(@"excess: colWidth < maxColWidth : col.count=%lu", self.columnInfoByColumn.allKeys.count);
				for (NSNumber *idxObj in self.columnInfoByColumn.allKeys) {
					//XT_WARN_2(@"excess: colWidth < maxColWidth : idxObj=%lu newContentWidthToDistribute=%lf", idxObj.unsignedIntegerValue, newContentWidthToDistribute);
					XTTableColumnInfo *columnInfo = self.columnInfoByColumn[idxObj];
					CGFloat colWidth = columnWidthByIndex[idxObj].doubleValue;
					CGFloat maxColWidth = [columnInfo getComputedMaxContentRectWidth];
					XTTextTableBlock *textTableBlock = textTableBlocksDefiningColumnWidthsByIndex[idxObj];
					BOOL columnIsEligible = [self columnIsEligibleForColWidth:colWidth
																  maxColWidth:maxColWidth
													  columnsWithoutWidthAttr:haveColumnsWithoutWidthAttr
															   textTableBlock:textTableBlock];
					if (columnIsEligible && newContentWidthToDistribute >= 1.0) {
						CGFloat diff = [columnInfo getComputedMaxContentRectWidth] - [columnInfo getComputedMinContentRectWidth];
						CGFloat fraction = diff / sumOfDiffMaxWidthMinColumnWidth;
						CGFloat toAdd = contentWidthToDistribute * fraction;
						if (textTableBlock.widthAsPoints != nil) {
							toAdd *= weightAbsColumns;
							iterate = YES;
						}
						toAdd = ceil(toAdd);
						CGFloat newColumnWidth = columnWidthByIndex[idxObj].doubleValue;
						//XT_WARN_2(@"toAdd %lf -> %lf", toAdd, ceil(toAdd));
						if ((newColumnWidth + toAdd) > maxColWidth) {
							CGFloat toSubtract = (newColumnWidth + toAdd) - maxColWidth;
							toAdd -= toSubtract;
							//XT_WARN_2(@"[%lu] toAdd -= %lf", idxObj.unsignedIntegerValue, toSubtract);
							iterate = YES;
						}
						if (newContentWidthToDistribute - toAdd < 0.0) {
							toAdd = newContentWidthToDistribute;
							//XT_WARN_2(@"[%lu] toAdd hard= %lf", idxObj.unsignedIntegerValue, newContentWidthToDistribute);
						}
						newColumnWidth += toAdd;
						if (newColumnWidth < [columnInfo getComputedMinContentRectWidth]) {
							CGFloat toSub = [columnInfo getComputedMinContentRectWidth] - newColumnWidth;
							toAdd -= toSub;
							//XT_WARN_4(@"newColumnWidth %lf < min %lf, toAdd -= %lf -> %lf", newColumnWidth, columnInfo.minContentRectWidth.doubleValue, toSub, toAdd);
							newColumnWidth = [columnInfo getComputedMinContentRectWidth];
						}
						//XT_WARN_3(@"excess: colWidth < maxColWidth : [%lu] += %lf -> %lf", idxObj.unsignedIntegerValue, toAdd, newColumnWidth);
						columnWidthByIndex[idxObj] = [NSNumber numberWithDouble:newColumnWidth];
						newContentWidthToDistribute -= toAdd;
					}
				}
			}
			
			contentWidthToDistribute = newContentWidthToDistribute;
		}
	}
	//XT_WARN_2(@"[%ld] DONE colWidth < maxColWidth : contentWidthToDistribute = %lf", column, contentWidthToDistribute);

	// Once all eligible columns are at their maximum widths, we distribute any remaining space to the eligible columns in proportion to their maximum widths.
	// (This keeps the amount of whitespace in each column balanced with the amount of content in that column.)

	/*BOOL*/ iterate = YES;
	while (iterate) {
		//XT_WARN_0(@"going...");
		iterate = NO;
		if (contentWidthToDistribute >= 1.0) {
			
			CGFloat sumOfMaxColumnWidths = 0.0;
			for (NSNumber *idxObj in self.columnInfoByColumn.allKeys) {
				XTTextTableBlock *textTableBlock = textTableBlocksDefiningColumnWidthsByIndex[idxObj];
				BOOL columnIsEligible = [self columnIsEligibleForColumnsWithoutWidthAttr:haveColumnsWithoutWidthAttr
																		  textTableBlock:textTableBlock];
				if (columnIsEligible) {
					XTTableColumnInfo *columnInfo = self.columnInfoByColumn[idxObj];
					CGFloat maxColWidth = [columnInfo getComputedMaxContentRectWidth];
					if (textTableBlock.widthAsPoints.doubleValue > maxColWidth) {
						maxColWidth = textTableBlock.widthAsPoints.doubleValue; // - (maxColWidth * 1.0);
					}
					sumOfMaxColumnWidths += maxColWidth;
				}
			}
			
			if (sumOfMaxColumnWidths >= 0.0) {
				for (NSNumber *idxObj in self.columnInfoByColumn.allKeys) {
					if (newContentWidthToDistribute < 1.0) {
						break;
					}
					
					XTTextTableBlock *textTableBlock = textTableBlocksDefiningColumnWidthsByIndex[idxObj];
					BOOL columnIsEligible = [self columnIsEligibleForColumnsWithoutWidthAttr:haveColumnsWithoutWidthAttr
																			  textTableBlock:textTableBlock];
					if (columnIsEligible) {
						XTTableColumnInfo *columnInfo = self.columnInfoByColumn[idxObj];
						CGFloat maxColWidth = [columnInfo getComputedMaxContentRectWidth];
						if (textTableBlock.widthAsPoints.doubleValue > maxColWidth) {
							maxColWidth = textTableBlock.widthAsPoints.doubleValue; // - (maxColWidth * 1.0);
						}

						CGFloat fraction = maxColWidth / sumOfMaxColumnWidths;
						CGFloat toAdd = contentWidthToDistribute * fraction;
						CGFloat newColumnWidth = columnWidthByIndex[idxObj].doubleValue;
						if (textTableBlock.widthAsPoints != nil) {
							toAdd *= weightAbsColumns;
						}
						toAdd = floor(toAdd);
						if (newContentWidthToDistribute - toAdd < 0.0) {
							toAdd = newContentWidthToDistribute;
						}
						iterate = (toAdd >= 1.0);
						newColumnWidth += toAdd;
						//XT_WARN_3(@"excess : other : [%lu] += %lf -> %lf", idxObj.unsignedIntegerValue, toAdd, newColumnWidth);
						columnWidthByIndex[idxObj] = [NSNumber numberWithDouble:newColumnWidth];
						newContentWidthToDistribute -= toAdd;
					}
				}

				contentWidthToDistribute = newContentWidthToDistribute;
			}
		}
	}
	//XT_WARN_0(@"...done going");

	// Distribute any remainder from the floor()'ing above,
	// preferring columns that are narrower than their min. width
	
	for (NSNumber *idxObj in self.columnInfoByColumn.allKeys) {
		if (contentWidthToDistribute < 1.0) {
			break;
		}
		XTTableColumnInfo *columnInfo = self.columnInfoByColumn[idxObj];
		CGFloat colWidth = columnWidthByIndex[idxObj].doubleValue;
		CGFloat minColWidth = [columnInfo getComputedMinContentRectWidth];
		if (colWidth < minColWidth) {
			CGFloat toAdd = 1.0;
			CGFloat newColWidth = colWidth + toAdd;
			columnWidthByIndex[idxObj] = [NSNumber numberWithDouble:newColWidth];
			contentWidthToDistribute -= toAdd;
		}
	}
	
	for (NSNumber *idxObj in self.columnInfoByColumn.allKeys) {
		if (contentWidthToDistribute < 1.0) {
			break;
		}
		CGFloat colWidth = columnWidthByIndex[idxObj].doubleValue;
		CGFloat toAdd = 1.0;
		CGFloat newColWidth = colWidth + toAdd;
		columnWidthByIndex[idxObj] = [NSNumber numberWithDouble:newColWidth];
		contentWidthToDistribute -= toAdd;
	}

	if (contentWidthToDistribute >= 1.0) {
		XT_WARN_1(@"contentWidthToDistribute %lf > 0.0", contentWidthToDistribute);
	}
	
	// Handle table width less than sum of min. column widths
	//--------------------------------------------------------
	
	//if (contentWidthToDistribute < 0.0) {
	//	XT_WARN_1(@"Handle table width less than sum of min: contentWidthToDistribute %lf < 0.0", contentWidthToDistribute);
	//}

	// Prefer columns > min width
	
	/*BOOL*/ iterate = YES;
	while (iterate) {
		//XT_WARN_1(@"going2... contentWidthToDistribute=%lf", contentWidthToDistribute);
		iterate = NO;
		for (NSNumber *idxObj in self.columnInfoByColumn.allKeys) {
			if (contentWidthToDistribute > -1.0) {
				break;
			}
			XTTableColumnInfo *columnInfo = self.columnInfoByColumn[idxObj];
			CGFloat colWidth = columnWidthByIndex[idxObj].doubleValue;
			CGFloat minColWidth = [columnInfo getComputedMinContentRectWidth];
			if (colWidth > minColWidth) {
				CGFloat toSub = 1.0;
				CGFloat newColWidth = colWidth - toSub;
				columnWidthByIndex[idxObj] = [NSNumber numberWithDouble:newColWidth];
				contentWidthToDistribute += toSub;
				iterate = YES;
				//XT_WARN_2(@"going2... using newColWidth=%lf contentWidthToDistribute=%lf", newColWidth, contentWidthToDistribute);
			} else {
				//XT_WARN_0(@"going2... NOT recalc'ing");
			}
		}
	}
	//XT_WARN_0(@"... done going2");

	// Then subtract from all columns
	//TODO !!! adapt: ideally we should subtract acc'ing to max width

	/*BOOL*/ iterate = (contentWidthToDistribute < 0.0);
	while (iterate) {
		//XT_WARN_1(@"going3... contentWidthToDistribute=%lf", contentWidthToDistribute);
		CGFloat oldContentWidthToDistribute = contentWidthToDistribute;
		for (NSNumber *idxObj in self.columnInfoByColumn.allKeys) {
			if (contentWidthToDistribute > -1.0) {
				break;
			}
			CGFloat colWidth = columnWidthByIndex[idxObj].doubleValue;
			CGFloat toSub = 1.0;
			CGFloat newColWidth = colWidth - toSub;
			if (newColWidth >= 14) {
				columnWidthByIndex[idxObj] = [NSNumber numberWithDouble:newColWidth];
				contentWidthToDistribute += toSub;
				//XT_WARN_3(@"going3... [%lu] using newColWidth=%lf contentWidthToDistribute=%lf", idxObj.unsignedIntegerValue, newColWidth, contentWidthToDistribute);
			} else {
				//XT_WARN_3(@"going3... [%lu] NOT using newColWidth=%lf contentWidthToDistribute=%lf", idxObj.unsignedIntegerValue, newColWidth, contentWidthToDistribute);
			}
		}
		iterate = (contentWidthToDistribute > oldContentWidthToDistribute);
	}
	//XT_WARN_1(@"... done going3 contentWidthToDistribute=%lf", contentWidthToDistribute);

	// We shouldn't have a deficit by now

	if (contentWidthToDistribute < 0.0) {
		XT_WARN_1(@"contentWidthToDistribute %lf < 0.0", contentWidthToDistribute);
	}

	// Finish up
	//-----------
	
	NSNumber *columnObj = [NSNumber numberWithInteger:column];
	XTTableColumnInfo *columnInfo = self.columnInfoByColumn[columnObj];
	columnInfo.contentRectWidth = columnWidthByIndex[columnObj];

	CGFloat res = columnInfo.contentRectWidth.doubleValue;
	//XT_WARN_2(@"[%ld] --> %lf", column, res);
	return res;
}

- (BOOL)columnIsEligibleForColWidth:(CGFloat)colWidth
						maxColWidth:(CGFloat)maxColWidth
			columnsWithoutWidthAttr:(BOOL)columnsWithoutWidthAttr
					 textTableBlock:(XTTextTableBlock *)textTableBlock
{
	BOOL res = (colWidth < maxColWidth);
	if (res) {
		if (columnsWithoutWidthAttr) {
			res = (textTableBlock == nil);
		} else {
			res = (textTableBlock.widthAsPoints == nil);
		}
	}
	return res;
}

- (BOOL)columnIsEligibleForColumnsWithoutWidthAttr:(BOOL)columnsWithoutWidthAttr
									textTableBlock:(XTTextTableBlock *)textTableBlock
{
	BOOL res;
	if (columnsWithoutWidthAttr) {
		res = (textTableBlock == nil);
	} else {
		res = YES;
	}
	return res;
}

- (NSMutableDictionary<NSNumber *, XTTextTableBlock *> *)getTextTableBlocksDefiningColumnWidths:(XTTextTable *)table
{
	//TODO !!! adapt: sep method, use for overall width too
	NSUInteger columnCount = self.columnInfoByColumn.count;
	NSMutableDictionary<NSNumber *, XTTextTableBlock *>*res = [NSMutableDictionary dictionaryWithCapacity:columnCount];
	
	for (NSUInteger columnIdx = 0; columnIdx < columnCount; columnIdx++) {
		NSUInteger maxWidthAsPercentage = 0;
		NSUInteger maxWidthAsPoints = 0;
		NSUInteger maxRowIdx = [table maxRowIndexForColumn:columnIdx];
		for (NSUInteger rowIdx = 0; rowIdx <= maxRowIdx; rowIdx++) {
			XTTextTableBlock *textTableBlock = [table textTableBlockAtRow:rowIdx column:columnIdx];
			if (textTableBlock == nil) {
				continue;
			}
			//	 If any column has an explicit pixel width specified, we'll try to make the table big enough to accomodate that pixel width.
			if (textTableBlock.widthAsPercentage != nil) {
				NSUInteger widthAsPercentage = textTableBlock.widthAsPercentage.unsignedIntegerValue;
				if (widthAsPercentage > maxWidthAsPercentage) {
					maxWidthAsPercentage = widthAsPercentage;
					NSNumber *columnIdxObj = [NSNumber numberWithUnsignedInteger:columnIdx];
					res[columnIdxObj] = textTableBlock;
				}
			} else if (textTableBlock.widthAsPoints != nil) {
				if (maxWidthAsPercentage <= 0) {
					NSUInteger widthAsPoints = textTableBlock.widthAsPoints.unsignedIntegerValue;
					if (widthAsPoints > maxWidthAsPoints) {
						//TODO !!! adapt: keep? if (textTableBlocksDefiningColumnWidths[columnIdx].widthAsPercentage == nil) {
							maxWidthAsPoints = widthAsPoints;
							NSNumber *columnIdxObj = [NSNumber numberWithUnsignedInteger:columnIdx];
							res[columnIdxObj] = textTableBlock;
						//}
					}
				}
			}
		}
	}
	
	return res;
}

- (CGFloat)totalMinColumnWidthExcludingBounds
{
	CGFloat res = 0.0;
	for (XTTableColumnInfo *columnInfo in self.columnInfoByColumn.allValues) {
		res += [columnInfo getComputedMinContentRectWidth];
	}
	return res;
}

- (CGFloat)totalMinColumnHeightExcludingBounds
{
	CGFloat res = 0.0;
	for (XTTableRowInfo *rowInfo in self.rowInfoByRow.allValues) {
		res += rowInfo.minContentRectHeight.doubleValue;
	}
	return res;
}

- (CGFloat)totalMaxColumnWidthExcludingBounds
{
	CGFloat res = 0.0;
	for (XTTableColumnInfo *columnInfo in self.columnInfoByColumn.allValues) {
		res += [columnInfo getComputedMaxContentRectWidth];
	}
	return res;
}

- (CGFloat)totalWidthOfBounds
{
	CGFloat res = 0.0;
	for (XTTableColumnInfo *columnInfo in self.columnInfoByColumn.allValues) {
		res += columnInfo.totalBoundsWidth.doubleValue;
	}
	return res;
}

//TODO !!! clean up
- (CGFloat)totalWidthOfInnerBounds
{
	CGFloat res = 0.0;
	
	if (self.columnInfoByColumn.count >= 1) {
		NSUInteger lowestColumnIndex = 0;
		//NSUInteger highestColumnIndex = self.columnInfoByColumn.count - 1;
		for (NSNumber *keyObj in self.columnInfoByColumn.allKeys) {
			NSUInteger key = [keyObj unsignedIntegerValue];
			XTTableColumnInfo *columnInfo = self.columnInfoByColumn[keyObj];
			CGFloat toAdd = columnInfo.totalBoundsWidth.doubleValue;
			if (key <= lowestColumnIndex /*|| key >= highestColumnIndex*/) {
				toAdd /= 2.0;
			}
			res += toAdd;
		}
	}
	
	res = floor(res);
	return res;
}

- (CGFloat)totalHeightOfBounds
{
	CGFloat res = 0.0;
	for (XTTableRowInfo *rowInfo in self.rowInfoByRow.allValues) {
		res += rowInfo.totalBoundsHeight.doubleValue;
	}
	return res;
}

- (CGFloat)contentRectXForTextTableBlock:(XTTextTableBlock *)textTableBlock
								 originX:(CGFloat)originX
							 usableWidth:(CGFloat)usableWidth
{
	//XT_DEF_SELNAME;
	
	NSInteger column = textTableBlock.startingColumn;
	CGFloat res = originX;
	//CGFloat res0 = originX;

	CGFloat totalTableWidth = 0.0;
	
	for (NSNumber *columnObj in self.columnInfoByColumn.allKeys) {
		XTTableColumnInfo *columnInfo = self.columnInfoByColumn[columnObj];
		CGFloat colWidth = columnInfo.contentRectWidth.doubleValue + columnInfo.totalBoundsWidth.doubleValue;
		if (columnObj.integerValue < column) {
			// We're to the left of textTableBlock's column
			
			//res0 += columnInfo.maxBoundsRectWidth.doubleValue;
			//res += columnInfo.contentRectWidth.doubleValue;
			//res += columnInfo.totalBoundsWidth.doubleValue;
			res += colWidth;
			//if (res0 != res) {
			//	int brkpt = 1;
			//}
			
			//TODO !!! might mess with td margin=
			//TODO !!! use cell level border size too?
		//} else if (columnObj.integerValue <= column) {
			//totalTableWidth += columnInfo.maxBoundsRectWidth.doubleValue;
				//TODO !!! align with totalTableWidth func?
		}
		totalTableWidth += colWidth;
	}

	res += [textTableBlock totalBoundsWidthOnLeftHandSide];
	//XT_WARN_1(@"--> %lf", res);
	
	XTTextTable *table = (XTTextTable *)textTableBlock.table;
	if (table.alignment == XT_TEXT_ALIGN_RIGHT || table.alignment == XT_TEXT_ALIGN_CENTER) {
		//CGFloat totalTableWidth = [self totalTableWidth];
		// Note: On window resize, totalTableWidth is inaccurate because it's based on the previous window size.
		//       To compensate for this, we re-layout the window after each resize step
			//TODO !!! do re-layout even after first-time layout?
		CGFloat widthToDistribute = usableWidth - totalTableWidth;
		if (widthToDistribute >= 2.0) {
			// Note: test against 2.0 because we don't always use the remainder. Prevents jittery lhs x pos.
			CGFloat adjustment = widthToDistribute;
			if (table.alignment == XT_TEXT_ALIGN_CENTER) {
				adjustment = adjustment / 2.0;
			}
			res += adjustment;
			//XT_WARN_5(@"row=%ld col=%ld usableWidth=%lf totalTableWidth=%lf -> adjustment=%lf",
			//		  textTableBlock.startingRow, textTableBlock.startingColumn, usableWidth, totalTableWidth, adjustment);
		} else {
			//XT_WARN_0(@"other");
			//XT_WARN_5(@"row=%ld col=%ld usableWidth=%lf totalTableWidth=%lf -> adjustment=%lf",
			//		  textTableBlock.startingRow, textTableBlock.startingColumn, usableWidth, totalTableWidth, 0.0);
		}
	}

	//TODO !!! test makes no sense:
	//if (res + usableWidth > usableWidth) {
	//	int brkpt = 1;
	//}

	return res;
}

- (CGFloat)totalTableWidth
{
	CGFloat res = 0.0;

	for (NSNumber *columnObj in self.columnInfoByColumn.allKeys) {
		XTTableColumnInfo *columnInfo = self.columnInfoByColumn[columnObj];
		res += columnInfo.boundsRectWidth.doubleValue;
	}

	return res;
}

- (void)clear
{
	[self.columnInfoByColumn removeAllObjects];
}

- (void)clearForRecalcOfTables
{
	for (XTTableRowInfo *rowInfo in self.rowInfoByRow.allValues) {
		rowInfo.minContentRectHeight = nil;
	}
}

- (XTTableColumnInfo *)getOrCreateColumnInfo:(NSInteger)column
{
	XTTableColumnInfo *columnInfo = [self getColumnInfo:column];
	if (columnInfo == nil) {
		columnInfo = [XTTableColumnInfo new];
		NSNumber *columnObj = [NSNumber numberWithInteger:column];
		self.columnInfoByColumn[columnObj] = columnInfo;
	}
	return columnInfo;
}

- (XTTableRowInfo *)getOrCreateRowInfo:(NSInteger)row
{
	XTTableRowInfo *rowInfo = [self getRowInfo:row];
	if (rowInfo == nil) {
		rowInfo = [XTTableRowInfo new];
		NSNumber *rowObj = [NSNumber numberWithInteger:row];
		self.rowInfoByRow[rowObj] = rowInfo;
	}
	return rowInfo;
}

- (XTTableColumnInfo *)getColumnInfo:(NSInteger)column
{
	NSNumber *columnObj = [NSNumber numberWithInteger:column];
	XTTableColumnInfo *columnInfo = self.columnInfoByColumn[columnObj];
	return columnInfo;
}

- (XTTableRowInfo *)getRowInfo:(NSInteger)row
{
	NSNumber *rowObj = [NSNumber numberWithInteger:row];
	XTTableRowInfo *rowInfo = self.rowInfoByRow[rowObj];
	return rowInfo;
}

@end
