#import "TGGifKeyboardBalancedPartition.h"

#define NH_LP_TABLE_LOOKUP(table, i, j, rowsize) (table)[(i) * (rowsize) + (j)]

@implementation TGGifKeyboardBalancedPartition

+ (NSInteger *)linearPartitionTable:(NSArray *)sequence numPartitions:(NSInteger)numPartitions
{
    NSInteger k = numPartitions;
    NSInteger n = [sequence count];
    
    // allocate a table buffer of n * k integers
    NSInteger tableSize = sizeof(NSInteger) * n * k;
    NSInteger *tmpTable = (NSInteger *)malloc(tableSize);
    memset(tmpTable, 0, tableSize);
    
    // allocate a solution buffer of (n - 1) * (k - 1) integers
    NSInteger solutionSize = sizeof(NSInteger) * (n - 1) * (k - 1);
    NSInteger *solution = (NSInteger *)malloc(solutionSize);
    memset(solution, 0, solutionSize);
    
    // fill table with initial values
    for (NSInteger i = 0; i < n; i++) {
        NSInteger offset = i? NH_LP_TABLE_LOOKUP(tmpTable, i - 1, 0, k) : 0;
        NH_LP_TABLE_LOOKUP(tmpTable, i, 0, k) = [sequence[i] integerValue] + offset;
    }
    
    for (NSInteger j = 0; j < k; j++) {
        NH_LP_TABLE_LOOKUP(tmpTable, 0, j, k) = [sequence[0] integerValue];
    }
    
    // calculate the costs and fill the solution buffer
    for (NSInteger i = 1; i < n; i++) {
        for (NSInteger j = 1; j < k; j++) {
            NSInteger currentMin = 0;
            NSInteger minX = NSIntegerMax;
            
            for (NSInteger x = 0; x < i; x++) {
                NSInteger c1 = NH_LP_TABLE_LOOKUP(tmpTable, x, j - 1, k);
                NSInteger c2 = NH_LP_TABLE_LOOKUP(tmpTable, i, 0, k) - NH_LP_TABLE_LOOKUP(tmpTable, x, 0, k);
                NSInteger cost = MAX(c1, c2);
                
                if (!x || cost < currentMin) {
                    currentMin = cost;
                    minX = x;
                }
            }
            
            NH_LP_TABLE_LOOKUP(tmpTable, i, j, k) = currentMin;
            NH_LP_TABLE_LOOKUP(solution, i - 1, j - 1, k - 1) = minX;
        }
    }
    
    // free the tmp table, we don't need it anymore
    free(tmpTable);
    
    return solution;
}

+ (NSArray *)linearPartitionForSequence:(NSArray *)sequence numberOfPartitions:(NSInteger)numberOfPartitions
{
    NSInteger n = [sequence count];
    NSInteger k = numberOfPartitions;
    
    if (k <= 0) return @[];
    
    if (k >= n) {
        NSMutableArray *partition = [[NSMutableArray alloc] init];
        [sequence enumerateObjectsUsingBlock:^(id obj, __unused NSUInteger idx, __unused BOOL *stop) {
            [partition addObject:@[obj]];
        }];
        return [partition copy];
    }
    
    if (n == 1) {
        return @[sequence];
    }
    
    // get the solution table
    NSInteger *solution = [self linearPartitionTable:sequence numPartitions:numberOfPartitions];
    NSInteger solutionRowSize = numberOfPartitions - 1;
    
    k = k - 2;
    n = n - 1;
    NSMutableArray *answer = [NSMutableArray array];
    
    while (k >= 0) {
        if (n < 1) {
            [answer insertObject:@[] atIndex:0];
        } else {
            NSMutableArray *currentAnswer = [NSMutableArray array];
            for (NSInteger i = NH_LP_TABLE_LOOKUP(solution, n - 1, k, solutionRowSize) + 1, range = n + 1; i < range; i++) {
                [currentAnswer addObject:sequence[i]];
            }
            [answer insertObject:currentAnswer atIndex:0];
            
            n = NH_LP_TABLE_LOOKUP(solution, n - 1, k, solutionRowSize);
        }
        
        k = k - 1;
    }
    
    // free the solution table because we don't need it anymore
    free(solution);
    
    NSMutableArray *currentAnswer = [NSMutableArray array];
    for (NSInteger i = 0, range = n + 1; i < range; i++) {
        [currentAnswer addObject:sequence[i]];
    }
    
    [answer insertObject:currentAnswer atIndex:0];
    
    return [answer copy];
}

@end
