/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2024 Olaf Wintermann. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#import "GridLayout.h"



@implementation GridLayout

@synthesize uilayout=_uilayout;
@synthesize container=_container;

- (GridLayout*)init {
    self = [super init];
    _columnspacing = 0;
    _rowspacing = 0;
    _children = cxArrayListCreateSimple(sizeof(GridElm), 32);
    _preferredSize.width = -1;
    _preferredSize.height = -1;
    
    return self;
}

/*
- (void) layout {
    [super layout];
    
    NSRect r1 = _test.frame;
    NSSize s1 = _test.intrinsicContentSize;
    NSEdgeInsets e1 = _test.alignmentRectInsets;
    
}
 */

- (BOOL)isFlipped {
    return YES;
}

- (void) layout {
    int ncols = _cols+1;
    int nrows = _rows+1;
    
    GridDef *cols = calloc(ncols, sizeof(GridDef));
    GridDef *rows = calloc(nrows, sizeof(GridDef));
    
    //NSRect viewFrame = self.frame;
    NSRect viewFrame = self.bounds;
    
    int colspacing = _columnspacing;
    int rowspacing = _rowspacing;
    
    int span_max = 1;
    for(int r=0;r<2;r++) {
        CxIterator i = cxListIterator(_children);
        cx_foreach(GridElm *, elm, i) {
            int x = elm->x;
            int y = elm->y;
            GridDef *col = &cols[x];
            GridDef *row = &rows[y];
            
            NSSize size = elm->view.intrinsicContentSize;
            NSSize size2 = elm->view.fittingSize;
            if(size.width == NSViewNoIntrinsicMetric) {
                size.width = size2.width;
            }
            if(size.height == NSViewNoIntrinsicMetric) {
                size.height = size2.height;
            }
            if(size.width != NSViewNoIntrinsicMetric) {
                CGFloat width = size.width + elm->margin.left + elm->margin.right;
                if(width > cols[elm->x].preferred_size && elm->colspan <= 1 && span_max == 1) {
                    cols[elm->x].preferred_size = width;
                }
                elm->preferred_width = width;
            }
            if(size.height != NSViewNoIntrinsicMetric) {
                CGFloat height = size.height + elm->margin.top + elm->margin.bottom;
                if(height > rows[elm->y].preferred_size && elm->rowspan <= 1 && span_max == 1) {
                    rows[elm->y].preferred_size = height;
                }
                elm->preferred_height = height;
            }
            
            
            if(elm->rowspan > span_max || elm->colspan > span_max) {
                continue;
            }
            
            int end_col = x+elm->colspan;
            if(end_col > ncols) {
                end_col = ncols;
            }
            int end_row = y+elm->rowspan;
            if(end_row > nrows) {
                end_row = nrows;
            }
            
            // are all columns in the span > preferred_width?
            if(elm->colspan > 1) {
                int span_width = 0;
                GridDef *last_col = col;
                for(int c=x;c<end_col;c++) {
                    span_width += cols[c].size;
                    last_col = &cols[c];
                }
                if(span_width < elm->preferred_width) {
                    last_col->size += elm->preferred_width - span_width;
                }
            }
            // are all rows in the span > preferred_height?
            if(elm->rowspan > 1) {
                int span_height = 0;
                GridDef *last_row = row;
                for(int c=x;c<end_row;c++) {
                    span_height += rows[c].size;
                    last_row = &rows[c];
                }
                if(span_height < elm->preferred_height) {
                    last_row->size += elm->preferred_height - span_height;
                }
            }
            
            if(elm->hexpand) {
                if(elm->colspan > 1) {
                    // check if any column in the span is expanding
                    // if not, make the last column expanding
                    GridDef *last_col = col;
                    for(int c=x;c<end_col;c++) {
                        last_col = &cols[c];
                        if(last_col->expand) {
                            break;
                        }
                    }
                    last_col->expand = TRUE;
                } else {
                    col->expand = TRUE;
                }
            }
            if(elm->vexpand) {
                if(elm->rowspan > 1) {
                    // same as colspan
                    GridDef *last_row = row;
                    for(int c=x;c<nrows;c++) {
                        last_row = &rows[c];
                        if(last_row->expand) {
                            break;
                        }
                    }
                    last_row->expand = TRUE;
                } else {
                    row->expand = TRUE;
                }
            }
        }
        span_max = 50000; // not sure if this is unreasonable low or high
    }
    
    
    int col_ext = 0;
    int row_ext = 0;
    
    int preferred_width = 0;
    int preferred_height = 0;
    for(int j=0;j<ncols;j++) {
        preferred_width += cols[j].preferred_size;
        if(cols[j].expand) {
            col_ext++;
        }
    }
    for(int j=0;j<nrows;j++) {
        preferred_height += rows[j].preferred_size;
        if(rows[j].expand) {
            row_ext++;
        }
    }
    if(ncols > 0) {
        preferred_width += (ncols-1) * colspacing;
    }
    if(nrows > 0) {
        preferred_height += (nrows-1) * rowspacing;
    }
    
    _preferredSize.width = preferred_width;
    _preferredSize.height = preferred_height;
    
    
    int hremaining = viewFrame.size.width - preferred_width;
    int vremaining = viewFrame.size.height - preferred_height;
    int hext = hremaining/col_ext;
    int vext = vremaining/row_ext;
    
    for(int j=0;j<ncols;j++) {
        GridDef *col = &cols[j];
        if(col->expand) {
            col->size = col->preferred_size + hext;
        } else {
            col->size = col->preferred_size;
        }
    }
    for(int j=0;j<nrows;j++) {
        GridDef *row = &rows[j];
        if(row->expand) {
            row->size = row->preferred_size + vext;
        } else {
            row->size = row->preferred_size;
        }
    }
    
    int pos = 0;
    for(int j=0;j<ncols;j++) {
        cols[j].pos = pos;
        pos += cols[j].size + colspacing;
    }
    pos = 0;
    for(int j=0;j<nrows;j++) {
        rows[j].pos = pos;
        pos += rows[j].size + rowspacing;
    }
    
    CxIterator i = cxListIterator(_children);
    cx_foreach(GridElm *, elm, i) {
        //NSSize size = elm->view.intrinsicContentSize;
        GridDef *col = &cols[elm->x];
        GridDef *row = &rows[elm->y];
        
        NSRect frame;
        if(elm->hfill) {
            if(elm->colspan > 1) {
                int cwidth = 0;
                int end_col = elm->x + elm->colspan;
                if(end_col > ncols) {
                    end_col = ncols;
                }
                int real_span = 0;
                for(int c=elm->x;c<end_col;c++) {
                    cwidth += cols[c].size;
                    real_span++;
                }
                if(real_span > 0) {
                    cwidth += (real_span-1) * colspacing;
                }
                frame.size.width = cwidth;
            } else {
                frame.size.width = col->size;
            }
        } else {
            frame.size.width = elm->preferred_width;
        }
        frame.size.width -= elm->margin.left + elm->margin.right;
        if(elm->vfill) {
            if(elm->rowspan > 1) {
                int rheight = 0;
                int end_row = elm->y + elm->rowspan;
                if(end_row > nrows) {
                    end_row = nrows;
                }
                int real_span = 0;
                for(int r=elm->y;r<end_row;r++) {
                    rheight += rows[r].size;
                    real_span++;
                }
                if(real_span > 0) {
                    rheight += (real_span-1) * rowspacing;
                }
                frame.size.height = rheight;
            }
            frame.size.height = row->size;
        } else {
            frame.size.height = elm->preferred_height;
        }
        
        frame.origin.x = col->pos + elm->margin.left;
        frame.origin.y = row->pos + elm->margin.top;
        NSRect viewFrame = [elm->view frameForAlignmentRect:frame];
        elm->view.frame = viewFrame;
    }
    
    free(cols);
    free(rows);
}
 

- (NSSize)intrinsicContentSize {
    if(_preferredSize.width == -1) {
        [self layout];
    }
    return self.preferredSize;
}

- (void) addView:(NSView*)view margin:(NSEdgeInsets)margin {
    _preferredSize.width = -1;
    _preferredSize.height = -1;
    
    if(self.container->newline) {
        _y++;
        _x = 0;
        self.container->newline = FALSE;
    }
    
    GridElm elm;
    elm.x = _x;
    elm.y = _y;
    elm.margin = margin;
    elm.colspan = _uilayout.colspan;
    elm.rowspan = _uilayout.rowspan;
    if(_uilayout.fill) {
        elm.hfill = TRUE;
        elm.vfill = TRUE;
        elm.hexpand = TRUE;
        elm.vexpand = TRUE;
    } else {
        elm.hfill = _uilayout.hfill;
        elm.vfill = _uilayout.vfill;
        elm.hexpand = _uilayout.hexpand;
        elm.vexpand = _uilayout.vexpand;
    }
    elm.view = view;
    cxListAdd(_children, &elm);
    
    [self addSubview:view];
    self.needsLayout = YES;
    
    if(_x > _cols) {
        _cols = _x;
    }
    if(_y > _rows) {
        _rows = _y;
    }
    _x++;
}

- (void) dealloc {
    cxListFree(_children);
}

@end
